HTTP/3: Fast and Secure, but Complex

All right.

Let's talk HTTP 3.

I'm sure, by now, most of you have heard about this new protocol and you might've felt, how can I apply it in my own projects?

You might also wonder what are its security characteristics, and how do those compare to what I already know.

Well, there is some good news, as you can see in the diagram, HTTP 3 is really very similar to HTTP/2.

It has almost the exact same features.

Additionally, there's a thing on top that I called the HTTP semantics over the things like the methods, headers, cookies, cross origin resource sharing, and all the other fun stuff that is of course also still in H3.

Additionally H3 also uses TLS transport layer security protocol to make sure it's always fully encrypted on the wire.

We see in the upper layers, things are still very similar.

It is lower than that that things have changed, as H3 has swapped out the TCP for something new called QUIC.

And QUIC is indeed quite different from TCP.

So different in fact, I've already tried to run H 2 on top of QUIC.

Things simply broke.

So we had to make a few key changes to H 2 to make it compatible with QUIC.

And this slightly modified version is what is now known as HTTP/3.

Now QUIC itself is a relatively complex protocol and the reasons why it exists and its performance benefits and all that are out of scope for today, because I want to focus on some of the security characteristics.

Luckily for that, we only need to know three things here.

The first one being that QUIC runs over UDP.

The second one being that QUIC deeply integrates the TLS protocol.

And three,that QUIC introduces a completely new feature called connection migration.

Let's look at these three things in a bit more detail.

First of all, QUIC runs on top of UDP, not as you might've heard because of performance reasons, but rather to make it easier to deploy on the internet.

You see conceptually we could have made QUIC run directly on top of the IP protocol as well.

The problem is that most devices on the internet only expect to see two things on top of IP, which is TCP and UDP.

Having a third one next to that would require updating quite a few of those devices, which might take many years in practice.

Luckily, UDP is a bare bones and very flexible protocol.

You can build pretty much whatever you want on top.

So that's what they did in QUIC.

They took the features we know and love from TCP things like reliability, the connection handshake, congestion control, and much more, and re-implemented it on top of UDP mainly to hopefully make it easier to actually deploy the new protocol.

Now, the fact that most devices support UDP and know what it is doesn't necessarily mean they also allow it on their networks.

As we'll see later, a lot of firewalls, indeed, block UDP, and we'll have to work around that.

The second thing is that QUIC, deeply integrates with TLS.

TCP does not, it sees it as a completely different protocol and this leads to some inefficiencies.

For example, in the handshake, you first need to do the TCP connections set up with the same SYN-ACK handshake.

Then you have to do the TLS session set up and only then can you start doing HTTP stuff.

With QUIC because we have integrated the transport and cryptographic mechanisms into a single thing, we can do all of that in the same round trip, saving quite a bit on performance.

And it gets even better.

This is only the first time that the QUIC connection is made to a new server.

Doing the first connection it can already negotiate encryption parameters for the next connection after that.

And so that can actually start sending HTTP/3 requests from the very first round trip and do that in a fully encrypted manner.

This is a powerful new feature of QUIC that we call 0-RTT.

QUIC's usage of TLS is good for performance, but of course also for security, because TCP uses it as a completely separate thing on top.

TLS, typically only encrypts things like the HTTP payload.

With QUIC we can now also encrypt a lot of the transport level metadata.

Things like packet numbers and acknowledgements that are normally plaintext visible and changeable with TCP are now fully encrypted with QUIC.

This should prevent some classes of attacks and also make it more difficult to track people across the net.

But of course, again, makes QUIC, a bit more difficult to, for example, firewall.

The third feature is called connection migration.

And this is something to solve the often called parking lot problem.

The idea there is that you are now probably in a building connected to wifi.

But soon you might go outside with your mobile device out of the range of the wifi, you will automatically reconnect to a cellular, say 4g, network.

Doing that will change your device's IP.

This in turn leads to all of your existing TCP connections to fail because TCP uses the IP as one of the unique identifiers for each connection.

So we have to reestablish all of the connections-TCP and TLS session again, taking quite a bit of time before we can continue.

QUIC trues to solve this problem in a relatively simple way.

