V9.1.3.1 security patch (#492) (#55515)

* apply security fix for admin only folder migration (#482)

* Data source: prevent from using auth proxy header as custom data source header (#474)

* applying changes from 446/fix-user-escalation-through-auth-proxy

* linting

* only validate custom headers if auth proxy is enabled

* import ordering

* add links to CVE

* clean up

* remove typo

Co-authored-by: Karl Persson <kalle.persson@grafana.com>
(cherry picked from commit 0100a6aa9645313b25a79a17d947cbf936cc4e76)
(cherry picked from commit c048378ad5)
This commit is contained in:
Ieva
2022-09-20 14:57:15 -04:00
committed by GitHub
parent c8f4918e48
commit d9a39c1553
7 changed files with 288 additions and 4 deletions
+32
View File
@@ -8,17 +8,20 @@ import (
"net/http"
"sort"
"strconv"
"strings"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/api/datasource"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins/adapters"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/datasources/permissions"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
"github.com/grafana/grafana/pkg/util/proxyutil"
"github.com/grafana/grafana/pkg/web"
@@ -325,6 +328,26 @@ func validateURL(cmdType string, url string) response.Response {
return nil
}
// validateJSONData prevents the user from adding a custom header with name that matches the auth proxy header name.
// This is done to prevent data source proxy from being used to circumvent auth proxy.
// For more context take a look at CVE-2022-35957
func validateJSONData(jsonData *simplejson.Json, cfg *setting.Cfg) error {
if jsonData == nil || !cfg.AuthProxyEnabled {
return nil
}
for key, value := range jsonData.MustMap() {
if strings.HasPrefix(key, "httpHeaderName") {
header := fmt.Sprint(value)
if http.CanonicalHeaderKey(header) == http.CanonicalHeaderKey(cfg.AuthProxyHeaderName) {
datasourcesLogger.Error("Forbidden to add a data source header with a name equal to auth proxy header name", "headerName", key)
return errors.New("validation error, invalid header name specified")
}
}
}
return nil
}
// swagger:route POST /datasources datasources addDataSource
//
// Create a data source.
@@ -356,6 +379,9 @@ func (hs *HTTPServer) AddDataSource(c *models.ReqContext) response.Response {
return resp
}
}
if err := validateJSONData(cmd.JsonData, hs.Cfg); err != nil {
return response.Error(http.StatusBadRequest, "Failed to add datasource", err)
}
if err := hs.DataSourcesService.AddDataSource(c.Req.Context(), &cmd); err != nil {
if errors.Is(err, datasources.ErrDataSourceNameExists) || errors.Is(err, datasources.ErrDataSourceUidExists) {
@@ -413,6 +439,9 @@ func (hs *HTTPServer) UpdateDataSourceByID(c *models.ReqContext) response.Respon
if resp := validateURL(cmd.Type, cmd.Url); resp != nil {
return resp
}
if err := validateJSONData(cmd.JsonData, hs.Cfg); err != nil {
return response.Error(http.StatusBadRequest, "Failed to update datasource", err)
}
ds, err := hs.getRawDataSourceById(c.Req.Context(), cmd.Id, cmd.OrgId)
if err != nil {
@@ -450,6 +479,9 @@ func (hs *HTTPServer) UpdateDataSourceByUID(c *models.ReqContext) response.Respo
if resp := validateURL(cmd.Type, cmd.Url); resp != nil {
return resp
}
if err := validateJSONData(cmd.JsonData, hs.Cfg); err != nil {
return response.Error(http.StatusBadRequest, "Failed to update datasource", err)
}
ds, err := hs.getRawDataSourceByUID(c.Req.Context(), web.Params(c.Req)[":uid"], c.OrgId)
if err != nil {
+65
View File
@@ -15,6 +15,7 @@ import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/models"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/datasources"
@@ -82,6 +83,7 @@ func TestAddDataSource_InvalidURL(t *testing.T) {
sc := setupScenarioContext(t, "/api/datasources")
hs := &HTTPServer{
DataSourcesService: &dataSourcesServiceMock{},
Cfg: setting.NewCfg(),
}
sc.m.Post(sc.url, routing.Wrap(func(c *models.ReqContext) response.Response {
@@ -108,6 +110,7 @@ func TestAddDataSource_URLWithoutProtocol(t *testing.T) {
DataSourcesService: &dataSourcesServiceMock{
expectedDatasource: &datasources.DataSource{},
},
Cfg: setting.NewCfg(),
}
sc := setupScenarioContext(t, "/api/datasources")
@@ -127,10 +130,42 @@ func TestAddDataSource_URLWithoutProtocol(t *testing.T) {
assert.Equal(t, 200, sc.resp.Code)
}
// Using a custom header whose name matches the name specified for auth proxy header should fail
func TestAddDataSource_InvalidJSONData(t *testing.T) {
hs := &HTTPServer{
DataSourcesService: &dataSourcesServiceMock{},
Cfg: setting.NewCfg(),
}
sc := setupScenarioContext(t, "/api/datasources")
hs.Cfg = setting.NewCfg()
hs.Cfg.AuthProxyEnabled = true
hs.Cfg.AuthProxyHeaderName = "X-AUTH-PROXY-HEADER"
jsonData := simplejson.New()
jsonData.Set("httpHeaderName1", hs.Cfg.AuthProxyHeaderName)
sc.m.Post(sc.url, routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(datasources.AddDataSourceCommand{
Name: "Test",
Url: "localhost:5432",
Access: "direct",
Type: "test",
JsonData: jsonData,
})
return hs.AddDataSource(c)
}))
sc.fakeReqWithParams("POST", sc.url, map[string]string{}).exec()
assert.Equal(t, 400, sc.resp.Code)
}
// Updating data sources with invalid URLs should lead to an error.
func TestUpdateDataSource_InvalidURL(t *testing.T) {
hs := &HTTPServer{
DataSourcesService: &dataSourcesServiceMock{},
Cfg: setting.NewCfg(),
}
sc := setupScenarioContext(t, "/api/datasources/1234")
@@ -149,6 +184,35 @@ func TestUpdateDataSource_InvalidURL(t *testing.T) {
assert.Equal(t, 400, sc.resp.Code)
}
// Using a custom header whose name matches the name specified for auth proxy header should fail
func TestUpdateDataSource_InvalidJSONData(t *testing.T) {
hs := &HTTPServer{
DataSourcesService: &dataSourcesServiceMock{},
Cfg: setting.NewCfg(),
}
sc := setupScenarioContext(t, "/api/datasources/1234")
hs.Cfg.AuthProxyEnabled = true
hs.Cfg.AuthProxyHeaderName = "X-AUTH-PROXY-HEADER"
jsonData := simplejson.New()
jsonData.Set("httpHeaderName1", hs.Cfg.AuthProxyHeaderName)
sc.m.Put(sc.url, routing.Wrap(func(c *models.ReqContext) response.Response {
c.Req.Body = mockRequestBody(datasources.AddDataSourceCommand{
Name: "Test",
Url: "localhost:5432",
Access: "direct",
Type: "test",
JsonData: jsonData,
})
return hs.AddDataSource(c)
}))
sc.fakeReqWithParams("PUT", sc.url, map[string]string{}).exec()
assert.Equal(t, 400, sc.resp.Code)
}
// Updating data sources with URLs not specifying protocol should work.
func TestUpdateDataSource_URLWithoutProtocol(t *testing.T) {
const name = "Test"
@@ -158,6 +222,7 @@ func TestUpdateDataSource_URLWithoutProtocol(t *testing.T) {
DataSourcesService: &dataSourcesServiceMock{
expectedDatasource: &datasources.DataSource{},
},
Cfg: setting.NewCfg(),
}
sc := setupScenarioContext(t, "/api/datasources/1234")