389 lines
17 KiB
Diff
389 lines
17 KiB
Diff
From 6b605505047416bbbf513bba1540220a8897f3f6 Mon Sep 17 00:00:00 2001
|
|
From: Damien Neil <dneil@google.com>
|
|
Date: Fri, 22 Nov 2024 12:34:11 -0800
|
|
Subject: [PATCH] [release-branch.go1.24] net/http: persist header stripping across repeated redirects
|
|
|
|
When an HTTP redirect changes the host of a request, we drop
|
|
sensitive headers such as Authorization from the redirected request.
|
|
Fix a bug where a chain of redirects could result in sensitive
|
|
headers being sent to the wrong host:
|
|
|
|
1. request to a.tld with Authorization header
|
|
2. a.tld redirects to b.tld
|
|
3. request to b.tld with no Authorization header
|
|
4. b.tld redirects to b.tld
|
|
3. request to b.tld with Authorization header restored
|
|
|
|
Thanks to Kyle Seely for reporting this issue.
|
|
|
|
For #70530
|
|
Fixes #71212
|
|
Fixes CVE-2024-45336
|
|
|
|
Change-Id: Ia58a2e10d33d6b0cc7220935e771450e5c34de72
|
|
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/1641
|
|
Reviewed-by: Roland Shoemaker <bracewell@google.com>
|
|
Reviewed-by: Tatiana Bradley <tatianabradley@google.com>
|
|
Commit-Queue: Roland Shoemaker <bracewell@google.com>
|
|
(cherry picked from commit 2889169b87a61f1218a02994feb80fd3d8bfa87c)
|
|
Reviewed-on: https://go-internal-review.googlesource.com/c/go/+/1766
|
|
Reviewed-on: https://go-review.googlesource.com/c/go/+/643100
|
|
Auto-Submit: Michael Knyszek <mknyszek@google.com>
|
|
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
|
|
Reviewed-by: Michael Pratt <mpratt@google.com>
|
|
---
|
|
|
|
diff --git a/src/net/http/client.go b/src/net/http/client.go
|
|
index fda7815..9231f63 100644
|
|
--- a/src/net/http/client.go
|
|
+++ b/src/net/http/client.go
|
|
@@ -615,8 +615,9 @@
|
|
reqBodyClosed = false // have we closed the current req.Body?
|
|
|
|
// Redirect behavior:
|
|
- redirectMethod string
|
|
- includeBody bool
|
|
+ redirectMethod string
|
|
+ includeBody = true
|
|
+ stripSensitiveHeaders = false
|
|
)
|
|
uerr := func(err error) error {
|
|
// the body may have been closed already by c.send()
|
|
@@ -681,7 +682,12 @@
|
|
// in case the user set Referer on their first request.
|
|
// If they really want to override, they can do it in
|
|
// their CheckRedirect func.
|
|
- copyHeaders(req)
|
|
+ if !stripSensitiveHeaders && reqs[0].URL.Host != req.URL.Host {
|
|
+ if !shouldCopyHeaderOnRedirect(reqs[0].URL, req.URL) {
|
|
+ stripSensitiveHeaders = true
|
|
+ }
|
|
+ }
|
|
+ copyHeaders(req, stripSensitiveHeaders)
|
|
|
|
// Add the Referer header from the most recent
|
|
// request URL to the new one, if it's not https->http:
|
|
@@ -747,7 +753,7 @@
|
|
// makeHeadersCopier makes a function that copies headers from the
|
|
// initial Request, ireq. For every redirect, this function must be called
|
|
// so that it can copy headers into the upcoming Request.
|
|
-func (c *Client) makeHeadersCopier(ireq *Request) func(*Request) {
|
|
+func (c *Client) makeHeadersCopier(ireq *Request) func(req *Request, stripSensitiveHeaders bool) {
|
|
// The headers to copy are from the very initial request.
|
|
// We use a closured callback to keep a reference to these original headers.
|
|
var (
|
|
@@ -761,8 +767,7 @@
|
|
}
|
|
}
|
|
|
|
- preq := ireq // The previous request
|
|
- return func(req *Request) {
|
|
+ return func(req *Request, stripSensitiveHeaders bool) {
|
|
// If Jar is present and there was some initial cookies provided
|
|
// via the request header, then we may need to alter the initial
|
|
// cookies as we follow redirects since each redirect may end up
|
|
@@ -799,12 +804,15 @@
|
|
// Copy the initial request's Header values
|
|
// (at least the safe ones).
|
|
for k, vv := range ireqhdr {
|
|
- if shouldCopyHeaderOnRedirect(k, preq.URL, req.URL) {
|
|
+ sensitive := false
|
|
+ switch CanonicalHeaderKey(k) {
|
|
+ case "Authorization", "Www-Authenticate", "Cookie", "Cookie2":
|
|
+ sensitive = true
|
|
+ }
|
|
+ if !(sensitive && stripSensitiveHeaders) {
|
|
req.Header[k] = vv
|
|
}
|
|
}
|
|
-
|
|
- preq = req // Update previous Request with the current request
|
|
}
|
|
}
|
|
|
|
@@ -984,28 +984,23 @@ func (b *cancelTimerBody) Close() error {
|
|
return err
|
|
}
|
|
|
|
-func shouldCopyHeaderOnRedirect(headerKey string, initial, dest *url.URL) bool {
|
|
- switch CanonicalHeaderKey(headerKey) {
|
|
- case "Authorization", "Www-Authenticate", "Cookie", "Cookie2":
|
|
- // Permit sending auth/cookie headers from "foo.com"
|
|
- // to "sub.foo.com".
|
|
-
|
|
- // Note that we don't send all cookies to subdomains
|
|
- // automatically. This function is only used for
|
|
- // Cookies set explicitly on the initial outgoing
|
|
- // client request. Cookies automatically added via the
|
|
- // CookieJar mechanism continue to follow each
|
|
- // cookie's scope as set by Set-Cookie. But for
|
|
- // outgoing requests with the Cookie header set
|
|
- // directly, we don't know their scope, so we assume
|
|
- // it's for *.domain.com.
|
|
-
|
|
- ihost := idnaASCIIFromURL(initial)
|
|
- dhost := idnaASCIIFromURL(dest)
|
|
- return isDomainOrSubdomain(dhost, ihost)
|
|
- }
|
|
- // All other headers are copied:
|
|
- return true
|
|
+func shouldCopyHeaderOnRedirect(initial, dest *url.URL) bool {
|
|
+ // Permit sending auth/cookie headers from "foo.com"
|
|
+ // to "sub.foo.com".
|
|
+
|
|
+ // Note that we don't send all cookies to subdomains
|
|
+ // automatically. This function is only used for
|
|
+ // Cookies set explicitly on the initial outgoing
|
|
+ // client request. Cookies automatically added via the
|
|
+ // CookieJar mechanism continue to follow each
|
|
+ // cookie's scope as set by Set-Cookie. But for
|
|
+ // outgoing requests with the Cookie header set
|
|
+ // directly, we don't know their scope, so we assume
|
|
+ // it's for *.domain.com.
|
|
+
|
|
+ ihost := idnaASCIIFromURL(initial)
|
|
+ dhost := idnaASCIIFromURL(dest)
|
|
+ return isDomainOrSubdomain(dhost, ihost)
|
|
}
|
|
|
|
// isDomainOrSubdomain reports whether sub is a subdomain (or exact
|
|
diff --git a/src/net/http/client_test.go b/src/net/http/client_test.go
|
|
index 429b8f1..1ce9539 100644
|
|
--- a/src/net/http/client_test.go
|
|
+++ b/src/net/http/client_test.go
|
|
@@ -1525,6 +1525,63 @@ func TestClientCopyHeadersOnRedirect(t *testing.T) {
|
|
}
|
|
}
|
|
|
|
+// Issue #70530: Once we strip a header on a redirect to a different host,
|
|
+// the header should stay stripped across any further redirects.
|
|
+func TestClientStripHeadersOnRepeatedRedirect_h1(t *testing.T) {
|
|
+ testClientStripHeadersOnRepeatedRedirect(t, h1Mode)
|
|
+}
|
|
+func TestClientStripHeadersOnRepeatedRedirect_h2(t *testing.T) {
|
|
+ testClientStripHeadersOnRepeatedRedirect(t, h2Mode)
|
|
+}
|
|
+func testClientStripHeadersOnRepeatedRedirect(t *testing.T, mode bool) {
|
|
+ setParallel(t)
|
|
+ defer afterTest(t)
|
|
+ var proto string
|
|
+ cst := newClientServerTest(t, mode, HandlerFunc(func(w ResponseWriter, r *Request) {
|
|
+ if r.Host+r.URL.Path != "a.example.com/" {
|
|
+ if h := r.Header.Get("Authorization"); h != "" {
|
|
+ t.Errorf("on request to %v%v, Authorization=%q, want no header", r.Host, r.URL.Path, h)
|
|
+ }
|
|
+ }
|
|
+ // Follow a chain of redirects from a to b and back to a.
|
|
+ // The Authorization header is stripped on the first redirect to b,
|
|
+ // and stays stripped even if we're sent back to a.
|
|
+ switch r.Host + r.URL.Path {
|
|
+ case "a.example.com/":
|
|
+ Redirect(w, r, proto+"://b.example.com/", StatusFound)
|
|
+ case "b.example.com/":
|
|
+ Redirect(w, r, proto+"://b.example.com/redirect", StatusFound)
|
|
+ case "b.example.com/redirect":
|
|
+ Redirect(w, r, proto+"://a.example.com/redirect", StatusFound)
|
|
+ case "a.example.com/redirect":
|
|
+ w.Header().Set("X-Done", "true")
|
|
+ default:
|
|
+ t.Errorf("unexpected request to %v", r.URL)
|
|
+ }
|
|
+ }))
|
|
+ defer cst.close()
|
|
+
|
|
+ ts := cst.ts
|
|
+ proto = strings.Split(ts.URL, ":")[0]
|
|
+
|
|
+ c := ts.Client()
|
|
+ c.Transport.(*Transport).Dial = func(_ string, _ string) (net.Conn, error) {
|
|
+ return net.Dial("tcp", ts.Listener.Addr().String())
|
|
+ }
|
|
+
|
|
+ req, _ := NewRequest("GET", proto+"://a.example.com/", nil)
|
|
+ req.Header.Add("Cookie", "foo=bar")
|
|
+ req.Header.Add("Authorization", "secretpassword")
|
|
+ res, err := c.Do(req)
|
|
+ if err != nil {
|
|
+ t.Fatal(err)
|
|
+ }
|
|
+ defer res.Body.Close()
|
|
+ if res.Header.Get("X-Done") != "true" {
|
|
+ t.Fatalf("response missing expected header: X-Done=true")
|
|
+ }
|
|
+}
|
|
+
|
|
// Issue 22233: copy host when Client follows a relative redirect.
|
|
func TestClientCopyHostOnRedirect(t *testing.T) {
|
|
// Virtual hostname: should not receive any request.
|
|
@@ -1696,43 +1753,39 @@ func TestClientAltersCookiesOnRedirect(t *testing.T) {
|
|
// Part of Issue 4800
|
|
func TestShouldCopyHeaderOnRedirect(t *testing.T) {
|
|
tests := []struct {
|
|
- header string
|
|
initialURL string
|
|
destURL string
|
|
want bool
|
|
}{
|
|
- {"User-Agent", "http://foo.com/", "http://bar.com/", true},
|
|
- {"X-Foo", "http://foo.com/", "http://bar.com/", true},
|
|
-
|
|
// Sensitive headers:
|
|
- {"cookie", "http://foo.com/", "http://bar.com/", false},
|
|
- {"cookie2", "http://foo.com/", "http://bar.com/", false},
|
|
- {"authorization", "http://foo.com/", "http://bar.com/", false},
|
|
- {"authorization", "http://foo.com/", "https://foo.com/", true},
|
|
- {"authorization", "http://foo.com:1234/", "http://foo.com:4321/", true},
|
|
- {"www-authenticate", "http://foo.com/", "http://bar.com/", false},
|
|
- {"authorization", "http://foo.com/", "http://[::1%25.foo.com]/", false},
|
|
+ {"http://foo.com/", "http://bar.com/", false},
|
|
+ {"http://foo.com/", "http://bar.com/", false},
|
|
+ {"http://foo.com/", "http://bar.com/", false},
|
|
+ {"http://foo.com/", "https://foo.com/", true},
|
|
+ {"http://foo.com:1234/", "http://foo.com:4321/", true},
|
|
+ {"http://foo.com/", "http://bar.com/", false},
|
|
+ {"http://foo.com/", "http://[::1%25.foo.com]/", false},
|
|
|
|
// But subdomains should work:
|
|
- {"www-authenticate", "http://foo.com/", "http://foo.com/", true},
|
|
- {"www-authenticate", "http://foo.com/", "http://sub.foo.com/", true},
|
|
- {"www-authenticate", "http://foo.com/", "http://notfoo.com/", false},
|
|
- {"www-authenticate", "http://foo.com/", "https://foo.com/", true},
|
|
- {"www-authenticate", "http://foo.com:80/", "http://foo.com/", true},
|
|
- {"www-authenticate", "http://foo.com:80/", "http://sub.foo.com/", true},
|
|
- {"www-authenticate", "http://foo.com:443/", "https://foo.com/", true},
|
|
- {"www-authenticate", "http://foo.com:443/", "https://sub.foo.com/", true},
|
|
- {"www-authenticate", "http://foo.com:1234/", "http://foo.com/", true},
|
|
-
|
|
- {"authorization", "http://foo.com/", "http://foo.com/", true},
|
|
- {"authorization", "http://foo.com/", "http://sub.foo.com/", true},
|
|
- {"authorization", "http://foo.com/", "http://notfoo.com/", false},
|
|
- {"authorization", "http://foo.com/", "https://foo.com/", true},
|
|
- {"authorization", "http://foo.com:80/", "http://foo.com/", true},
|
|
- {"authorization", "http://foo.com:80/", "http://sub.foo.com/", true},
|
|
- {"authorization", "http://foo.com:443/", "https://foo.com/", true},
|
|
- {"authorization", "http://foo.com:443/", "https://sub.foo.com/", true},
|
|
- {"authorization", "http://foo.com:1234/", "http://foo.com/", true},
|
|
+ {"http://foo.com/", "http://foo.com/", true},
|
|
+ {"http://foo.com/", "http://sub.foo.com/", true},
|
|
+ {"http://foo.com/", "http://notfoo.com/", false},
|
|
+ {"http://foo.com/", "https://foo.com/", true},
|
|
+ {"http://foo.com:80/", "http://foo.com/", true},
|
|
+ {"http://foo.com:80/", "http://sub.foo.com/", true},
|
|
+ {"http://foo.com:443/", "https://foo.com/", true},
|
|
+ {"http://foo.com:443/", "https://sub.foo.com/", true},
|
|
+ {"http://foo.com:1234/", "http://foo.com/", true},
|
|
+
|
|
+ {"http://foo.com/", "http://foo.com/", true},
|
|
+ {"http://foo.com/", "http://sub.foo.com/", true},
|
|
+ {"http://foo.com/", "http://notfoo.com/", false},
|
|
+ {"http://foo.com/", "https://foo.com/", true},
|
|
+ {"http://foo.com:80/", "http://foo.com/", true},
|
|
+ {"http://foo.com:80/", "http://sub.foo.com/", true},
|
|
+ {"http://foo.com:443/", "https://foo.com/", true},
|
|
+ {"http://foo.com:443/", "https://sub.foo.com/", true},
|
|
+ {"http://foo.com:1234/", "http://foo.com/", true},
|
|
}
|
|
for i, tt := range tests {
|
|
u0, err := url.Parse(tt.initialURL)
|
|
@@ -1745,10 +1798,10 @@ func TestShouldCopyHeaderOnRedirect(t *testing.T) {
|
|
t.Errorf("%d. dest URL %q parse error: %v", i, tt.destURL, err)
|
|
continue
|
|
}
|
|
- got := Export_shouldCopyHeaderOnRedirect(tt.header, u0, u1)
|
|
+ got := Export_shouldCopyHeaderOnRedirect(u0, u1)
|
|
if got != tt.want {
|
|
- t.Errorf("%d. shouldCopyHeaderOnRedirect(%q, %q => %q) = %v; want %v",
|
|
- i, tt.header, tt.initialURL, tt.destURL, got, tt.want)
|
|
+ t.Errorf("%d. shouldCopyHeaderOnRedirect(%q => %q) = %v; want %v",
|
|
+ i, tt.initialURL, tt.destURL, got, tt.want)
|
|
}
|
|
}
|
|
}
|
|
diff --git a/src/net/http/internal/testcert/testcert.go b/src/net/http/internal/testcert/testcert.go
|
|
index d510e79..78ce42e 100644
|
|
--- a/src/net/http/internal/testcert/testcert.go
|
|
+++ b/src/net/http/internal/testcert/testcert.go
|
|
@@ -10,37 +10,56 @@ import "strings"
|
|
// LocalhostCert is a PEM-encoded TLS cert with SAN IPs
|
|
// "127.0.0.1" and "[::1]", expiring at Jan 29 16:00:00 2084 GMT.
|
|
// generated from src/crypto/tls:
|
|
-// go run generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
|
+// go run generate_cert.go --rsa-bits 2048 --host 127.0.0.1,::1,example.com,*.example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
|
|
var LocalhostCert = []byte(`-----BEGIN CERTIFICATE-----
|
|
-MIICEzCCAXygAwIBAgIQMIMChMLGrR+QvmQvpwAU6zANBgkqhkiG9w0BAQsFADAS
|
|
+MIIDSDCCAjCgAwIBAgIQFgSNW7Q203yuk2HUM4z0GDANBgkqhkiG9w0BAQsFADAS
|
|
MRAwDgYDVQQKEwdBY21lIENvMCAXDTcwMDEwMTAwMDAwMFoYDzIwODQwMTI5MTYw
|
|
-MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCB
|
|
-iQKBgQDuLnQAI3mDgey3VBzWnB2L39JUU4txjeVE6myuDqkM/uGlfjb9SjY1bIw4
|
|
-iA5sBBZzHi3z0h1YV8QPuxEbi4nW91IJm2gsvvZhIrCHS3l6afab4pZBl2+XsDul
|
|
-rKBxKKtD1rGxlG4LjncdabFn9gvLZad2bSysqz/qTAUStTvqJQIDAQABo2gwZjAO
|
|
-BgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH/BAUw
|
|
-AwEB/zAuBgNVHREEJzAlggtleGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAA
|
|
-AAAAATANBgkqhkiG9w0BAQsFAAOBgQCEcetwO59EWk7WiJsG4x8SY+UIAA+flUI9
|
|
-tyC4lNhbcF2Idq9greZwbYCqTTTr2XiRNSMLCOjKyI7ukPoPjo16ocHj+P3vZGfs
|
|
-h1fIw3cSS2OolhloGw/XM6RWPWtPAlGykKLciQrBru5NAPvCMsb/I1DAceTiotQM
|
|
-fblo6RBxUQ==
|
|
+MDAwWjASMRAwDgYDVQQKEwdBY21lIENvMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
|
+MIIBCgKCAQEAqspE6oCu4tLTgAca17wS9FecmXBWM2+qzz5dmvQAI25qf4xo0vKC
|
|
+/apwhh8pHO0S3IOHcILAy8j9e/cs1V8k0Dre7KGNqqZyNCc7950ZQtt/CRN1H8MF
|
|
+vAh20qsXC7BQjfE0Ga522d1UTUU0rAAhQk9Ityp4hy5f7RjXMbEJphgmtg1qZBnS
|
|
++Uahsiky1L6hmUdjWMKZYaS14X+N3MhGWjR2R2hiP/QMEb7Y9ReCd2sRqWTjKyXJ
|
|
+i5JD1+tVJgUeBJp4+38naVT3LG/VviIOE9xo4WhrtsnX9adm6ctcxPIFrPRWIhyV
|
|
+RSfQwHo26mJiGH2KsmM3gggX/W9E0ft6UQIDAQABo4GXMIGUMA4GA1UdDwEB/wQE
|
|
+AwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MB0GA1Ud
|
|
+DgQWBBT46lXgr7Azh/gy8zDLaq8oCpGCmzA9BgNVHREENjA0ggtleGFtcGxlLmNv
|
|
+bYINKi5leGFtcGxlLmNvbYcEfwAAAYcQAAAAAAAAAAAAAAAAAAAAATANBgkqhkiG
|
|
+9w0BAQsFAAOCAQEATM2XXLR3gTagL5MXK29RloJRD/vyZNiKL3Fuj/EOaU53Mpm2
|
|
+MJ7b7WTz4Kft285UaFqeSSTnOkDmlSPmccI2v7Ridp7gO3RimPz5Ofd1zLw12zEx
|
|
+4ZzFjU6vuLUwfw+1lw8xnS7cn1j2Q5AoWmfJxnQKCnkX487m/16szda49ydTestc
|
|
+s4g18dV/OGhWOQpLA2Z75DAu2rmE1oLKTrmYc36xjGXqMqSB33QU0sHgtkopuWdC
|
|
+C1TGa/ZwgQLdNPZfbdrYqtJQrnlyxSLx9R/ZZH00zse2N1eY0qPje2yXjNkD10Lp
|
|
+DXa5YlHo2skMtBMQ8uq+nQ+gWhRu5CJ2OYajVg==
|
|
-----END CERTIFICATE-----`)
|
|
|
|
// LocalhostKey is the private key for LocalhostCert.
|
|
var LocalhostKey = []byte(testingKey(`-----BEGIN RSA TESTING KEY-----
|
|
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
-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
|
|
+WlHTZvq5FrvuRL63przviPPw
|
|
-----END RSA TESTING KEY-----`))
|
|
|
|
func testingKey(s string) string { return strings.ReplaceAll(s, "TESTING KEY", "PRIVATE KEY") }
|
|
|