It says, how about I just assign a unique number, a connection ID, with each individual connection.

And I send this connection ID in each individual packet header.

And so when the client changes networks and changes IP, this unique number stays the same and the server knows 'oh, this is actually the same connection I had before, I can just keep on using it without having to reset".

That all sounds great until you start thinking about it.

What if a very large actor, let's say something like a government who has broad view over many different networks sees this kind of traffic?

They could use this connection ID to start tracking users geographically across the different networks.

Terrible idea for privacy.

And so QUIC is of course, quite a bit smarter than this.

It will start using a single connection ID at the start of the connection, during the handshake.

But once the encrypted is connection is set up, it will then have the client and server exchange new connection IDs that conceptually map to the same connection.

And so when the client changes networks and changes IPs, it also changes this connection ID to one of the previously discussed values.

This way, the server knows, "oh, it's actually the same connection.

I can keep on reusing it", but anyone in between, any observer, does not.

In QUIC this is called linkability prevention, and it is a very strong privacy feature.

So we see that QUIC, indeed changes quite a bit from TCP.

And these are just a few of the examples.

Now this has a big impact on things like, I've mentioned them multiple times, firewalls.

How can we change them to make QUIC, um, uh, to make them QUIC compatible?

Now, the thing is that QUIC runs on top of UDP is a big problem.

Because firewalls historically don't really like UDP because it's often used in all kinds of attacks.

One of those attacks is called an amplification attack.

And the idea is that the attacker is going to send a request over UDP, to the server, hoping to get a rather large response back.

The thing is the attacker is going to pretend there's someone else there to fix it.

They do this, using a technique called IP spoofing, where they change the source IP of the request to that of their intended target.

The server does not notice, and it happily sends the large response towards the victim.

If you do this multiple times, many attackers on many servers, you can end up overloading the victims network.

The key thing here is that the attacker only needs a little bit of bandwidth to get much more output from the server.

Hence it being an amplification attack.

And this is one of the reasons why protocols like TCP and also QUIC use a handshake.

Because this allows them to make sure that there is no IP spoofing going on before actually sending application layer data,.

While it should be relatively secure to allow QUIC on the firewall, but how do we do this?

Well, one way to do this would be to open up a network port.

Now, HTTP3 much like a HTTP2 typically uses port 443.

Now I say typically, because it can use any port you like.

This is because browsers typically won't immediately try a QUIC connection to a new server they haven't seen before, mainly because they, most servers simply do not support QUIC yet.

Everybody get a lot of timeouts on waiting.

Instead what they're going to do is first try an H one or H two connection.

If that works, those servers can send back what is called an alt-svc header.

The stands for alternative services and allows the server to indicate, Hey, you're currently loading this content over HTTP 2, but it's also available over HTTP 3 on this specific port.

So you see, you could put in pretty much any port would like there, but of course you need to make sure it's a port that is open on the firewall that your deployment is using.

Now opening a full port, of course still allows attackers to try and put any type of UDP traffic to there.

What we really want is to only allow things that are valid QUIC.

To do that we need to detect if a package is indeed QUIC or not, and this becomes difficult because like I said, QUIC is really quite encrypted, even at the transport layer.

It really isn't much to look at inside of the QUIC packet header.

It's really only the connection ID, which like I said is pretty much a random number.

Luckily, this is only one of the types of packets that QUIC uses.

It's called a one RTT packet and it uses a short packet header, hopefully for obvious reasons.

This of course implies that there were also other packet types and also long packet headers.

And that's indeed true.

During the handshake QUIC uses other packet types, called initials, handshake packets, and of course 0-RTT packets.

And those all have long packet headers with lot more information in them.

For example of such a long header, it has a QUIC version field that the firewall might say I'm only letting through QUIC packets with versions, I support.

Other things that are possible, for example, the first packet that the client sends its initial, contains the TLS client's hello.

In there is all kinds of juicy metadata, like the server name indication, which contains the hosts or the domain that the client is trying to connect to.

And the firewall can parse this and see, is this really a server that I expect to find behind myself or not, and use that to make some decisions.

Well, we see that based on these long headers during the connection setup, the firewall can decide, is this something I will allow or not?

