From: Mike Samuel Date: Tue, 23 Jan 2018 19:27:47 +0000 (-0500) Subject: net/http: don't sniff Content-type in Server when X-Content-Type-Options:nosniff X-Git-Tag: go1.11beta1~895 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=1a677e03c827b7b1ab2008be2a8f340fb072531c;p=gostls13.git net/http: don't sniff Content-type in Server when X-Content-Type-Options:nosniff The docs for ResponseWriter.Write say // If the Header // does not contain a Content-Type line, Write adds a Content-Type set // to the result of passing the initial 512 bytes of written data to // DetectContentType. The header X-Content-Type-Options:nosniff is an explicit directive that content-type should not be sniffed. This changes the behavior of Response.WriteHeader so that, when there is an X-Content-Type-Options:nosniff header, but there is no Content-type header, the following happens: 1. A Content-type:application/octet-stream is added 2. A warning is logged via the server's logging mechanism. Previously, a content-type would have been silently added based on heuristic analysis of the first 512B which might allow a hosted GIF like http://www.thinkfu.com/blog/gifjavascript-polyglots to be categorized as JavaScript which might allow a CSP bypass, loading as a script despite `Content-Security-Policy: script-src 'self' `. ---- https://fetch.spec.whatwg.org/#x-content-type-options-header defines the X-Content-Type-Options header. ["Polyglots: Crossing Origins by Crossing Formats"](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.905.2946&rep=rep1&type=pdf) explains Polyglot attacks in more detail. Change-Id: I2c8800d2e4b4d10d9e08a0e3e5b20334a75f03c0 Reviewed-on: https://go-review.googlesource.com/89275 Reviewed-by: Brad Fitzpatrick --- diff --git a/src/net/http/serve_test.go b/src/net/http/serve_test.go index be465dd35e..e40bbc5575 100644 --- a/src/net/http/serve_test.go +++ b/src/net/http/serve_test.go @@ -3553,6 +3553,23 @@ func TestHeaderToWire(t *testing.T) { return nil }, }, + { + name: "Nosniff without Content-type", + handler: func(rw ResponseWriter, r *Request) { + rw.Header().Set("X-Content-Type-Options", "nosniff") + rw.WriteHeader(200) + rw.Write([]byte("\nsome html")) + }, + check: func(got string) error { + if !strings.Contains(got, "Content-Type: application/octet-stream\r\n") { + return errors.New("Output should have an innocuous content-type") + } + if strings.Contains(got, "text/html") { + return errors.New("Output should not have a guess") + } + return nil + }, + }, } for _, tc := range tests { ht := newHandlerTest(HandlerFunc(tc.handler)) diff --git a/src/net/http/server.go b/src/net/http/server.go index ad3222d3a1..114a2263c3 100644 --- a/src/net/http/server.go +++ b/src/net/http/server.go @@ -1339,7 +1339,15 @@ func (cw *chunkWriter) writeHeader(p []byte) { // If no content type, apply sniffing algorithm to body. _, haveType := header["Content-Type"] if !haveType && !hasTE && len(p) > 0 { - setHeader.contentType = DetectContentType(p) + if cto := header.get("X-Content-Type-Options"); strings.EqualFold("nosniff", cto) { + // nosniff is an explicit directive not to guess a content-type. + // Content-sniffing is no less susceptible to polyglot attacks via + // hosted content when done on the server. + setHeader.contentType = "application/octet-stream" + w.conn.server.logf("http: WriteHeader called with X-Content-Type-Options:nosniff but no Content-Type") + } else { + setHeader.contentType = DetectContentType(p) + } } } else { for _, k := range suppressedHeaders(code) {