Skip to content
Permalink
Browse files Browse the repository at this point in the history
[v7.5.x] Fix for CVE-2022-21702 (#226)
Fix for CVE-2022-21702
  • Loading branch information
marefr committed Jan 21, 2022
1 parent 7b6cadf commit 2772686
Show file tree
Hide file tree
Showing 7 changed files with 79 additions and 2 deletions.
1 change: 1 addition & 0 deletions pkg/api/pluginproxy/ds_proxy.go
Expand Up @@ -49,6 +49,7 @@ func (t *handleResponseTransport) RoundTrip(req *http.Request) (*http.Response,
return nil, err
}
res.Header.Del("Set-Cookie")
proxyutil.SetProxyResponseHeaders(res.Header)
return res, nil
}

Expand Down
12 changes: 12 additions & 0 deletions pkg/api/pluginproxy/ds_proxy_test.go
Expand Up @@ -605,6 +605,18 @@ func TestDataSourceProxy_requestHandling(t *testing.T) {
assert.Equal(t, "important_cookie=important_value", proxy.ctx.Resp.Header().Get("Set-Cookie"))
})

t.Run("When response should set Content-Security-Policy header", func(t *testing.T) {
ctx, ds := setUp(t)
dsPlugin := &plugins.DataSourcePlugin{}
proxy, err := NewDataSourceProxy(ds, dsPlugin, ctx, "/render", &setting.Cfg{})
require.NoError(t, err)

proxy.HandleRequest()

require.NoError(t, writeErr)
assert.Equal(t, "sandbox", proxy.ctx.Resp.Header().Get("Content-Security-Policy"))
})

t.Run("Data source returns status code 401", func(t *testing.T) {
ctx, ds := setUp(t, setUpCfg{
writeCb: func(w http.ResponseWriter, r *http.Request) {
Expand Down
8 changes: 7 additions & 1 deletion pkg/api/pluginproxy/pluginproxy.go
Expand Up @@ -71,5 +71,11 @@ func NewApiPluginProxy(ctx *models.ReqContext, proxyPath string, route *plugins.
}
}

return &httputil.ReverseProxy{Director: director}
return &httputil.ReverseProxy{Director: director, ModifyResponse: modifyResponse}
}

func modifyResponse(resp *http.Response) error {
proxyutil.SetProxyResponseHeaders(resp.Header)

return nil
}
44 changes: 44 additions & 0 deletions pkg/api/pluginproxy/pluginproxy_test.go
Expand Up @@ -2,6 +2,7 @@ package pluginproxy

import (
"net/http"
"net/http/httptest"
"testing"

"github.com/grafana/grafana/pkg/bus"
Expand All @@ -11,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/util"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
macaron "gopkg.in/macaron.v1"
)

func TestPluginProxy(t *testing.T) {
Expand Down Expand Up @@ -148,6 +150,48 @@ func TestPluginProxy(t *testing.T) {
)
assert.Equal(t, "https://example.com", req.URL.String())
})

t.Run("When proxying a request should set expected response headers", func(t *testing.T) {
requestHandled := false
backendServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
_, _ = w.Write([]byte("I am the backend"))
requestHandled = true
}))

responseRecorder := &closeNotifierResponseRecorder{
ResponseRecorder: httptest.NewRecorder(),
}
responseWriter := macaron.NewResponseWriter("GET", responseRecorder)

t.Cleanup(responseRecorder.Close)
t.Cleanup(backendServer.Close)

route := &plugins.AppPluginRoute{
Path: "/",
URL: backendServer.URL,
}

ctx := &models.ReqContext{
SignedInUser: &models.SignedInUser{},
Context: &macaron.Context{
Req: macaron.Request{
Request: httptest.NewRequest("GET", "/", nil),
},
Resp: responseWriter,
},
}
proxy := NewApiPluginProxy(ctx, "", route, "", &setting.Cfg{})
proxy.ServeHTTP(ctx.Resp, ctx.Req.Request)

for {
if requestHandled {
break
}
}

require.Equal(t, "sandbox", ctx.Resp.Header().Get("Content-Security-Policy"))
})
}

// getPluginProxiedRequest is a helper for easier setup of tests based on global config and ReqContext.
Expand Down
1 change: 1 addition & 0 deletions pkg/plugins/backendplugin/manager.go
Expand Up @@ -406,6 +406,7 @@ func flushStream(plugin Plugin, stream CallResourceClientResponseStream, w http.
}
}

proxyutil.SetProxyResponseHeaders(w.Header())
w.WriteHeader(resp.Status)
}

Expand Down
9 changes: 8 additions & 1 deletion pkg/plugins/backendplugin/manager_test.go
Expand Up @@ -177,7 +177,8 @@ func TestManager(t *testing.T) {
t.Run("Call resource should return expected response", func(t *testing.T) {
ctx.plugin.CallResourceHandlerFunc = backend.CallResourceHandlerFunc(func(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
return sender.Send(&backend.CallResourceResponse{
Status: http.StatusOK,
Status: http.StatusOK,
Headers: map[string][]string{},
})
})

Expand All @@ -186,7 +187,13 @@ func TestManager(t *testing.T) {
w := httptest.NewRecorder()
err = ctx.manager.callResourceInternal(w, req, backend.PluginContext{PluginID: testPluginID})
require.NoError(t, err)
for {
if w.Flushed {
break
}
}
require.Equal(t, http.StatusOK, w.Code)
require.Equal(t, "sandbox", w.Header().Get("Content-Security-Policy"))
})
})
})
Expand Down
6 changes: 6 additions & 0 deletions pkg/util/proxyutil/proxyutil.go
Expand Up @@ -42,3 +42,9 @@ func ClearCookieHeader(req *http.Request, keepCookiesNames []string) {
req.AddCookie(c)
}
}

// SetProxyResponseHeaders sets proxy response headers.
// Sets Content-Security-Policy: sandbox
func SetProxyResponseHeaders(header http.Header) {
header.Set("Content-Security-Policy", "sandbox")
}

0 comments on commit 2772686

Please sign in to comment.