quicwg / base-drafts

Internet-Drafts that make up the base QUIC specification
https://quicwg.org
1.62k stars 204 forks source link

Request forgery attacks #3995

Closed martinthomson closed 3 years ago

martinthomson commented 3 years ago

What is server-side request forgery?

Server-side request forgery (SSRF) is an attack technique where a malicious server takes advantage of a client that is in a privileged location of the network to attack unprotected servers from the client location.

This relies on there being a server that has no protections at all. This server takes actions without authenticating the requester. That is, the server assumes that because the request reached it, the request was authorized. These servers are often deployed with the assumption that access to the network is tightly controlled and so rely on that access control alone. Historically, these servers were very common in enterprise networks, but that is no longer best practice. It is. however, still common to find services deployed on loopback addresses that fail to properly authenticate requests.

A simple example exploits bugs in URL parsing of clients to embed messages. The attacker asks the client to navigate to http://10.0.0.1¤\r\nEXPLOIT BUG\r\n/. The client connects to the IP address, ignoring the junk, but includes all of the junk in the TLS Server Name Indication (SNI) field it sends. As a result, data that is controlled by the attacker is sent in the clear to the vulnerable server. For this style of attack, the trick is finding a server that will read past the TLS junk that precedes the instruction to find and execute the instruction. And don't laugh, it wasn't that hard to find servers that did this (that paper describes a form of SSRF as well, but the victim was a middlebox).

With TLS (and presumably DTLS), is also possible to use session IDs or session tickets to the same end, without relying on bugs in the client.

SSRF often doesn't produce any feedback directly to the attacker, but it can be used to establish control over the vulnerable host, which might lead to opening communication channels.

How QUIC enables SSRF

From the protocol side, the key enablers for SSRF are mechanisms that allow an endpoint to control the destination address and part of the (unencrypted) content of messages sent by a peer. So what opportunities are there for SSRF in QUIC?

With some exceptions, QUIC offers few options for an adversary to send to a destination address that does not understand QUIC.

As far as controlling the content of messages goes, Initial protection using the client-chosen Destination Connection ID is pretty effective, but the same does not apply to other messages. The fact that this is an encrypted protocol doesn't help as much as you would like, thanks to use of XOR-based stream ciphers.

That means we need to examine the cases where destinations can be controlled, as those are where QUIC is most vulnerable.

Controlling packet destination

The destination for the very first Initial packets from a client is open to server control. An attacker can, through control over the IP addresses returned by DNS or Alt-Svc, affect where connection attempts go.

Once a connection is established, address validation is used to ensure that a target address is willing to communicate. However, address validation requires that some packets be sent to an address prior to validating it. As migration is currently only possible for a client, this means we need to consider the potential for clients to mount this style of attack using a server. This requires source address spoofing to fake a connection migration.

As servers are not permitted to initiate migration, preferred address is the only way that a server can cause a client to send any other type of packet to an address that it does not control.

For a completely new connection, we can assume that the victim doesn't support QUIC and does not respond with valid QUIC messages. Therefore, it is safe to assume that the client won't send any other type of message other than Initial in cases other than when it migrates to a preferred address.

Controlling content

For content, the most obvious fields are the Destination Connection ID in every packet and the Token field of Initial packets.

Use of encryption for Initial messages and the randomized destination connection ID ensures that a server controls none of the message plaintext for Initial messages. This means that Initial messages are mostly safe. The exception here is the Token field, which is server controlled.

Encryption doesn't help a great deal. As the attacker has encryption keys and we use ciphers that are effectively stream ciphers, an attacker has a reasonable chance of predicting how encryption will transform a large proportion of packets. An attacker that can control the content of frames and can predict the packet number that is used can therefore provide content that encrypts to ciphertext of its choosing. This relies on being able to predict frame placement, but implementations are fairly consistent and so are expected to be somewhat predictable.

Browsers in particular offer a number of ways for adversaries to control client behavior. Similarly, servers often allow clients to create or alter content in ways that might be suited to this style of exploitation.

Concrete attacks

As altering content seems to be broadly feasible, our primary focus needs to be on those cases where packets are sent to an address that has not been validated.

This leaves three avenues to explore more: preferred address, address validation tokens in Initial packets, and spoofed migrations by a client.

Preferred address

The preferred address is a pretty good SSRF enabler. The entire point is to enable server specify an address, so controlling destination is trivial.

For content, we only have short headers as a client cannot migrate prior to confirming the handshake, after which we only use packets with short headers.

The effect is that a client will send a UDP datagram containing one uncontrolled byte (this is in the range that includes all ASCII alphabet characters) and then up to 20 attacker-controlled bytes to a destination of the attacker's choice.

