HTTP/2-exclusive threats caused by implementation flaws and RFC imperfections
portswigger.net
portswigger.net
So it seemed that somewhere along the years while I haven't been paying any attention to websec, it became a common practice to send requests from different clients through the same TLS connection. And due to the non conforming way HTTP 2/1.1 interop was implemented by these webservers/load balancers request boundaries were not delimited correctly, making it possible to inject requests on behalf of followup clients.
Fine I get the issue. Sounds like another "good enough" optimization that backfired.
What is the solution aside from playing the patching wack-a-mole game? Should maybe a HTTP2.1 protocol work without a strict TLS requirement so that protocol downgrades aren't necessary to squeeze out extra performance (unless I misunderstand why the HTTP2/HTTP1.1 bridge was in place). Or is the problem that some application servers still don't support HTTP2 out of the box?
Http3 is almost exactly http2 but over quic. This means the requests should be possible to force the same desyncs talked about in the article.
I don’t think Ruby is doing much better? Correct me if I’m wrong.
EDIT: and yes I understand that you should use http/2 on the LB and http/2 on the backend to get the best of both worlds.
EDIT2: anyway my opinion is that the general reaction to a security discovery like this one shouldn't be "let's stop using this tech immediately" but "let's get this patched ASAP"
If on Kubernetes, just install cert-manager. Or if using FaaS, your platform will already do TLS termination, no?
Besides, if you're doing microservices, whatever is managing them should be able to restart them gracefully. No need for reload as such.
You can still of course use self-signed certificates (or setting up your own "CA"), but you'll hit other problems related to runtime certificate reloads and so on. It's still a lot of work to enable SSL for fully offline services.
Terminating HTTP/2 over TLS on a web frontend and then HTTP/2 over plaintext to the application servers sounds like a viable model.
[1]: https://docs.aws.amazon.com/elasticloadbalancing/latest/appl...
curl -v --http2 --http2-prior-knowledge http://localhost
* Connected to localhost (::1) port 80 (#0)
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x559a7c6545c0)
> GET / HTTP/2
> Host: localhost
> user-agent: curl/7.74.0
> accept: */*
>
* Connection state changed (MAX_CONCURRENT_STREAMS == 100)!
< HTTP/2 301
< date: Fri, 06 Aug 2021 11:16:05 GMT
*snip*
Sadly none of the services that I reverse proxy through Apache support HTTP/2..Although all of your attack vectors are fascinating, this seems to be the one which is particularly terrifying. It seems like one single request can basically mess up user authentication across an entire website, and for every active user, until the server becomes resynchronized somehow. If I were running a large public-facing site or application, checking for this vulnerability would be my #1 priority this morning.
That said, I haven't actually tested this on very many live servers, for obvious reasons!
It’s not like the feature is super useful in general.
edit: Cloudflare has responded that "[they] are asking your question internally."
Client → (HTTP/2) → Cloudflare ingress → (???) → Cloudflare egress → (???) → Your endpoint → (???) → Your web app
On your endpoint (LB or webserver) logs you can check if the requests coming in from Cloudbees are using HTTP/2 or not. If they do use HTTP/2, they internally might still do translation down to HTTP/1.1 and back.
But more importantly, you should investigate how your LB or webserver talks to the web app. In many cases that part won't be TLS encrypted and therefore on an HTTP/1.1 channel.
The exception is if your outward facing, TLS terminating, webserver interacts with the web app via CGI or FastCGI. While that part of the call stack would not be affected by this particular attack, (Fast)CGI comes with it's own set of risks.
In the end, the only part that you can't easily validate yourself of being susceptible to this attack (aka having to translate between HTTP/2 and HTTP/1.1) is the bit that occurs internally to Cloudflare.
Or even HTTP 1.0 . I found out recently while inspecting some requests in the upstream server that nginx was using HTTP 1.0 after terminating TLS. I was dumbfounded that this was still the default.
http://nginx.org/en/docs/http/ngx_http_proxy_module.html#pro...
I'm certainly not trying to downplay the seriousness of these issues. But it seems like a equally (it not more) valid title might be something like "HTTP/1: Continues to rear its ugly head" or something like that.
It's also not he narrative the content of the article purses ;-)
This is a deliberately cheeky title, selected as a presentation title, rather than being aimed at social media where many people will read the title but not the contents. For HN I'd go with something dry like "HTTP/2-exclusive attack vectors".
As far as I can tell, the article doesn't attack HTTP2, that seems work fine. The article clearly demonstrates the problems of HTTP1. But the real problem is sloppy HTTP2 forntends that generate broken HTTP1.
From a look at the HTTP/2 spec. this protocol translation is an expected use case and explicitly results in several restrictions on the contents of HTTP/2 headers. So going by the HTTP/2 spec. at least some of those headers should have been rejected by a conformant front end and never made it into HTTP/1.
Parts of the HTTP/2 spec. specify what malformed headers look like, so this bug is almost entirely in the code not validating those HTTP/2 headers. This also isn't something that a few high school dropouts got wrong on their side projects, several major services got it wrong. Given that the spec. made at least some attempts to warn its implementers maybe future standards need a security section titled "Important: Why ignoring security advisory in standards is a bad idea".
Then, the \r\n in headers can easily trip an application that was naively ported to http/2 supporting network library. That's too pretty ripe for blame shifting :)
> If you're coding an HTTP/2 server, especially one that supports downgrading, enforce the charset limitations present in HTTP/1 - reject requests that contain newlines in headers, colons in header names, spaces in the request method, etc.
Am I misunderstanding?
Just a quick question about the H2.CL case study. Did Netflix have a separate vulnerability where their server wrongly trusted Host headers with netflix.com suffix and returned a 302 response which can redirected users to an arbitrary host? I'm just trying to see whether I interpret the case study correctly.
I wouldn't exactly class this as a vulnerability - more of a useful gadget. The front-end would refuse to forward such a request so it's impossible to hit this code path without request smuggling. Even if you could hit it without request smuggling it would still be useless.
Some of them are a little more complex, requiring actual HTTP/2 and HTTP/1.1 knowledge (largely meaning HTTP/2 framing and the content-length and transfer-encoding headers), but not most of them.
Is HTTP/2 actually worse? Not in the slightest; HTTP/1.1 is the problem here. This is growing pains from compatibility measures as part of removing the problems of an unstructured text protocol. If you have a pure-HTTP/2 system and don’t ever do the downgrade, you’re in a better position.
On the other hand, one maxim I've learned from my time bug hunting is that nobody ever validates strings in binary protocols. As such, I'm utterly unsurprised there are so many implementations with these kinds of bugs, and I'd say they could have been predicted in advance.
In fact… let's see… yep, they were predicted. Some of them, at least. In the HTTP/2 RFC, under Security Considerations, 10.3 'Intermediary Encapsulation Attacks' describes one of the attack classes from the blog post, the one involving stuffing newlines into header names.
Does that mean something could have been done about it? Perhaps not. The ideal solution would be to somehow design the HTTP/2 protocol itself to be resistant to misimplementation, but that seems pretty much impossible. The spec already bans colons and newlines in header names, but there's no way to be sure implementations won't allow them anyway, short of actually making them a delimiter like HTTP/1 did – in other words, reverting to a text-based protocol. But a text-based protocol would come with its own share of misimplementation risks, the same ones that HTTP/1 has.
On the other hand, perhaps the bug classes could have been mitigated if someone designed test cases to trigger them, and either included them in conformance tests (apparently there was an official HTTP/2 test suite [2] though it doesn't seem to have been very popular), or set up some kind of bot to try them on the entire web. In principle you could blame the authors of HTTP/2 collectively for the fact that nobody did this. But I admit that's pretty handwavey.
[1] https://datatracker.ietf.org/doc/html/rfc7540#section-10.3
I wonder how much this has to do with the way strings need to be handled in the programming languages these protocols are implemented in. If dealing with strings is something that seems to be even more of a danger (if done incorrectly) you might just not do it.
It's easy to believe most professional teams make that mistake at some point. I'd hope that it's far more rare to make that mistake twice.
It’s quite possible that my attitude to these sorts of things has been warped by using Rust, which both encourages proper validation and makes it easier and more natural than it tends to be in languages like C or C++. I’d be curious to see figures of these sorts of vulnerabilities in varying languages—I strongly suspect that they occur vastly less in Rust code than in C or C++ code, even when they’re not directly anything to do with memory safety.
In general, when writing a high performance middle box, you want to touch the data as little as possible: ideally, the CPU wouldn't even see most of the bytes in the message, they would just be DMA'd from the external NIC to the internal NIC. This is probably not doable for HTTP2->HTTP1, but the general principle applies. In high-performance code, you don't want to go matching strings any more than you think is strictly necessary (e.g. matching the host or path to know where to actually send the packet).
Which is not to say that it wasn't a mistake to assume you can get away with this trade-off. But it's not a rookie error.
As someone who has implemented HTTP/1.1 message parsing myself, it blows my mind how downgrade implementers could've made such basic mistakes as to not validate the contents of headers. The companies selling security software with these mistakes should be held accountable IMHO, as the attacks are so simple and so many that they've probably been exploited extensively in the wild, and similar attacks almost certainly continue to be.
However, if the only way you can use HTTP/2 is by having the front-end downgrade it to HTTP/1, I would recommend disabling it.
I'd say that the main cause of these issues is the directive language used in the H2 spec almost only saying "this must be done like this" without giving rationale for the rules, meaning that implementations that were unable to implement them exactly the same way were left with no hint about what the trouble was nor how to address it. The new spec in progress and the extraction of the core semantics makes the whole thing a lot cleaner.
In addition, H2 uses much more resources per connection than H1, which encourages to coalesce them between the proxy and the server. But coalescing connections can easily cause some head-of-line blocking if coalesced front streams show different bandwidths. So even end-to-end H2 is not always a panacea.
It simply broke client certificate authentication and NTLM authentication, leaving only cookie-based authentication fully functional.
Can you guess which two of the three popular authentication mechanisms Google doesn't use?
It's not a protocol designed to advance the Internet, it's a protocol designed by Google to shave 1% off their network bill.
Kerberos and HTTP/2 work just fine together.
What in particular is broken with NTLM?
https://docs.microsoft.com/en-us/iis/get-started/whats-new-i...
I’m not sure that it’s a problem with HTTP/2 that Microsoft found no reason to implement Windows authentication for it.
But MS has also the "Negotiate" protocol they made to replace NTLM on IE (because even the IE team considered it irremediably broken). That one is a variant of Kerberos, and a lot of people say just NTLM when they mean "Negotiate with a possible downgrade to NTLM".
IOW, HTTP/2 is not designed for users. It is designed for online advertisers and the companies that serve them.
How do I know this. Because HTTP/1.1 pipelining still works great. I am using it on a separate task as I type this (I use it outside the browser).
It just doesn't work well for companies that profit from online advertising as a "business model", the so-called "ecosystem" of actors seeking to capture "eyeballs" and Hoover up data about users.
That includes people who design whiz-bang websites making dozens to hundreds of "automatic" requests not initiated by the user, many for arguably unnecessary external resources (or for telemetry), to gather data on the user and/or to serve her with ads.
The type of websites that drive HN readers nuts. That is not a problem with HTTP/1.1, that's a problem with the effects of online advertising, surveillance and greed run amok.
It also includes popular browsers supported by advertising. HTTP does not exist exclusively for a handful of corporate-sponsored browsers. It exists for all present and future clients that users wish to use to retreive information from the web.
HTTP/2 is biased against users in favour of a web that exists only for online advertising (so it can keep filling Google's pockets with cash). It is a protocol that is so complex that users, not to mention the author of this blog post, "cannot easily implement clients for it."
https://en.wikipedia.org/wiki/HTTP_pipelining: says that proxies and web browsers mostly don't support it. It claims that it's easy for servers to support it, but provides no more details. It also mentions that curl removed pipelining support.
https://forum.nginx.org/read.php?2,269248,269249#msg-269249: Some person says that Nginx doesn't support pipelining. Other people agree. I can't find anything to contradict that.
https://serverfault.com/questions/266184/does-apache-webserv...: Someone says that Apache doesn't support pipelining. Again, I can't find anything to contradict that.
https://stackoverflow.com/questions/17299489/iis-and-http-pi...: This person says IIS doesn't support pipelining. Against, I can't find contradictory evidence.
Twisted used to support pipelining, but removed it: https://twistedmatrix.com/trac/ticket/8320
So, who exactly supports HTTP/1.1 pipelining?
It is true that if you fire multiple requests off to an HTTP/1.1 host without waiting for a response, you'll probably get responses back. The thing is that most hosts will process those requests one at a time. This is not pipelining - this is just processing requests one at a time as they come in. So, you're saving the latency required to get the request to the server - but not getting any benefit from parallel processing since the servers process the requests serially. With HTTP/2, however, at least some servers will actually process those requests in parallel - potentially with better performance.
And i actually guess the person who mentioned pipelining just meant connection reuse.
The client would then be expected to close the socket, possibly causing a TCP reset (if the server hadn't read all the data off the socket)
Your guess is incorrect. I mean sending multiple requests over a single TCP connection. Usually 100 or more.
When performing information retrieval, e.g., fetching a series of pages from the same host, I do not want out of order responses. I want the responses returned in order. I want the HTTP headers, too, as record separators and so I can be sure all requests were filled.
This sort of pipelining is not useful for websites that want to source myriad resources into their pages from external sources to serve ads, perform tracking and all that commercially-oriented stuff that is necessary for companies like Google to survive. HTTP/2 is useful for commercially-oriented use of the web. Surveillance and ads. I'm not interested in using the web that way.
HTTP/1.1 pipelining is useful for informational retrieval, i.e., retrieving hundreds of resources from the same host without opening up hundreds of connections. That sort of bulk information retrieval is not compatible with online advertising and tracking. Thus, Google and other companies supporting HTTP/2 have no interest in it. It benefits users, not advertisers.
I fully expect some nasty comments from "tech" workers whenever I bring up this topic. I am speaking from a user persepctive, not a "developer" perspective.
In the early days of the web, opening up hundreds of connections at once would be poor etiquette (toward the server operator). Today, many so-called "engineers" do not know any other way. Not only do they do it to servers (e.g, asynchronous requests), they do it to clients, causing a user's browser to make hundreds of requests to different servers for a single web page. Looking at a Network panel in Developer Tools in a popular browser when accessing an "average" web page reveals the sheer insanity of so-called today's "web development". The other commenter clearly has never even used HTTP/1.1 pipelining, at least not consciously, and yet he wants to offer his opinion on it. Nice.
I use HTTP/1.1 pipelining every day. For example, I use it to retrieve bulk DNS data from DoH servers. The future of HTTP/2 is uncertain. HTTP/1.1 is not going away anytime soon.
I can test every website that is currently submitted to HN for pipelining support. I would bet the majority allow pipelining. If I wanted to retrieve a large number of pages from any of them, I could use pipelining to do it.
I get no benefits from HTTP/2 because I mainly use the web for non-commercial purposes. For that use, I do not use a popular browser. I do not wait for websites to "load" whilst they open dozens or even hundreds of connections to other servers. I retrieve HTML from the command line using netcat and haproxy. It's fast. I use a text-only browser to read HTML.
When using the web in the way I do, without seeing any ads, without all the automatically triggered requests to external servers, performance is not an issue. When using the web the way Google wants people to use it, chock full of ads, then performance is an issue and something like HTTP/2 makes sense. Thus, how one uses the web matters. One size does not fit all.
There are probably millions. IME, over 20 years of using pipelining, it is quite rare to find ones that don't. Here is a simple example.
Download the k-tree transcript archive from stackexchange.com
1116 HTTP requests, 1 TCP connection
26MB download
stunnel -fd 0 << eof
debug=debug
pid=$HOME/1.pid
foreground=no
[ x ]
accept=127.0.0.77:80
client=yes
connect=198.252.206.29:443
options=NO_TICKET
options=NO_RENEGOTIATION
renegotiation=no
sni=
sslVersion=TLSv1.3
eof
export Connection=keep-alive;
sh -c "$(sh -c "$(seq -f "seq -f 'seq -f "https://chat.stackexchange.com/transcript/90748/%g/%%g/%%%%g" 1 31' 1 12" 2019 2021)")" \
|a.out \
|nc -vvn 127.77 80 > 1.htm
cd;kill $(cat 1.pid)
cat > 1.l
int yy_get_next_buffer();
int fileno(FILE *);
int setenv (const char *, const char *, int);
int dprintf(int, const char *__restrict, ...);
#define httpMethod "GET"
#define httpVersion "1.1"
#define Host ""
#define jmp BEGIN
#define Y(x,y) fprintf(stdout,x,y)
int count,path,ka;
int httpheaders(){
setenv("httpMethod",httpMethod,0);Y("%s ",getenv("httpMethod"));
Y("%s HTTP/",getenv("Path"));
setenv("httpVersion",httpVersion,0);Y("%s\r\n",getenv("httpVersion"));
if(0==setenv("Host","",0))Y("Host: %s\r\n",getenv("Host"));
if(getenv("Connection"))Y("Connection: %s\r\n",getenv("Connection"));
fputs("\r\n",stdout);
return 0;}
%option nounput noinput
%s xa xb xc
xa "http://"|"https://"
xb [-A-Za-z0-9.:]*
xc [^#'|<> \r\n]*
%%
^{xa} count++;setenv("Scheme",yytext,0);jmp xa;
<xa>{xb} setenv("Host",yytext,1);if(!getenv("Host"))setenv("Host",Host,0);jmp xb;
<xb>\n path=0;setenv("Path","/",0);httpheaders();jmp 0;
<xb>{xc} path=1;setenv("Path",yytext,1);httpheaders();jmp 0;
.|\n
%%
int main(){yylex();exit(0);}
int yywrap(){if(count>1){
fputs("GET /robots.txt HTTP/1.1\r\n",stdout);
Y("Host: %s\r\n",getenv("Host"));
fputs("Connection: close\r\n",stdout);
fputs("\r\n",stdout);};
exit(0);}
^D
flex 1.l
cc -std=c89 -Wall -pedantic -static -pipe lex.yy.cThat's incorrect. This is pipelining as it is defined in RFC2616.
https://tools.ietf.org/html/rfc7230#section-6.3.2
It is not latency we are trying to save with HTTP/1.1 pipelining, it is server resources, namely the number of simultaneously open connections. (See Section 6.4)
RFC 2616 does not require parallel processing. It's optional.
Personally, I do not care about parallel processing. I want the responses returned in order. I get satisfactory performance from FIFO. That's because I only request resources from the domain I type into the computer.
A website that allows ads to be served from a variety of domains that the user never typed might have a problem with performance. However that is the web developer's problem, not the user's. Online ads are optional. There is nothing in RFC2616 that requires online advertising. The web works great without ads, and that is how I use it.
HTTP/2 is designed to serve the goals of companies that assist with online advertising. Google and others. Automatically triggering requests for ads from third party domains through webpages is a particular type of web usage, promoted by "tech" companies to support their online advertising "business model", but it is not the only type of web usage. It has performance problems. Go figure.
There is nothing to indicate any person outside of the "tech" industry is interested in this type of web use. How many users intentionally request ads. None. No user ever types in the domain of an ad server.
Requesting many small resources from the same domain, i.e., the domain the user types into the computer, using pipelined requests generally does not suffer from performance problems. It is fast and efficient for the types of web use that are not requesting ads from third party domains. Not to mention it is far more energy efficient.
The only usability difference between HTTP2 and HTTP1(.1) is that it's easier to write a broken implementation of a quarter of HTTP1 and still do some useful things with it.
A full, production-grade HTTP1.1 client or server is more or less as complex as an HTTP2 client or server. HTTP2 is actually easier to implement at the base level, since it's much easier to work with binary protocol formats than with the horrible string encoding of HTTP1. HTTP2 does add a lot of complexity with the stream multiplexing features afterwards - more or less enough as to cover the endless headache of deciphering HTTP 1.1 requests and arcane connection headers.
Google is hardly alone. Non-cookie-based authentication mechanisms aren't used (and aren't even usable) on any other public web site, either.
TLS client certificates have always been a UX nightmare. There is no standard UI for creating, installing, or selecting a certificate to use to authenticate to a web server; many browsers (especially on mobile devices) don't even support those features. There's no way to log out without closing the browser. There's no way for an average user to transfer a certificate from one device to another.
NTLM is simply irrelevant outside the scope of a Windows network. Other HTTP password mechanisms have many of the same failings as client certificates -- the UI is clunky and sometimes unavailable, and there's no standard way to log out.
Internal-use web sites are a thing, however. A very common, heavily used thing. Many of them use NTLM or Kerberos authentication.
> TLS client certificates have always been a UX nightmare.
Which isn't an issue for APIs accessed over HTTPS, many of which are "public" yet use client certificates for authentication.
Itw like theu gave up halfway when inplementing it. Wtf
Wouldn't you just enrol a new certificate from the new device?
Well, with TLS client certs, each request is essentially similar to a new login, request, logout, so "logout" doesn't really make sense, unless you want to it to mean "stop using the cert temporarily"? Perhaps the UX for certs should be more like "this site wants you to login", you press login in the browser UI, then all future requests are signed with your cert, until you click logout in the browser UI and then they aren't signed any more.
Indeed, it is very sad; they could be a great auth standard if only the client UX was acceptable.
The fact that http/1 downgrading is so prevalent and unlikely to change in the short term is the point.
Inconsistencies between HTTP/1.1 and HTTP/2 allow for these behaviours but you still need to inject these malicious prefixes in to a user's request flow, right?
Can you actually set the Content-Length header from a browser on a HTTP/2 request? And isn't the suffix just ignored by the final remote (or does this depend on HTTP/1.1 pipelining?)
This attack does not require a MITM - the attacker would use a tool like Burp Suite to issue the (technically RFC-violating) HTTP request. The prefix injection happens because front-end places the attacker's request and the victim's request on the same HTTP/1.1 connection to the back-end, as shown in this diagram: https://portswigger.net/cms/images/9c/c1/4c32-article-http2-...
> isn't the suffix just ignored by the final remote The back-end treats the suffix as the start of the next request, due to TCP buffering. The vast majority of servers have this kind of accidental pipelining support thanks to TCP.
I would guess a huge percentage of attacks are made possible because of protocol or algorithm downgrading. I wonder if built in downgrading abilities into protocols is a security smell.
If, instead of forwarding a direct translation of the headers, the front ends calculated the relevant requests and sent that, there wouldn't have been any HTTP header attacks (it would have been much slower though).
I'm also confused at their usage of the word "prefix". Did they mean "suffix"? Or does "prefix" have a specific meaning in this case?
This means you are making other clients get responses for their request prefixed by your smuggled second request.
[1] https://docs.aws.amazon.com/elasticloadbalancing/latest/appl...