If they do all they have to do is remember the connection ID or disconnection.

Then later when QUIC switches to the one RTT packets with the short packet headers, which it does for efficiency reasons after the handshake, the firewall only needs to match the connection ID, all it knows is allowed for current connections.

This works quite well until it's breaks completely, when the client does connection migration.

Because as we've said, when the client changes networks, they also change the connection ID for privacy reasons.

And so at this point, the firewall has no way of knowing if these one RTT packets are in fact from a recently migrated connection, or if they are from an attacker trying to do some kind of nefarious things.

At this point, the firewall has several options.

They can just drop the unknown one RTT packets, basically disabling connection migration, forcing a client to reconnect.

This is kind of a performance security trade-off.

It can also do this less aggressively by saying I only do that when I suspect I am under attack.

So it takes little bit more risk most of the time, but it can shut things down when it thinks things are getting hairy.

You can also think of more complex setups.

Or for example, we ask the server to only generate a particular type of connection IDs.

In my metaphor, for example, only purple connection IDs.

If the firewall then is trained to say, okay, purple is good.

Other colors are bad.

Then it is possible to accept connection migration.

At least until the attackers find out that purple is good.

And then we of course need to do more advanced stuff like encrypting the connection IDs themselves as well.

And then it gets tricky and this is something we're actually still figuring out.

This is not yet standardized, but it is something we're working on because it's also needed for, for example, load balancing in a QUIC deployment.

There is something coming, but it's not done yet.

And that is true for many other security based features in QUIC as well.

Therefore, it's not really a surprise that most of the firewall vendors currently advise you to simply block QUIC, not allow QUIC or UDP on the networks, because it will automatically fall back to HTTP/2 or HTTP/1 over TCP anyway, so nothing will really break.

And I expect this to take quite a few, uh, quite a bit of time, even a few years until firewalls get updated to properly support QUIC.

The firewalls are, of course only one thing you need in a modern network setup.

Another thing you really want is the ability to detect and also mitigate denial of service attacks.

One example of such an attack that is often seen as called the SYN flood.

The idea is that the attacker will open a new connection by sending a TCP SYN packet or the equivalent QUIC initial as we've seen to the server.

In response, the server has to use a few resources, reserve some memory, or do some TLS calculations to support a new connection.

It then sends back a reply to the attacker who simply drops it, abandons this connection, and instead tries to initiate yet another new one causing the server to reserve even more resources.

If you do this fast enough, and in large enough numbers you will end up consuming the entire resource pool of the server, causing real connections to simply fail.

Now this exists for TCP and we know how to solve it.

And this mitigation has been implemented in QUIC as well.

In HTTP it's called a SYN cookie and in QUIC, it's called the retry token.

The idea is that instead of reserving some states, some resources on the server, we're instead going to put that state inside of a token and send it back to the client.

The client then has to initiate a new connection containing this token from the server.

Only those connections actually get through because they are legitimate.

The other ones can be dropped.

That's a pretty strong mechanism to deal with this, but you don't want to enable this all the time because of course it adds an additional round trip to every connection set up you do.

And this is again, one of those mitigations you only want to enable when you think you are under attack.

A second type of a denial of service attack is one that we've already mentioned, which is the amplification attack.

Well, you might be wondering, you might be confused because earlier I said that the fact that QUIC uses a handshake protects us against this UDP attack.

And it's true until you realize that for the 0-RTT feature, we actually don't wait for this handshake to complete before sending an HTTP request and expect a response.

That's the whole point of the 0-RTT feature.

And this is not really something QUIC can solve.

There is no real solution to this, but we have to instead mitigate it.

And the option that QUIC has chosen for this is to say, okay, you can amplify, but only up to three times, what you send me.

That the client sends you thre pockets, the server could only reply with nine packs.

Anything left over will have to wait until you get something back from the client.

Proving that it's a real one and not an IP spoofing attacker and continue the connection.

But this is not a perfect solution, but hopefully it does enough to discourage people from using QUIC servers as amplification vectors.

And the 0-RTT feature again, quite interesting for performance.

It has other security related problems as well.

As we've seen it is fully encrypted from the start.

So attackers cannot read it.

They cannot change it.