The remainder of the datagram cannot be directly controlled by the attacker. However, the attacker has encryption keys and can probably predict the packet number. Therefore, if it can control the content of frames, it has an opportunity to affect what appears in the encrypted datagram. Thus we need to consider what might be included in this packet.

If we look at probing frames, the packet likely contains PATH_CHALLENGE, which is explicitly not controlled. Other likely candidates for inclusion in this packet are the other probing frames (PADDING, NEW_CONNECTION_ID, and maybe PATH_RESPONSE), which offer an attacker limited options for control, even if frame composition is attacker controlled. We can also assume here that the attacker is unable to effectively control the encoded packet number, so it would have to work around those bytes.

Frames that carry application data (STREAM or DATAGRAM) are definitely more open to control. In some settings (browsers in particular) the contents of these frames might be controlled by the attacker. Therefore, frames carrying application data offer the attacker the means of indirectly controlling large portions of plaintext. This only depends on being able to predict packet numbers and to know the likely placement of frames in the packet, both of which have to be regarded as easy. That said, if clients refuse to migrate to a preferred address without first validating it, this option will not be useful to an attacker.

Initial tokens

NEW_TOKEN provides a server with some ability to control both destination and content. The attacker can alter DNS records or use Alt-Svc to select a target.

The server has less control over the Initial packet generally, but the token is only limited in length by the size of the packets that carry it, so it provides a lot more space to play with than a connection ID. There is a little more uncontrolled junk (at least 16 bytes) ahead of the token than with preferred address. That junk is not likely to be a major barrier, especially with the extra space on offer. The TLS attacks mentioned above deal with more than twice this amount of junk.

Retry also provides an opportunity as the server selects the connection ID and token. As the server is unable to change the IP address the client uses after a Retry, Retry cannot be used to select a target.

(As an aside for Retry, controlling the connection ID provides control over the Initial encryption keys. In theory, this removes the defense offered by Initial protection as the server can choose connection IDs until encryption produces the content it desires. As the client is required to use the same CRYPTO frames, the attacker only has to iterate through values until it gets what it wants. However, this control is limited to a small number of bytes as the cost increases exponentially with the number of bits that the server wishes to control. Using the Token field is considerably easier.)

Client spoofed migrations

This case is the inverse of SSRF in that it is the client that performs the attack with the goal of using the privileged position of the server. To mount the attack, the client needs to inject its payload into a connection ID and then spoof its source address to match the intended target.

Observable effects

All of these cases can be exploited without observable effect. Packets are sent as part of an existing connection that continues to operate normally, or packets are sent as part of a connection establishment that is performed while another connection remains active (assuming that NEW_TOKEN is used with Alt-Svc and not DNS targeting; DNS targeting could be more obvious). This means that the exploits have no natural pressures that force them to be exposed. The victim might notice if the attacker has to try many times to be successful or the endpoint that is mounting the attack might notice unusual activity, but only if there are measures in place to look for an attack.

Both preferred address and NEW_TOKEN do require that new connections be established, which might limit the rate at which an attacker can retry the attack.

Spoofing addresses is not limited in the same way. If the same connection ID is used, then the attack is limited only by the rate at which the server is willing to use the address (or addresses).

Do we care?

Yes, we should care. Though it takes a horribly insecure configuration to be vulnerable to this sort of attack, that does not mean attacks won't happen or that clients or the IETF won't be held responsible. See also the law of the internet: blame attaches to the last thing that changed.

Browsers are very careful in defending against this style of attack. CORS is a horrible tax that every cross origin request pays for this. That doesn't mean that only browsers need to worry about defenses.

There are a lot of deployments that understand this attack well and have deployed defenses for TCP. These likely include a suite of patterns that can be used to identify and defend against known attacks. The risk with QUIC is that these defenses are less mature or non-existent for UDP.

This analysis assumes that removing any of these features that might be exploited this way is not desirable. It further assumes that adding new mechanisms, particularly those that might degrade performance, is not tenable. These problems might be worth doing something about, but they are still largely the fault of servers that act on unauthenticated instructions. It should not be entirely up to QUIC to address these concerns.

Anything that makes QUIC substantially worse to protect such servers is not worth considering. For instance, control over cleartext could be achieved using the masking technique employed by Web Sockets. Requiring additional round trips for address validation might help narrow the options for attack. However, both would likely adversely affect performance. That doesn't mean doing absolutely nothing, as that would not be responsible.

What can we do?

For NEW_TOKEN, we have a mitigation that seems pretty effective: don't send the token. As the protocol does not depend on successful use of a token, we can simply recommend that clients not include a token in Initial packets when the server address changes. We might allow heuristics so that tokens can be used when the server addresses stays within the same subnet (of unspecified size) or same AS or something along those lines. That should retain most of the value of the mechanism without dramatically increasing exposure. Even if heuristics are used, this would require that the attacker be adjacent to the victim, which should be effective in blunting the attack.

