]> Cypherpunks repositories - gostls13.git/commitdiff
net/http: don't sniff Content-type in Server when X-Content-Type-Options:nosniff
authorMike Samuel <mikesamuel@gmail.com>
Tue, 23 Jan 2018 19:27:47 +0000 (14:27 -0500)
committerBrad Fitzpatrick <bradfitz@golang.org>
Tue, 10 Apr 2018 16:42:54 +0000 (16:42 +0000)
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 <bradfitz@golang.org>
src/net/http/serve_test.go
src/net/http/server.go

index be465dd35ef029b6f822c7579e0e9c4a60d24891..e40bbc5575dadb4778bbcf419c8c3868e2e410d8 100644 (file)
@@ -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("<!doctype html>\n<html><head></head><body>some html</body></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))
index ad3222d3a1b61e9a047187844874327cb3317167..114a2263c36c200cc19b479e92737221ed51551d 100644 (file)
@@ -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) {