What they can do is copy it and then send all those copies back to the server.

Now withthe 0-RTT request that is something permanent, like say add a row to a database or very badly transfer money from a bank account.

And of course, this might have a very big impact.

The thing is, this is something QUIC cannot solve at that level.

It doesn't really know what impact a request will have.

There are many different things you might do.

For example, the CloudFlare deployment, they say we only accept GET requests without query parameters in our 0-RTT requests because we expect them to not permanently change state, but of course it depends on your application and how you've programmed the endpoint, if that's actually true and not all deployments will be this strict in what they allow in 0-RTT.

And so this means that as even as an application developer, you need to be aware of the possibility of a replay attack.

And be sure that your endpoints are secure against it.

And so we have come full circle.

We started on top with HTTP/3.

We've gone all the way down into QUIC, and now we've surfaced back up again because some of those QUIC layer abstractions and features start to leak back up and we need to understand them to be perfectly secure at the higher layers as well.

With that it's time for a conclusion.

We've seen that indeed 3H is very similar to H2 at the higher layers.

It's mainly at the lower layer for QUIC where things start to get different and indeed quite complex.

To do a proper QUIC deployment will be difficult not in the least, because a lot of the vendors will take quite some time to update their software and hardware to properly secure, properly support the new protocol.

Luckily QUIC provides quite a few built-in features who help with some of the most prolific attacks, but still it is necessary for even application layer developers to be aware of some of this, things like to replay attacks, to make sure they are fully protected against everything that can go wrong.

And this was of course, a very fast whirlwind tour of just 20 minutes.

There is much more we can say, even if it's just about QUIC security characteristics.

If you would like to know more, please follow me on Twitter.

Watch some of my previous content and ask me any question you'd like.

And with that I would say in case I don't see you.

Good afternoon.

Good evening.

And good night.

Fast and Secure, but Complex

Robin Marx [email protected]

HTTP/3 = HTTP/2 adjusted for QUIC

Image of the HTTP/3 Technology Stack.

QUIC = TCP + TLS combined over UDP

Image of the same HTTP/3 Technology Stack, with a big arrow pointing to the UDP Port Numbers section.

QUIC = TCP + TLS combined over UDP

Image of the same HTTP/3 Technology Stack, with a big arrow pointing to the TLS section.

QUIC = TCP + TLS combined over UDP

Image of the same HTTP/3 Technology Stack, with a big arrow pointing to the QUIC section.

1. QUIC runs on top of UDP

Not because of performance, but deployability

supported != allowed

Image of the same HTTP/3 Technology Stack, with a big arrow pointing to the UDP section.

2. QUIC integrates TLS 1.3

Combined Transport and Cryptographic handshake

Two representations of the traffic between client and server, one for TCP, TLS 1.3 and HTTP/2, one for QUIC, TLS 1.3 and HTTP/3. Demonstrates that the process is quicker with the newer stack, as the transport and cryptographic handshakes are combined for it.

2. QUIC integrates TLS 1.3

Combined Transport and Cryptographic handshake

Send encrypted HTTP/3 request from the very start

Image above of client/server interaction with a third representation for 0-RTT combined with QUIC, TLS 1.3 and HTTP/3. It shows all three handshakes taking place simultaneously.

2. QUIC integrates TLS 1.3

Anything can read and change these TCP fields

Red arrow points to a representation of the TCP Packer header. The HTTP data of the TLS payload is marked as encrypted. A second image shows much more of the data is encrypted with TLS 1.3

3. QUIC supports Connection Migration

The Parking Lot problem

Moving from WiFi to 4G breaks existing TCP connections

Diagram shows the client initially communicating with server over wifi. Text reads "TCP handshake from WiFi IP starts connection". Then the communication switches to 4G. Text reads "TCP data packet from unknown IP... no idea what to do with this"

3. QUIC supports Connection Migration

Diagram adds a new representation for QUIC's handling of the parking lot problem. Again, client is initially communicating with server over wifi. Text reads "Let's use this QUIC Connection ID (CID) for now". Communication again switches to 4G. Text this time reads "The network is different, but the CID is the same, so it's the same connection"

3. QUIC supports Connection Migration

