VYPR
High severity7.6NVD Advisory· Published May 22, 2025· Updated Apr 29, 2026

CVE-2025-4123

CVE-2025-4123

Description

A cross-site scripting (XSS) vulnerability exists in Grafana caused by combining a client path traversal and open redirect. This allows attackers to redirect users to a website that hosts a frontend plugin that will execute arbitrary JavaScript. This vulnerability does not require editor permissions and if anonymous access is enabled, the XSS will work. If the Grafana Image Renderer plugin is installed, it is possible to exploit the open redirect to achieve a full read SSRF.

The default Content-Security-Policy (CSP) in Grafana will block the XSS though the connect-src directive.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
github.com/grafana/grafanaGo
< 0.0.0-20250521183405-c7a690348df70.0.0-20250521183405-c7a690348df7

Affected products

8
  • Grafana/Grafana8 versions
    cpe:2.3:a:grafana:grafana:*:*:*:*:*:*:*:*+ 7 more
    • cpe:2.3:a:grafana:grafana:*:*:*:*:*:*:*:*range: <10.4.18
    • cpe:2.3:a:grafana:grafana:10.4.18:-:*:*:*:*:*:*
    • cpe:2.3:a:grafana:grafana:11.2.9:-:*:*:*:*:*:*
    • cpe:2.3:a:grafana:grafana:11.3.6:-:*:*:*:*:*:*
    • cpe:2.3:a:grafana:grafana:11.4.4:-:*:*:*:*:*:*
    • cpe:2.3:a:grafana:grafana:11.5.4:-:*:*:*:*:*:*
    • cpe:2.3:a:grafana:grafana:11.6.1:-:*:*:*:*:*:*
    • cpe:2.3:a:grafana:grafana:12.0.0:-:*:*:*:*:*:*

Patches

7
c7a690348df7