BCP 38 seems like the best countermeasure for attacks that use client address spoofing. Assuming that the attacker isn't in the same network, ingress filtering will easily prevent this attack. Aiming to prevent control over the cleartext of packets is difficult and likely to be ineffective.

Spoofing can be made to look like NAT rebinding. Insisting on address validation prior to validating the address is an undesirable imposition that will affect performance pretty badly relative to just making a new connection. If we assume the use of ingress filtering where there are servers that might be vulnerable, it should not be necessary to require address validation.

For spoofing, we could recommend that implementations not send toward an address that has failed address validation for some period. This will limit the rate at which spoofing might be used.

Preferred address mitigations

Unfortunately, there doesn't appear to be a single crisp thing we can recommend for preferred addresses.

One possible option we have is to prohibit the use of preferred address to elevate privileges. That is, prevent a server from picking a preferred address from a portion of the network that might have different protections. (Does anyone remember Microsoft's concept of Zones from Internet Explorer days?) How we identify this elevation becomes challenging though, and it is not always clear that an apparent "elevation" is truly illegitimate.

We might advise clients to avoid preferred addresses that go from an apparently public IP to a link-local or 1918 address, but that sort of restriction could affect real use cases in ways that we don't want. To give a concrete example, an ISP might operate their network in a private address range and use NAT for traffic that leaves the network. A server that is hosted inside that network might be reachable using a public address (which might be unicast), but it could offer clients a more direct connection using the preferred address mechanism that includes a private address.

Prohibiting use of loopback (127.0.01 or ::1) preferred addresses from non-loopback servers seems like it might be safe, but that too might preemptively exclude valid uses. I don't have any such use case right now, so it seems like this is a reasonable recommendation. People who might eventually discover a use case will need to deal with that recommendation.

You might consider sending a garbage long header packet prior to address validation, but that doesn't really change the dynamics much. A long header adds 5 more bytes ahead of the attacker-controlled connection ID. That might make it marginally harder to exploit, but it's not enough of an improvement to justify recommending it.

We can prohibit the sending of non-probing frames to a preferred address prior to address validation. That's a pretty big change, but I think that it is reasonable given the attack. That reduces the amount of controlled cleartext to the connection ID, not the majority of the packet. Restricting to non-probing frames adds a performance hit if a client is forced to migrate before acting on a preferred address, but that seems like a tolerable loss.

Summary

This seems like a lot. I've certainly written a lot of words. But I think that the attack is not that serious.

The protections we might deploy for two of the three attacks aren't that onerous, and we can do a little bit to mitigate risk for preferred addresses. The important thing here is to ensure that we document the risks. This is one of those areas where building a protocol on top of UDP exposes new operational risks.

I'm proposing that we simply document the risks and add some small mitigations provided that they don't cost much. I'll propose a PR with that proposal.

mikkelfj commented 3 years ago

Interesting.

In the issue you mention loopback but then goes on to use a local router address. I was thinking loopback was on the clients machine.

It is often the case that documentation servers run on localhost to display content, and some applications may be written to either directly use a browser interface, or indirectly via electron, to a locally hosted server.

These type of applications are generally not hardened and therefore comparatively easy to exploit. In the worst case, such applications insist on running on port 80 or some other privileged port, thereby having elevated permissions.

But, how does this new attack differ from a HTTP redirect from a random web server, where part of the url is designed til trigger a vulnerability?

I can see some level of same domain protection for HTTPS, but as far as I am aware, such protections do not apply to redirects. EDIT: Or does CORS cover that? No matter how often I read about CORS, it never sinks in...

kazuho commented 3 years ago

@martinthomson Thank you for raising the issue.

I agree that NEW_TOKEN tokens and SPA are concerning. At the same time, I am not sure how much we have to worry about the encrypted payload being used as the attack vector.

Therefore, frames carrying application data offer the attacker the means of indirectly controlling large portions of plaintext.

1-RTT packet protection key that is being used for encrypting the frames is not controlled by the attacker. The key is derived from the input of both endpoints. I am not sure how practical it would be to assume that a rouge server can calculate what the corresponding plaintext would be for an encrypted text that it wants to use for the attack, when the encryption keys are not obtained until the handshake is performed.

Regarding the amount of effort we should be spending to address this issue, I would point out that SSRF as we know today can induce a browser to send two types of TCP messages: 1) HTTP GET request (or preflight), 2) first flight of TLS over TCP.

I think we can call close the issue if we can make certain that the QUIC packets that the browsers send is as controlled as either of the two patterns.

Encrypted data meets the 2nd requirement. QUIC Initial packets are roughly as random and as binary as Client Hello of TLS over TCP. Encrypted part of QUIC packets are random and as binary as 0-RTT records of TLS over TCP.