Each Connection has multiple Connection IDs = Linkability Prevention

Diagram shows the same QUIC communication between client and server. Text now reads "Let's use this Connection D (CID) for now: A, In the future, you can also use these CIDs: B, C. (this is sent encrypted, so observers don't know about the new CIDs) I know A is actually B (but observers don't!)"

Firewalling and Connection Tracking

Firewalls don’t like UDP

One of many reasons: UDP amplification attacks

Diagram showing an attacker spoofing the source IP on the request, then the server sends a lot of response data to the victim

Firewalling and Connection Tracking

Only want to allow QUIC-over-UDP

1. Port-based: 443

Alternative Services

Image shows browser developer tools, and highlights response headers

Firewalling and Connection Tracking

Only want to allow QUIC-over-UDP

encrypted encrypted
  1. Port-based: 443
  2. Packet headers

Packet headers showing the encrypted parts of the packet.

Combined Transport + Cryptographic handshake

Diagram shows the process of the combined handshake

Firewalling and Connection Tracking

Only want to allow QUIC-over-UDP

  1. Port-based: 443
  2. Long packet headers and payloads

TLS 1.3 ClientHello (e.g., Server Name Indication)

Firewalling and Connection Tracking

  1. Look for long QUIC Initial/Handshake packets on supported ports
  2. Store their Connection IDs
  3. Only allow short 1-RTT packets with a previously seen Connection ID

Firewalling and Connection Tracking

  1. Look for long QUIC Initial/Handshake packets on supported ports
  2. Store their Connection IDs
  3. Only allow short 1-RTT packets with a previously seen Connection ID
  4. Break after connection migration...

Firewalling and Connection Tracking

No observer knows about the new Connection IDs

Not even the firewall

Repeat of earlier diagram showing connection IDs

Firewalling and Connection Tracking

How to deal with connection migration:

  1. Don’t
  2. Only allow when not “under attack”
  3. Let server generate only Connection IDs that the firewall knows are “real” / safe

Firewalling and Connection Tracking

Many firewalls currently just advise to block QUIC

Denial of Service Handling

Attack 1: SYN / Initial flood

  1. Attacker sends SYN / Initial to target
  2. Target reserves/uses some resources to handle connection
  3. Attacker never continues connection, just sends more SYNs / Initials
  4. Server has no resources left for real connections

Denial of Service Handling

Attack 1: SYN / Initial flood

  • SYN cookies / RETRY tokens
  • Server stores state in token, Client needs to echo token to be accepted

Denial of Service Handling

Attack 2: 0-RTT Amplification Attack

Repeat of earlier diagram showing source IP spoofing attack.

Denial of Service Handling

Attack 2: 0-RTT Amplification Attack

Repeat of earlier diagram showing source IP spoofing attack, to the right repeat of the diagram of the simultaneous handshakes with 0-RTT, QUIC, TLS 1.3 and HTTP/3.

Denial of Service Handling

Attack 2: 0-RTT Amplification Attack

Mitigation: 3X amplification limit

Repeat of the source IP spoofing attack, with the text "Too much, do not send before we’re sure it’s a real client!""

Replay Attacks

  • Fully encrypted Attacker cannot read nor change
  • However, they can COPY + PASTE

repeat of the diagram of the simultaneous handshakes with 0-RTT, QUIC, TLS 1.3 and HTTP/3

Replay Attacks

  • Fully encrypted Attacker cannot read nor change
  • However, they can COPY + PASTE

repeat of the diagram of the simultaneous handshakes with 0-RTT, QUIC, TLS 1.3 and HTTP/3

Specifically, only GET requests with no query parameters are answered over 0-RTT.


1. HTTP/3 is similar to HTTP/2

(your skills are still useful!)

2. New challenges for Network Operators

(will take a while for vendors to update)

3. QUIC tries to help where possible

(but still tons of complexity)

Would you like to know more?

  • Deep Packet Inspection / TLS interception AEAD usage
  • Header protection
  • Load balancing
  • Optimistic ACK attacks
  • RST/FIN attacks and timeouts
  • Network observability challenges
  • 0-RTT/Resumption STEK
  • ECHO

(this is a deep rabbit hole)