net/http/httputil: reencode queries with many parameters in proxy

When ReverseProxy forwards a request containing more than
urlmaxqueryparams (GODEBUG) query parameters, reencode the
outbound query parameters.

Avoids potential smuggling of query parameters, where the
sender sends many query parameters, the user's Rewrite hook
fails to observe those parameters due to the limit being
exceeded, and the request is forwarded with the full set
of parameters.

Fixes #78948
Fixes CVE-2026-39825

Change-Id: I691be7899c4b6208bf61f6b78dacfdf56a6a6964
Reviewed-on: https://go-review.googlesource.com/c/go/+/770541
Reviewed-by: Nicholas Husin <nsh@golang.org>
Reviewed-by: Nicholas Husin <husin@google.com>
Auto-Submit: Damien Neil <dneil@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Damien Neil 2026-04-24 14:10:47 -07:00 committed by Gopher Robot
parent 1f5c165a81
commit 6795bb3317
3 changed files with 21 additions and 0 deletions

View file

@ -10,6 +10,7 @@ import (
"context"
"errors"
"fmt"
"internal/godebug"
"io"
"log"
"mime"
@ -922,11 +923,24 @@ func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
errc <- errCopyDone
}
var urlmaxqueryparams = godebug.New("urlmaxqueryparams")
// Keep this in sync with net/url.
const defaultMaxParams = 10000
func cleanQueryParams(s string) string {
reencode := func(s string) string {
v, _ := url.ParseQuery(s)
return v.Encode()
}
if urlmaxqueryparams.Value() != "" {
// Always reencode when a non-default urlmaxqueryparams is set.
return reencode(s)
}
if numParams := strings.Count(s, "&") + 1; numParams > defaultMaxParams {
// Too many query parameters.
return reencode(s)
}
for i := 0; i < len(s); {
switch s[i] {
case ';':

View file

@ -2087,6 +2087,12 @@ func testReverseProxyQueryParameterSmuggling(t *testing.T, wantCleanQuery bool,
}, {
rawQuery: "a=1&a=%zz&b=3",
cleanQuery: "a=1&b=3",
}, {
rawQuery: "a=%zz",
cleanQuery: "",
}, {
rawQuery: strings.Repeat("a=1&", 10000) + "a=1",
cleanQuery: "",
}} {
res, err := frontend.Client().Get(frontend.URL + "?" + test.rawQuery)
if err != nil {

View file

@ -956,6 +956,7 @@ func ParseQuery(query string) (Values, error) {
var urlmaxqueryparams = godebug.New("urlmaxqueryparams")
// Keep this in sync with net/http/httputil.
const defaultMaxParams = 10000
func urlParamsWithinMax(params int) bool {