Therefore, I think what we need to discuss is limited to NEW_TOKEN tokens and the DCID of SPA.

kazuho commented 3 years ago

PS. My bad, I think I missed the fact that we are using CTR mode for encryption. Yes, the server can control the bits that are sent by the client.

martinthomson commented 3 years ago

@mikkelfj

In the issue you mention loopback but then goes on to use a local router address. I was thinking loopback was on the clients machine.

Yes, loopback is same host. But local router addresses (the example was just an address from a private address range), are also potentially vulnerable.

It is often the case that documentation servers run on localhost to display content, and some applications may be written to either directly use a browser interface, or indirectly via electron, to a locally hosted server.

Yes, but those servers rarely take instructions that have any real consequence, so they don't tend to be exploitable. That's not uniformly true of course.

These type of applications are generally not hardened and therefore comparatively easy to exploit. In the worst case, such applications insist on running on port 80 or some other privileged port, thereby having elevated permissions.

Privileged ports can mean that the process serving that port is privileged, yes. But it doesn't have to mean that. Of course, if it is, then it can potentially do a lot of harm.

But, how does this new attack differ from a HTTP redirect from a random web server, where part of the url is designed til trigger a vulnerability?

One thing with HTTP is that we don't really protect against this sort of thing very well. Mostly because the Internet and Web grew up together and people more or less assumed at first that this sort of thing was OK. So we generally allow attackers to make requests over HTTP. But it's only a subset of requests that are designated as "safe".

Later, as we realized that some of the HTTP stuff was dangerous we added CORS. For those things we believe to be "dangerous", CORS effectively does the same thing that address validation does for QUIC: it checks whether the server is willing to accept a request using a "safe" check (an OPTIONS request which includes minimal attacker controlled content) before it makes any request with any of the "dangerous" content that might be controlled by an attacker.

Of course, we would love not to have to do CORS. It's a horrible waste when most servers don't suffer from these problems to spend a whole round trip negotiating its use.

The CSRF paper mentioned in the pull request is the one that basically started all of this. It's fairly accessible if you are interested in learning more.

mikkelfj commented 3 years ago

@martinthomson thanks for elaborating. On local document servers: their parsers might not be hardened since it is" just local" and bad input, e.g. url > 512 characters might lead to a crash leading to arbitrary code execution. On privileged ports, yes, it is good praxis to not serve requests with privileged permissions.

MikeBishop commented 3 years ago

Bringing this to the issue as well, since it's not directly a comment on the text of the PR. It's perhaps a bit of a nuclear option, but if we're concerned about the peer being able to control the ciphertext by predicting the input plaintext, there seem to be some options for introducing entropy there:

Additionally, an implementation could deliberately break large CRYPTO/STREAM frames into smaller chunks and then randomize the order of frames within the packet. That's not a protocol change, but would make the packet ciphertext unpredictable even given an attacker who can predict what you will send in those frames.

I don't claim to have any specialty in cryptography, so I'd be happy (but also disappointed) to learn why those don't actually keep the attacker from potentially controlling the ciphertext.

martinthomson commented 3 years ago

Adding PATH_CHALLENGE just adds a few unpredictable bytes, it doesn't really address the problem of control. For that, you need something like the masking in websockets.

The problem with something like masking is that it adds a fixed overhead to packets because you need to tell the recipient what the unpredictable stuff was and the size is determined by the number of attempts you might allow (see below). Maybe that only applies to packets that are sent prior to address validation, so maybe you could repurpose one of the long header packet types (0-RTT?) to carry a masked payload in those cases, but it's a massive change. I don't even want to think about making that sort of change right now...

The refragmenting idea is interesting. The analysis supports the view that it is unnecessary for CRYPTO. It might be too much work otherwise as well. Skipping a random offset for STREAM frames might work, but you have to work through all the ways that an adversary might try to game out whatever strategy you adopt. You need to ensure that the number of potential variations is at least the square of the number of attempts that an attacker might have (if you have 100 attempts, that's a minimum of 10,000 variations) or you hit the birthday bound and attacks become feasible in the aggregate. Getting variations that high isn't necessarily that easy. I'm not sure if it is worthwhile chasing that down.

kazuho commented 3 years ago

I tend to agree with what @martinthomson says.

Among those two alternatives, masking (or re-encoding) might be a better solution. But as I stated on the mailing list, that does not need to happen now. We can wait and see if that's necessary, and if it turns out to be, define an extension for that purpose.

MikeBishop commented 3 years ago

Ah, I missed that we're dealing with stream ciphers, so you can't rely on a few unpredictable bytes on the input to scramble the entire output. That does reduce the effectiveness of both ideas.

LPardue commented 3 years ago

There is discussion here and on the list, this is clearly a design issue and passes the bar of raising some legitimate security concern.