Practical HTTP Header Smuggling: Sneaking Past Reverse Proxies to Attack AWS
intruder.io
intruder.io
As soon as a space in a header name is found, a 400 Bad Request needs to be thrown. "Content-Length abcd: 0" is invalid and should never be accepted.
It shows just how careful you have to be when writing code that is Internet-facing, and especially on the scale of AWS where you have half the world's hackers trying to find exploits.
I'm not even looking for exploits and I find them every day. For instance, I wanted to read some magazines the other day but they were behind a paywall. Just to see what was behind the wall I checked for a sitemap file. 35MB sitemap.xml contains direct links to the full downloads of every item with no auth needed.
All code. “Internet facing” is not the only relevant qualification.
Any code where user-generated code is parsed should be carefully written, tested, and documented. Edge cases should be identified and described in specs. Non-compliant software should be identified and shamed (or preferably PRed).
I know that AWS has already patched some HTTP Smuggling attacks maybe 3 years ago, but I don’t remember if is was the same AWS feature (the previous one might have been CloudFront) and the parsing error might have been a little different.
Personally, if I were writing a HTTP request parser while being lazy about enforcing spec, I'd split ONLY on the colon, then just strip the white space on either side of both the header name and value. In Python:
header, value = line.split(':', maxsplit=1)
header = header.strip().lower()
value = value.strip()
After that, `header` should ALWAYS be checked via equality, and never `.startswith(...)`.Also, your code doesn’t fix the issue where a header name with a white space is accepted (which may violate expectations, depending on the server).
Your pseudo code also doesn’t handle edge cases where 2 headers which normalize to the same stripped text collide. One HTTP smuggling vector is the front server keeping a different header value than the back server when 2 header names collide.
Just shows how easy it is to be wrong by being lazy with http parsing.
According to which spec? RFC 7230 allows optional whitespace (OWS) after the colon, but not before it:
header-field = field-name ":" OWS field-value OWSI started a GitHub repo to run integration tests for popular combinations of reverse proxy to popular language web servers to identify these gaps in expectations (how duplicates, capitalization, white space, etc affect HTTP headers in different servers)
[1] A single byte is sufficient --- there have been far less than 255 headers defined since the beginnings of HTTP; maybe custom ones can be defined in an additional space, but a byte is actually already more than sufficient to convey the same information that would take dozens of bytes in the current text-based protocol.
The reason http is good is due to the text nature (along with whatever drawbacks associated with it being text).
You're probably only talking about ones defined in something like an RFC. I'm pretty sure there are far more than 255 different HTTP headers in use just by my employer's in-house stuff.
> more than sufficient to convey the same information that would take dozens of bytes in the current text-based protocol
It's 2021, who cares about dozens of bytes? I'd wager that's far less than 1% of the size of most HTTP exchanges, and it means generic tooling can actually show you something useful for all that custom stuff it's guaranteed not to know the specifics of.
It's 2021, who cares about dozens of bytes?
That's the sort of attitude that got us Electron and all the other bloated web crap out there. A little bit adds up quickly, especially at the scale of the Internet.
It's kind of ridiculous to argue for a one-byte address space in 2021, then kludging on an extension mechanism to handle the obvious fact that's too small. And even after that, you're still leaving everyone with ints instead of names. So we'll all get to ask ourselves "what header is 3849, again?" way more than we ever should.
That's ignoring the fact that this proposal is totally DOA unless you can find a time machine to go back to 1989 and hit Tim Berners-Lee on the head with a pipe while he was writing the HTTP spec.
>> It's 2021, who cares about dozens of bytes?
> That's the sort of attitude that got us Electron and all the other bloated web crap out there. A little bit adds up quickly, especially at the scale of the Internet.
There's about a light year between worrying about a dozen bytes and something like Electron.
I'm saying that it's not too small. You still haven't mentioned anything about your use-case of needing several hundred(!?!?) different unique headers. 30 years of HTTP and so far there's been less than 100 defined.
So we'll all get to ask ourselves "what header is 3849, again?" way more than we ever should.
Note that even those who have only a very vague idea of what HTTP is, know what a 404 is; and probably 403 too.
There's about a light year between worrying about a dozen bytes and something like Electron.
Multiply that dozen bytes by however much traffic goes through the entire Internet... and it suddenly doesn't look small anymore.
That's not actually my use case. My objection is that you're basically advocating for running a code obfuscator on HTTP requests, which would make them far more painful to work with for very, very little gain.
>> So we'll all get to ask ourselves "what header is 3849, again?" way more than we ever should.
> Note that even those who have only a very vague idea of what HTTP is, know what a 404 is; and probably 403 too.
You're missing the point, numeric codes work there because almost no one defines new HTTP status codes. IIRC, there's maybe two dozen defined and most programmers could probably name only 4 or 5 off the top of their heads. People define new headers all the time.
Ints as identifiers have practically no human meaning at all. Very, very few will know what header 3000 is without looking it up, and because it's an int, when there's a naming collision the meanings will almost certainly be wildly different. It'll be a mess.
> Multiply that dozen bytes by however much traffic goes through the entire Internet... and it suddenly doesn't look small anymore.
Think of is this way: you're wasting bytes in your markup by using long names. Most JSON objects should never need more than 26 variables. How about we restrict variables to single-character strings? It'll save some bytes, and think about how much JSON is zipping around the internet. Ditto with our programs. Lets install a linter on your machine that will fail your builds if you use more than a 1-char variable name.
Also, it's well into the 21st century. Don't you think we should go back to two-digit years for most dates, because bytes?
There are 61 headers already defined in this table.
[1] https://httpwg.org/specs/rfc7541.html#static.table.definitio...
Just use variable length encodings like LEB 128
And yes I have seen bytes in both names and values in the wild (where bytes in names are invalid but need to be handled gracefully, while bytes in values are effectively valid latin1 if only for legacy reasons)
Looking at the bug you linked to, looks like this almost bit them too. Here's the final field-value behavior they landed on: https://go-review.googlesource.com/c/go/+/18375/
Based on that, some implementations seem to restrict allowed values to the rules that you describe, while others don't.