Data source and plugin proxy endpoints could leak the authentication cookie to some destination plugins
Description
Grafana is an open source observability and data visualization platform. Starting with version 5.0.0-beta1 and prior to versions 8.5.14 and 9.1.8, Grafana could leak the authentication cookie of users to plugins. The vulnerability impacts data source and plugin proxy endpoints under certain conditions. The destination plugin could receive a user's Grafana authentication cookie. Versions 9.1.8 and 8.5.14 contain a patch for this issue. There are no known workarounds.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
github.com/grafana/grafanaGo | >= 5.0.0-beta1, < 8.5.14 | 8.5.14 |
github.com/grafana/grafanaGo | >= 9.0.0, < 9.1.8 | 9.1.8 |
Affected products
1Patches
2c658816f5229Security: Fix do not forward login cookie in outgoing requests
6 files changed · +37 −11
pkg/api/metrics_test.go+2 −1 modified@@ -11,6 +11,7 @@ import ( "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/sqlstore/mockstore" + "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web/webtest" "golang.org/x/oauth2" @@ -498,7 +499,7 @@ func TestAPIEndpoint_Metrics_ParseDashboardQueryParams(t *testing.T) { // `/ds/query` endpoint test func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) { qds := query.ProvideService( - nil, + setting.NewCfg(), nil, nil, &fakePluginRequestValidator{},
pkg/api/pluginproxy/ds_proxy.go+1 −1 modified@@ -214,7 +214,7 @@ func (proxy *DataSourceProxy) director(req *http.Request) { } } - proxyutil.ClearCookieHeader(req, keepCookieNames) + proxyutil.ClearCookieHeader(req, keepCookieNames, []string{proxy.cfg.LoginCookieName}) req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion)) jsonData := make(map[string]interface{})
pkg/api/plugins.go+1 −1 modified@@ -555,7 +555,7 @@ func (hs *HTTPServer) makePluginResourceRequest(w http.ResponseWriter, req *http } } - proxyutil.ClearCookieHeader(req, keepCookieModel.KeepCookies) + proxyutil.ClearCookieHeader(req, keepCookieModel.KeepCookies, []string{hs.Cfg.LoginCookieName}) proxyutil.PrepareProxyRequest(req) body, err := ioutil.ReadAll(req.Body)
pkg/services/query/query_test.go+1 −1 modified@@ -5,9 +5,9 @@ import ( "net/http" "testing" + "github.com/grafana/grafana-plugin-sdk-go/backend" "golang.org/x/oauth2" - "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/components/simplejson" "github.com/grafana/grafana/pkg/models"
pkg/util/proxyutil/proxyutil.go+18 −5 modified@@ -3,6 +3,7 @@ package proxyutil import ( "net" "net/http" + "sort" ) // PrepareProxyRequest prepares a request for being proxied. @@ -26,19 +27,31 @@ func PrepareProxyRequest(req *http.Request) { } } -// ClearCookieHeader clear cookie header, except for cookies specified to be kept. -func ClearCookieHeader(req *http.Request, keepCookiesNames []string) { - var keepCookies []*http.Cookie +// ClearCookieHeader clear cookie header, except for cookies specified to be kept (keepCookiesNames) if not in skipCookiesNames. +func ClearCookieHeader(req *http.Request, keepCookiesNames []string, skipCookiesNames []string) { + keepCookies := map[string]*http.Cookie{} for _, c := range req.Cookies() { for _, v := range keepCookiesNames { if c.Name == v { - keepCookies = append(keepCookies, c) + keepCookies[c.Name] = c } } } + for _, v := range skipCookiesNames { + delete(keepCookies, v) + } + req.Header.Del("Cookie") - for _, c := range keepCookies { + + sortedCookies := []string{} + for name := range keepCookies { + sortedCookies = append(sortedCookies, name) + } + sort.Strings(sortedCookies) + + for _, name := range sortedCookies { + c := keepCookies[name] req.AddCookie(c) } }
pkg/util/proxyutil/proxyutil_test.go+14 −2 modified@@ -49,7 +49,7 @@ func TestClearCookieHeader(t *testing.T) { require.NoError(t, err) req.AddCookie(&http.Cookie{Name: "cookie"}) - ClearCookieHeader(req, nil) + ClearCookieHeader(req, nil, nil) require.NotContains(t, req.Header, "Cookie") }) @@ -60,8 +60,20 @@ func TestClearCookieHeader(t *testing.T) { req.AddCookie(&http.Cookie{Name: "cookie2"}) req.AddCookie(&http.Cookie{Name: "cookie3"}) - ClearCookieHeader(req, []string{"cookie1", "cookie3"}) + ClearCookieHeader(req, []string{"cookie1", "cookie3"}, nil) require.Contains(t, req.Header, "Cookie") require.Equal(t, "cookie1=; cookie3=", req.Header.Get("Cookie")) }) + + t.Run("Clear cookie header with cookies to keep and skip should clear Cookie header and keep cookies", func(t *testing.T) { + req, err := http.NewRequest(http.MethodGet, "/", nil) + require.NoError(t, err) + req.AddCookie(&http.Cookie{Name: "cookie1"}) + req.AddCookie(&http.Cookie{Name: "cookie2"}) + req.AddCookie(&http.Cookie{Name: "cookie3"}) + + ClearCookieHeader(req, []string{"cookie1", "cookie3"}, []string{"cookie3"}) + require.Contains(t, req.Header, "Cookie") + require.Equal(t, "cookie1=", req.Header.Get("Cookie")) + }) }
b571acc1dc13Security: Fix do not forward login cookie in outgoing requests
10 files changed · +52 −27
pkg/api/datasources.go+1 −1 modified@@ -826,7 +826,7 @@ func (hs *HTTPServer) checkDatasourceHealth(c *models.ReqContext, ds *datasource } } - proxyutil.ClearCookieHeader(c.Req, ds.AllowedCookies()) + proxyutil.ClearCookieHeader(c.Req, ds.AllowedCookies(), []string{hs.Cfg.LoginCookieName}) if cookieStr := c.Req.Header.Get("Cookie"); cookieStr != "" { req.Headers["Cookie"] = cookieStr }
pkg/api/metrics_test.go+3 −2 modified@@ -15,6 +15,7 @@ import ( "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/quota/quotatest" + "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/web/webtest" "golang.org/x/oauth2" @@ -68,7 +69,7 @@ func (ts *fakeOAuthTokenService) IsOAuthPassThruEnabled(*datasources.DataSource) // `/ds/query` endpoint test func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) { qds := query.ProvideService( - nil, + setting.NewCfg(), nil, nil, &fakePluginRequestValidator{}, @@ -117,7 +118,7 @@ func TestAPIEndpoint_Metrics_QueryMetricsV2(t *testing.T) { func TestAPIEndpoint_Metrics_PluginDecryptionFailure(t *testing.T) { qds := query.ProvideService( - nil, + setting.NewCfg(), nil, nil, &fakePluginRequestValidator{},
pkg/api/pluginproxy/ds_proxy.go+1 −1 modified@@ -223,7 +223,7 @@ func (proxy *DataSourceProxy) director(req *http.Request) { applyUserHeader(proxy.cfg.SendUserHeader, req, proxy.ctx.SignedInUser) - proxyutil.ClearCookieHeader(req, proxy.ds.AllowedCookies()) + proxyutil.ClearCookieHeader(req, proxy.ds.AllowedCookies(), []string{proxy.cfg.LoginCookieName}) req.Header.Set("User-Agent", fmt.Sprintf("Grafana/%s", setting.BuildVersion)) jsonData := make(map[string]interface{})
pkg/api/plugin_resource.go+1 −10 modified@@ -15,7 +15,6 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins/backendplugin" - "github.com/grafana/grafana/pkg/services/contexthandler" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/util/proxyutil" "github.com/grafana/grafana/pkg/web" @@ -119,15 +118,7 @@ func (hs *HTTPServer) makePluginResourceRequest(w http.ResponseWriter, req *http hs.log.Warn("failed to unpack JSONData in datasource instance settings", "err", err) } } - - list := contexthandler.AuthHTTPHeaderListFromContext(req.Context()) - if list != nil { - for _, name := range list.Items { - req.Header.Del(name) - } - } - - proxyutil.ClearCookieHeader(req, keepCookieModel.KeepCookies) + proxyutil.ClearCookieHeader(req, keepCookieModel.KeepCookies, []string{hs.Cfg.LoginCookieName}) proxyutil.PrepareProxyRequest(req) body, err := ioutil.ReadAll(req.Body)
pkg/infra/httpclient/httpclientprovider/forwarded_cookie_middleware_test.go+8 −1 modified@@ -13,6 +13,7 @@ func TestForwardedCookiesMiddleware(t *testing.T) { tcs := []struct { desc string allowedCookies []string + disallowedCookies []string expectedCookieHeader string }{ { @@ -30,6 +31,12 @@ func TestForwardedCookiesMiddleware(t *testing.T) { allowedCookies: []string{"c1", "c3"}, expectedCookieHeader: "c1=1; c3=3", }, + { + desc: "When provided with allowed and not allowed cookies should populate Cookie header", + allowedCookies: []string{"c1", "c3"}, + disallowedCookies: []string{"c1"}, + expectedCookieHeader: "c3=3", + }, } for _, tc := range tcs { @@ -41,7 +48,7 @@ func TestForwardedCookiesMiddleware(t *testing.T) { {Name: "c2", Value: "2"}, {Name: "c3", Value: "3"}, } - mw := httpclientprovider.ForwardedCookiesMiddleware(forwarded, tc.allowedCookies) + mw := httpclientprovider.ForwardedCookiesMiddleware(forwarded, tc.allowedCookies, tc.disallowedCookies) opts := httpclient.Options{} rt := mw.CreateMiddleware(opts, finalRoundTripper) require.NotNil(t, rt)
pkg/infra/httpclient/httpclientprovider/forwarded_cookies_middleware.go+2 −2 modified@@ -11,13 +11,13 @@ const ForwardedCookiesMiddlewareName = "forwarded-cookies" // ForwardedCookiesMiddleware middleware that sets Cookie header on the // outgoing request, if forwarded cookies configured/provided. -func ForwardedCookiesMiddleware(forwardedCookies []*http.Cookie, allowedCookies []string) httpclient.Middleware { +func ForwardedCookiesMiddleware(forwardedCookies []*http.Cookie, allowedCookies []string, disallowedCookies []string) httpclient.Middleware { return httpclient.NamedMiddlewareFunc(ForwardedCookiesMiddlewareName, func(opts httpclient.Options, next http.RoundTripper) http.RoundTripper { return httpclient.RoundTripperFunc(func(req *http.Request) (*http.Response, error) { for _, cookie := range forwardedCookies { req.AddCookie(cookie) } - proxyutil.ClearCookieHeader(req, allowedCookies) + proxyutil.ClearCookieHeader(req, allowedCookies, disallowedCookies) return next.RoundTrip(req) }) })
pkg/services/query/query.go+2 −2 modified@@ -162,7 +162,7 @@ func (s *Service) handleQueryData(ctx context.Context, user *models.SignedInUser middlewares := []httpclient.Middleware{} if parsedReq.httpRequest != nil { middlewares = append(middlewares, - httpclientprovider.ForwardedCookiesMiddleware(parsedReq.httpRequest.Cookies(), ds.AllowedCookies()), + httpclientprovider.ForwardedCookiesMiddleware(parsedReq.httpRequest.Cookies(), ds.AllowedCookies(), []string{s.cfg.LoginCookieName}), ) } @@ -179,7 +179,7 @@ func (s *Service) handleQueryData(ctx context.Context, user *models.SignedInUser } if parsedReq.httpRequest != nil { - proxyutil.ClearCookieHeader(parsedReq.httpRequest, ds.AllowedCookies()) + proxyutil.ClearCookieHeader(parsedReq.httpRequest, ds.AllowedCookies(), []string{s.cfg.LoginCookieName}) if cookieStr := parsedReq.httpRequest.Header.Get("Cookie"); cookieStr != "" { req.Headers["Cookie"] = cookieStr }
pkg/services/query/query_test.go+2 −1 modified@@ -6,6 +6,7 @@ import ( "testing" "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/setting" "github.com/stretchr/testify/require" "golang.org/x/oauth2" @@ -99,7 +100,7 @@ func setup(t *testing.T) *testContext { dataSourceCache: dc, oauthTokenService: tc, pluginRequestValidator: rv, - queryService: query.ProvideService(nil, dc, nil, rv, ds, pc, tc), + queryService: query.ProvideService(setting.NewCfg(), dc, nil, rv, ds, pc, tc), } }
pkg/util/proxyutil/proxyutil.go+18 −5 modified@@ -3,6 +3,7 @@ package proxyutil import ( "net" "net/http" + "sort" ) // PrepareProxyRequest prepares a request for being proxied. @@ -26,19 +27,31 @@ func PrepareProxyRequest(req *http.Request) { } } -// ClearCookieHeader clear cookie header, except for cookies specified to be kept. -func ClearCookieHeader(req *http.Request, keepCookiesNames []string) { - var keepCookies []*http.Cookie +// ClearCookieHeader clear cookie header, except for cookies specified to be kept (keepCookiesNames) if not in skipCookiesNames. +func ClearCookieHeader(req *http.Request, keepCookiesNames []string, skipCookiesNames []string) { + keepCookies := map[string]*http.Cookie{} for _, c := range req.Cookies() { for _, v := range keepCookiesNames { if c.Name == v { - keepCookies = append(keepCookies, c) + keepCookies[c.Name] = c } } } + for _, v := range skipCookiesNames { + delete(keepCookies, v) + } + req.Header.Del("Cookie") - for _, c := range keepCookies { + + sortedCookies := []string{} + for name := range keepCookies { + sortedCookies = append(sortedCookies, name) + } + sort.Strings(sortedCookies) + + for _, name := range sortedCookies { + c := keepCookies[name] req.AddCookie(c) } }
pkg/util/proxyutil/proxyutil_test.go+14 −2 modified@@ -49,7 +49,7 @@ func TestClearCookieHeader(t *testing.T) { require.NoError(t, err) req.AddCookie(&http.Cookie{Name: "cookie"}) - ClearCookieHeader(req, nil) + ClearCookieHeader(req, nil, nil) require.NotContains(t, req.Header, "Cookie") }) @@ -60,8 +60,20 @@ func TestClearCookieHeader(t *testing.T) { req.AddCookie(&http.Cookie{Name: "cookie2"}) req.AddCookie(&http.Cookie{Name: "cookie3"}) - ClearCookieHeader(req, []string{"cookie1", "cookie3"}) + ClearCookieHeader(req, []string{"cookie1", "cookie3"}, nil) require.Contains(t, req.Header, "Cookie") require.Equal(t, "cookie1=; cookie3=", req.Header.Get("Cookie")) }) + + t.Run("Clear cookie header with cookies to keep and skip should clear Cookie header and keep cookies", func(t *testing.T) { + req, err := http.NewRequest(http.MethodGet, "/", nil) + require.NoError(t, err) + req.AddCookie(&http.Cookie{Name: "cookie1"}) + req.AddCookie(&http.Cookie{Name: "cookie2"}) + req.AddCookie(&http.Cookie{Name: "cookie3"}) + + ClearCookieHeader(req, []string{"cookie1", "cookie3"}, []string{"cookie3"}) + require.Contains(t, req.Header, "Cookie") + require.Equal(t, "cookie1=", req.Header.Get("Cookie")) + }) }
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
6- github.com/advisories/GHSA-x744-mm8v-vpgrghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2022-39201ghsaADVISORY
- github.com/grafana/grafana/commit/b571acc1dc130a33f24742c1f93b93216da6cf57ghsaWEB
- github.com/grafana/grafana/commit/c658816f5229d17f877579250c07799d3bbaebc9ghsaWEB
- github.com/grafana/grafana/releases/tag/v9.1.8ghsaWEB
- github.com/grafana/grafana/security/advisories/GHSA-x744-mm8v-vpgrghsaWEB
News mentions
0No linked articles in our index yet.