src/net/http/cgi/host.go | 4 ++++ src/net/http/cgi/host_test.go | 37 ++++++++++++++++++++++++++++++++++--- src/net/http/transport.go | 3 +++ src/net/http/transport_test.go | 14 +++++++++++++- diff --git a/src/net/http/cgi/host.go b/src/net/http/cgi/host.go index 9b4d8754183247661e339a104bd6a1d2738323b2..3f1600b1f55454b5cc703d1d4374652d7177c1ae 100644 --- a/src/net/http/cgi/host.go +++ b/src/net/http/cgi/host.go @@ -145,6 +145,10 @@ } for k, v := range req.Header { k = strings.Map(upperCaseAndUnderscore, k) + if k == "PROXY" { + // See Issue 16405 + continue + } joinStr := ", " if k == "COOKIE" { joinStr = "; " diff --git a/src/net/http/cgi/host_test.go b/src/net/http/cgi/host_test.go index 33277640ea54caa05bd319e6d275bbd9e07cbe7c..2783fe1eabe80aab5dabcb5ceb83d48e1b2a1c14 100644 --- a/src/net/http/cgi/host_test.go +++ b/src/net/http/cgi/host_test.go @@ -34,15 +34,18 @@ req.RemoteAddr = "1.2.3.4:1234" return req } -func runCgiTest(t *testing.T, h *Handler, httpreq string, expectedMap map[string]string) *httptest.ResponseRecorder { +func runCgiTest(t *testing.T, h *Handler, + httpreq string, + expectedMap map[string]string, checks ...func(reqInfo map[string]string)) *httptest.ResponseRecorder { rw := httptest.NewRecorder() req := newRequest(httpreq) h.ServeHTTP(rw, req) - runResponseChecks(t, rw, expectedMap) + runResponseChecks(t, rw, expectedMap, checks...) return rw } -func runResponseChecks(t *testing.T, rw *httptest.ResponseRecorder, expectedMap map[string]string) { +func runResponseChecks(t *testing.T, rw *httptest.ResponseRecorder, + expectedMap map[string]string, checks ...func(reqInfo map[string]string)) { // Make a map to hold the test map that the CGI returns. m := make(map[string]string) m["_body"] = rw.Body.String() @@ -79,6 +82,9 @@ } if got != expected { t.Errorf("for key %q got %q; expected %q", key, got, expected) } + } + for _, check := range checks { + check(m) } } @@ -233,6 +239,31 @@ "X-Foo: val1\n"+ "X-Foo: val2\n"+ "Host: example.com\n\n", expectedMap) +} + +// Issue 16405: CGI+http.Transport differing uses of HTTP_PROXY. +// Verify we don't set the HTTP_PROXY environment variable. +// Hope nobody was depending on it. It's not a known header, though. +func TestDropProxyHeader(t *testing.T) { + check(t) + h := &Handler{ + Path: "testdata/test.cgi", + } + expectedMap := map[string]string{ + "env-REQUEST_URI": "/myscript/bar?a=b", + "env-SCRIPT_FILENAME": "testdata/test.cgi", + "env-HTTP_X_FOO": "a", + } + runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\n"+ + "X-Foo: a\n"+ + "Proxy: should_be_stripped\n"+ + "Host: example.com\n\n", + expectedMap, + func(reqInfo map[string]string) { + if v, ok := reqInfo["env-HTTP_PROXY"]; ok { + t.Errorf("HTTP_PROXY = %q; should be absent", v) + } + }) } func TestPathInfoNoRoot(t *testing.T) { diff --git a/src/net/http/transport.go b/src/net/http/transport.go index 1e3ea11d9c5bca462b65f215f2402f7951830bef..794b7869429b8f28a2e23d5910b5f1564702fb41 100644 --- a/src/net/http/transport.go +++ b/src/net/http/transport.go @@ -216,6 +216,9 @@ proxy = httpsProxyEnv.Get() } if proxy == "" { proxy = httpProxyEnv.Get() + if proxy != "" && os.Getenv("REQUEST_METHOD") != "" { + return nil, errors.New("net/http: refusing to use HTTP_PROXY value in CGI environment; see golang.org/s/cgihttpproxy") + } } if proxy == "" { return nil, nil diff --git a/src/net/http/transport_test.go b/src/net/http/transport_test.go index d9da078fa0098f5c1d2513874710c221e73edbb3..381432e8b9c56639b18c2c66ea6b204d758c8d5e 100644 --- a/src/net/http/transport_test.go +++ b/src/net/http/transport_test.go @@ -1985,7 +1985,8 @@ req string // URL to fetch; blank means "http://example.com" env string // HTTP_PROXY httpsenv string // HTTPS_PROXY - noenv string // NO_RPXY + noenv string // NO_PROXY + reqmeth string // REQUEST_METHOD want string wanterr error @@ -2008,6 +2009,10 @@ } if t.noenv != "" { space() fmt.Fprintf(&buf, "no_proxy=%q", t.noenv) + } + if t.reqmeth != "" { + space() + fmt.Fprintf(&buf, "request_method=%q", t.reqmeth) } req := "http://example.com" if t.req != "" { @@ -2032,6 +2037,12 @@ // Use secure for https. {req: "https://secure.tld/", env: "http.proxy.tld", httpsenv: "secure.proxy.tld", want: "http://secure.proxy.tld"}, {req: "https://secure.tld/", env: "http.proxy.tld", httpsenv: "https://secure.proxy.tld", want: "https://secure.proxy.tld"}, + // Issue 16405: don't use HTTP_PROXY in a CGI environment, + // where HTTP_PROXY can be attacker-controlled. + {env: "http://10.1.2.3:8080", reqmeth: "POST", + want: "", + wanterr: errors.New("net/http: refusing to use HTTP_PROXY value in CGI environment; see golang.org/s/cgihttpproxy")}, + {want: ""}, {noenv: "example.com", req: "http://example.com/", env: "proxy", want: ""}, @@ -2047,6 +2058,7 @@ for _, tt := range proxyFromEnvTests { os.Setenv("HTTP_PROXY", tt.env) os.Setenv("HTTPS_PROXY", tt.httpsenv) os.Setenv("NO_PROXY", tt.noenv) + os.Setenv("REQUEST_METHOD", tt.reqmeth) ResetCachedEnvironment() reqURL := tt.req if reqURL == "" {