Apply security patch security-patch-202505051005.patch (#105754)

https://github.com/grafana/grafanaSofia PapagiannakiMay 21, 2025via ghsa
2 files changed · +183 5
  • pkg/api/static/static.go+6 5 modified
    @@ -159,16 +159,17 @@ func staticHandler(ctx *web.Context, log log.Logger, opt StaticOptions) bool {
     	if fi.IsDir() {
     		// Redirect if missing trailing slash.
     		if !strings.HasSuffix(ctx.Req.URL.Path, "/") {
    -			path := fmt.Sprintf("%s/", ctx.Req.URL.Path)
    -			if !strings.HasPrefix(path, "/") {
    +			redirectPath := path.Clean(ctx.Req.URL.Path)
    +			redirectPath = fmt.Sprintf("%s/", redirectPath)
    +			if !strings.HasPrefix(redirectPath, "/") {
     				// Disambiguate that it's a path relative to this server
    -				path = fmt.Sprintf("/%s", path)
    +				redirectPath = fmt.Sprintf("/%s", redirectPath)
     			} else {
     				// A string starting with // or /\ is interpreted by browsers as a URL, and not a server relative path
     				rePrefix := regexp.MustCompile(`^(?:/\\|/+)`)
    -				path = rePrefix.ReplaceAllString(path, "/")
    +				redirectPath = rePrefix.ReplaceAllString(redirectPath, "/")
     			}
    -			http.Redirect(ctx.Resp, ctx.Req, path, http.StatusFound)
    +			http.Redirect(ctx.Resp, ctx.Req, redirectPath, http.StatusFound)
     			return true
     		}
     
    
  • pkg/api/static/static_test.go+177 0 added
    @@ -0,0 +1,177 @@
    +package httpstatic
    +
    +import (
    +	"io"
    +	"net/http"
    +	"net/http/httptest"
    +	"os"
    +	"path/filepath"
    +	"testing"
    +
    +	claims "github.com/grafana/authlib/types"
    +	"github.com/grafana/grafana/pkg/models/usertoken"
    +	"github.com/grafana/grafana/pkg/services/authn"
    +	"github.com/grafana/grafana/pkg/services/authn/authntest"
    +	"github.com/grafana/grafana/pkg/services/contexthandler"
    +	"github.com/grafana/grafana/pkg/services/featuremgmt"
    +	"github.com/grafana/grafana/pkg/setting"
    +	"github.com/grafana/grafana/pkg/web"
    +	"github.com/stretchr/testify/assert"
    +	"github.com/stretchr/testify/require"
    +)
    +
    +func TestStatic(t *testing.T) {
    +	// Create a temporary directory for test files
    +	tmpDir, err := os.MkdirTemp("", "static-test")
    +	require.NoError(t, err)
    +	defer func() {
    +		err := os.RemoveAll(tmpDir)
    +		require.NoError(t, err)
    +	}()
    +
    +	// Create test files
    +	testFiles := map[string]string{
    +		"test.txt":        "Test content",
    +		"subdir/test.txt": "Subdir content",
    +	}
    +
    +	for path, content := range testFiles {
    +		fullPath := filepath.Join(tmpDir, path)
    +		err := os.MkdirAll(filepath.Dir(fullPath), 0o750)
    +		require.NoError(t, err)
    +		err = os.WriteFile(fullPath, []byte(content), 0o644)
    +		require.NoError(t, err)
    +	}
    +
    +	tests := []struct {
    +		dir              string
    +		name             string
    +		path             string
    +		options          StaticOptions
    +		expectedStatus   int
    +		expectedBody     string
    +		expectedLocation string
    +	}{
    +		{
    +			name:           "should serve existing file",
    +			path:           "/test.txt",
    +			expectedStatus: http.StatusOK,
    +			expectedBody:   "Test content",
    +			dir:            tmpDir,
    +		},
    +		{
    +			name:           "should serve file from subdirectory",
    +			path:           "/subdir/test.txt",
    +			expectedStatus: http.StatusOK,
    +			expectedBody:   "Subdir content",
    +			dir:            tmpDir,
    +		},
    +
    +		{
    +			name:             "should redirect directory without trailing slash",
    +			path:             "/subdir",
    +			expectedStatus:   http.StatusFound,
    +			expectedLocation: "/subdir/",
    +			dir:              tmpDir,
    +		},
    +		{
    +			name:           "should handle prefix",
    +			path:           "/static/test.txt",
    +			options:        StaticOptions{Prefix: "/static"},
    +			expectedStatus: http.StatusOK,
    +			expectedBody:   "Test content",
    +			dir:            tmpDir,
    +		},
    +		{
    +			name:           "should handle excluded path",
    +			path:           "/test.txt",
    +			options:        StaticOptions{Exclude: []string{"/test.txt"}},
    +			expectedStatus: http.StatusNotFound,
    +			dir:            tmpDir,
    +		},
    +		{
    +			name:           "should add custom headers",
    +			path:           "/test.txt",
    +			options:        StaticOptions{AddHeaders: func(ctx *web.Context) { ctx.Resp.Header().Set("X-Test", "test") }},
    +			expectedStatus: http.StatusOK,
    +			expectedBody:   "Test content",
    +			dir:            tmpDir,
    +		},
    +		{
    +			name:             "should clean up path before redirecting",
    +			path:             "/subdir/..%2F%5C127.0.0.1:80%2F%3F%2F..%2F..",
    +			options:          StaticOptions{Prefix: "subdir"},
    +			expectedStatus:   http.StatusFound,
    +			expectedLocation: "/",
    +			dir:              tmpDir,
    +		},
    +	}
    +
    +	for _, tt := range tests {
    +		t.Run(tt.name, func(t *testing.T) {
    +			sc := setupScenarioContext(t, "")
    +			sc.m.Use(Static(tt.dir, tt.options))
    +
    +			// Create a test request
    +			req := httptest.NewRequest("GET", tt.path, nil)
    +			w := httptest.NewRecorder()
    +
    +			// Execute the handler
    +			sc.m.ServeHTTP(w, req)
    +
    +			// Verify the response
    +			resp := w.Result()
    +			require.Equal(t, tt.expectedStatus, resp.StatusCode)
    +
    +			if tt.expectedBody != "" {
    +				body, err := io.ReadAll(resp.Body)
    +				require.NoError(t, err)
    +				assert.Equal(t, tt.expectedBody, string(body))
    +			}
    +
    +			if tt.options.AddHeaders != nil {
    +				assert.Equal(t, "test", resp.Header.Get("X-Test"))
    +			}
    +
    +			if tt.expectedLocation != "" {
    +				assert.Equal(t, tt.expectedLocation, resp.Header.Get("Location"))
    +			}
    +		})
    +	}
    +}
    +
    +type scenarioContext struct {
    +	t       *testing.T
    +	cfg     *setting.Cfg
    +	m       *web.Mux
    +	ctxHdlr *contexthandler.ContextHandler
    +}
    +
    +func getContextHandler(t *testing.T, cfg *setting.Cfg) *contexthandler.ContextHandler {
    +	t.Helper()
    +
    +	if cfg == nil {
    +		cfg = setting.NewCfg()
    +	}
    +
    +	return contexthandler.ProvideService(
    +		cfg,
    +		&authntest.FakeService{ExpectedIdentity: &authn.Identity{ID: "0", Type: claims.TypeAnonymous, SessionToken: &usertoken.UserToken{}}},
    +		featuremgmt.WithFeatures(),
    +	)
    +}
    +
    +func setupScenarioContext(t *testing.T, url string) *scenarioContext {
    +	cfg := setting.NewCfg()
    +	ctxHdlr := getContextHandler(t, cfg)
    +	sc := &scenarioContext{
    +		t:       t,
    +		cfg:     cfg,
    +		ctxHdlr: ctxHdlr,
    +	}
    +
    +	sc.m = web.New()
    +	sc.m.Use(ctxHdlr.Middleware)
    +
    +	return sc
    +}
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.