Go: net/http: request with a + in Content-Length is considered valid

Created on 12 May 2020  Â·  15Comments  Â·  Source: golang/go

What version of Go are you using (go version)?

$ go version
go version go1.14.2 linux/amd64

Does this issue reproduce with the latest release?

Yes

What operating system and processor architecture are you using (go env)?

go env Output

$ go env
GO111MODULE="on"
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/juliens/.cache/go-build"
GOENV="/home/juliens/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GONOPROXY=""
GOOS="linux"
GOPATH="/home/juliens/dev/go"
GOPRIVATE=""
GOPROXY="direct"
GOROOT="/home/juliens/go-current"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/home/juliens/go-current/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD="/dev/null"
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build262375258=/tmp/go-build -gno-record-gcc-switches"

What did you do?

I launch a server using

http.ListenAndServe(":8080", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
    fmt.Println(req.ContentLength)
}))

and after that, I make a call with a Content-Length: +3

echo -ne "POST / HTTP/1.1\r\nContent-Length: +3\r\nHost: 127.0.0.1\r\n\r\naaa\r\n" | nc 127.0.0.1 8083

What did you expect to see?

A bad request ( because of the RFC https://tools.ietf.org/html/rfc2616#section-14.13 )

What did you see instead?

A valid request, the Content-Length is parsed as just 3

More

For what I saw, it's because the Content-Length is parsed by using strconv.ParseInt and so +3 is valid and becomes 3

NeedsFix

Most helpful comment

In the process of looking into this, we found another hardening fix I'll request a freeze exception for in a moment: leading zeroes are accepted the same way a leading + is. The HTTP/2 fix will probably want to fix that as well for Go 1.16.

Sigh, scratch that. Turns out that leading zeroes are wholly unspecified. Someone tried to make an argument for a better spec in 2008 but was met with strong resistance.

This goes to show that

  1. making header parsing and semantics critical to the security of the protocol framing was a catastrophic design decision; and
  2. it's simply impossible for implementations to agree on the exact same set of valid headers, so the only countermeasure to request smuggling is for proxies not to forward headers they did not parse successfully.

(Request smuggling can still occur when two implementations both find a header valid, but disagree on its meaning, but in that case it's likely one of them is simply wrong.)

All 15 comments

I'd like to take a stab if there's no objection from the Go team!

For my first attempt, I simply used strconv.ParseUint (which disallows the prefixed sign), to replace the strconv.ParseInt calls (that allow it), and then cast back to int64. I'm using ParseUint(cl, 10, 63) to leave that extra sign bit, so it's safe to convert back to an int64.

From some manual testing, this seems to do the trick. I've added some unit test that reference this issue for posterity and have opened a CL.

With that change, I get

func main() {
    http.ListenAndServe(":8080", http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
        fmt.Println(req.ContentLength)
    }))
## Before
$ go run main.go
$ echo -ne "POST / HTTP/1.1\r\nContent-Length: 3\r\nHost: 127.0.0.1\r\n\r\naaa\r\n" | nc 127.0.0.1 8080
HTTP/1.1 200 OK
...

$ echo -ne "POST / HTTP/1.1\r\nContent-Length: +3\r\nHost: 127.0.0.1\r\n\r\naaa\r\n" | nc 127.0.0.1 8080
HTTP/1.1 200 OK
...

## After
$ $GODIR/bin/go run main.go
$ echo -ne "POST / HTTP/1.1\r\nContent-Length: 3\r\nHost: 127.0.0.1\r\n\r\naaa\r\n" | nc 127.0.0.1 8080
HTTP/1.1 200 OK
...

$ echo -ne "POST / HTTP/1.1\r\nContent-Length: +3\r\nHost: 127.0.0.1\r\n\r\naaa\r\n" | nc 127.0.0.1 8080
HTTP/1.1 400 Bad Request
...

Change https://golang.org/cl/234817 mentions this issue: net/http: Disallow plus symbol in Content-Length

Thank you for the report @juliens! Thank you for working on the fix @tpaschalis, awesome!
I've added some suggestions to your CL, please take a look. Our HTTP/2 stack purposefully skips checking for errors from strconv.ParseInt, because such errors don't necessarily affect our framing. Unfortunately this increases the surface area for what could change be broken within this release that we are already in.

@bradfitz would it be pragmatic for us to firstly accept only the fix for HTTP/1.1 for Go1.15? For Go1.16 make the follow-on update in x/net/http2 and subsequently to net/http/h2_bundle.go? We ignore the errors anyways in HTTP/2 so the change could perhaps survive one more release.

Change https://golang.org/cl/236098 mentions this issue: x/net/http2: reject HTTP/2 Content-Length headers containing a sign

@odeke-em sorry for pinging you! A few days ago, I opened a similar CL for HTTP/2.

I'm not sure if I should put you as a reviewer since we had some communication for the HTTP/1.1 part, or if you're busy with other things; in any case I'd be grateful if you could take a look! Thanks in advance.

Too late for 1.15. Moving to Backlog.

HTTP/2 Content-Lengths are indeed not security-relevant, so now that this is fixed in HTTP/1.1, it's not a security issue anymore. Even in HTTP/1.1, this was only an issue when paired with a misbehaving proxy that forwards invalid Content-Length headers that it ignored, when it's supposed to strip or reject them, so we consider this a security hardening fix, and it won't be backported.

We'd like to also thank Amit Klein, Safebreach for reporting this to [email protected] before this issue was opened.

In the process of looking into this, we found another hardening fix I'll request a freeze exception for in a moment: leading zeroes are accepted the same way a leading + is. The HTTP/2 fix will probably want to fix that as well for Go 1.16.

In the process of looking into this, we found another hardening fix I'll request a freeze exception for in a moment: leading zeroes are accepted the same way a leading + is. The HTTP/2 fix will probably want to fix that as well for Go 1.16.

Sigh, scratch that. Turns out that leading zeroes are wholly unspecified. Someone tried to make an argument for a better spec in 2008 but was met with strong resistance.

This goes to show that

  1. making header parsing and semantics critical to the security of the protocol framing was a catastrophic design decision; and
  2. it's simply impossible for implementations to agree on the exact same set of valid headers, so the only countermeasure to request smuggling is for proxies not to forward headers they did not parse successfully.

(Request smuggling can still occur when two implementations both find a header valid, but disagree on its meaning, but in that case it's likely one of them is simply wrong.)

Shall we make backports for Go1.13 and Go1.14 for this issue for HTTP/1.1?

Shall we make backports for Go1.13 and Go1.14 for this issue for HTTP/1.1?

No need, we decided we'd land this as a hardening measure, not as a security fix.

Cool, thanks!

On Mon, Jun 29, 2020 at 6:16 PM Filippo Valsorda notifications@github.com
wrote:

Shall we make backports for Go1.13 and Go1.14 for this issue for HTTP/1.1?

No need, we decided we'd land this as a hardening measure, not as a
security fix.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/golang/go/issues/39017#issuecomment-651458761, or
unsubscribe
https://github.com/notifications/unsubscribe-auth/ABFL3V3DH5BB4WOBWXRGAHTRZE4GXANCNFSM4M63G7DQ
.

Change https://golang.org/cl/253238 mentions this issue: src/go.mod, net/http: update bundled and latest golang.org/x/net

Change https://golang.org/cl/261919 mentions this issue: [release-branch.go1.15] src, net/http: update vendor, h2_bundle.go regeneration

Change https://golang.org/cl/258540 mentions this issue: [release-branch.go1.15] src, net/http: update vendor, regenerate h2_bundle.go

Change https://golang.org/cl/258538 mentions this issue: [release-branch.go1.14] src, net/http: update vendor, regenerate h2_bundle.go

Was this page helpful?
0 / 5 - 0 ratings