Cache Poisoned DoS Attack: Shutdown any CDN Website with One HTTP Request
cpdos.org
cpdos.org
""" Amazon Web Services (AWS). We reported this issue to the AWSSecurity team. They confirmed the vulnerabilities on CloudFront. The AWS-Security team stopped caching error pages with the status code 400 Bad Request by default. However, they took over three months to fix our CPDoS reportings. Unfortunately, the overall disclosure process was characterized by a one-way communication. We periodically asked for the current state, without getting much information back from the AWS-Security team. They never contacted us to keep us up to date with the current process.
"""
[0] - https://cpdos.org/paper/Your_Cache_Has_Fallen__Cache_Poisone...
So, if a client sends a 20kb `X-Oversized-Header`, when the server responds with a 400 -- it might be conceivable that it should include `Vary: X-Oversized-Header`.
Is that "really" the right fix? Probably not. But the HTTP RFCs provide `Vary` for exact this kind of reason within HTTP caching: the origin is varying its response based on a subset of headers.
Like, in this case, a 400 is really the origin is saying that it's not even sending you a representation of the resource you requested, because you didn't make a request that can be parsed as any particular resource. It's a Left RequestFormatError instead of a Right CachableResourceRepresentation.
And, annoyingly, the codes don't have any line-up with which layer of the result went bad. 4XX is "client error", sure; but 404 isn't really an "error" at all, but an eminently cacheable representation of the representation of the non-existence of a resource.
It'd be neat to see the HTTP codes rearranged into layers by what caching semantics they require of the implementing UA, such that UAs could just attach behavior to status-code ranges. Maybe in HTTP/4?
4xx (Client Error): The request contains bad syntax or cannot be fulfilled
I would state that, since this is client-specific, all 4xx responses should not be cached at some proxy/CDN, since he is not the client. And even the client should not cache a 404. A ressource could just be created the next moment.However, effectively we agree with derefr, saying that HTTP status code design did not have this pecularity of cachable vs. non-cachable errors in mind. This is definetly a shortcoming.
If messaging stuff was factored out or moved to be the primary protocol (with content delivery implemented on top) a lot of the issues we have with security, latency and caching would just disappear. And no, WebSockets (the way they work right now) are not going to solve this. Neither will QUIC aka HTTP3.
Basically the problem stems from the cache implementation not being DRY.
This is so f*ing scary. Who in their right mind invents such crazy tricks that absolutely circumvent all we know about web API security, implements them in frameworks, and leaves them enabled by default?
Seems more reasonable than trying circumvent a legitimate restriction. Not every block is an adversary which needs defeating.
So if you circumvent my original blocks, as an administrator, I will use my home-field advantage to just start targeting your individual users and remote resources. Suddenly your users can't even get on Google. Then they'll have to come to me for "the talk."
So the decision quickly becomes a) circumvent enough firewalls that you blacklist yourself or b) get all your users blacklisted for using your service.
"That is managed by X department, we can put in a change request and we'll probably get a timeline for the fix at the beginning of next quarter. We'll re-evaluate if you can help with our issue once they get back to us."
... and you'll never hear from them again.
a) don't support that platform and lose out on that audience
b) embed your own HTTP library with your client
c) do everything with POST and always return HTTP 200, because every http library supports that.
Or I guess
d) use C, but with headers to pretend that you're seeing an action other than POST.
It's just an information for the routing system to dispatch a different method on a controller. You could implement a way to use a query string to pass this override too. An API framework does not have to be RESTful. It could work with POST requests only and simulate deletes with something like ?method=delete.
Edit: I saw a GitHub comment that actually says it is possible to use query string to override the method in Play 1.
https://github.com/playframework/play1/issues/1300#issuecomm...
> We've found that although the header is disabled, its still possible to use X-HTTP-Method-Override by passing as a query string
But more to your point, one consideration is CORS performance. If you want to allow non "simple" requests and still avoid CORS preflight requests, you pretty much have to tunnel everything through POSTs, and handle cross-origin security on the server side. Yes this bad for security, but the spec folks haven't given us a performant alternative that I'm aware of.
We're talking about caching proxies run by sites. These terminate SSL and work in the clear, because their whole job is to take the response one user got and efficiently distribute it to all the other users who want the same thing.
It's clearly happened, though; you can search for vulnerabilities tied to it. I just don't think it's all that common. Someone got a bounty from Google for a cloud.google.com service that used an internal proxy where XMO got them control over a PUT.
Obviously, things like the request method, path and Host header matter a lot. Perhaps if you're A/B testing, the A/B cookie would make sense as part of the cache key too.
This seems like a simple misconfiguration at a very critical location. But an 'exploit' that warrants it's own domain? Hardly. This is a promotion for the authors and their upcoming presentation. It's a very nice gotcha.
As for whether it requires it's on domain, domains are cheap and publicity gets people to pay attention to problems and fix them.
Caching a 5xx kinda makes sense I guess, but a 4xx client error? That's nuts.
For example, every browser requests /favicon.ico by convention. If you don't have one, you wouldn't want every single request reaching your backend.
It's not an ideal design but I wouldn't call it 'horrible'.
Technically it stands for "common gateway interface" but that doesn't come up very much.
CDNs already parse headers, and the CDN developers have the knowledge and know how to correctly constrain header values.
There are 100's of different HTTP Header attacks e.g. I like this one that returns a response from a different persons HTTP response into your HTTP response: https://portswigger.net/blog/http-desync-attacks-request-smu...
I'm clearly going to have to spend another day staring at the cloudfront documentation.
Only thing I can think of is too many cache entries with the same cache values for the case where requests with different headers produce the same API response.
Which could lead to a large cache.
It's also a good idea to whitelist headers and query params your app uses.
Better put this sounds like an attack that only works on very poorly configured CDNs.
Anyway, if you're an attacker it's easier to DDOS against urls that will 404 as those must go back origin anyway typically and likely won't be safe to cache for long even if the cdn is config'd to do so for 4xx responses. To protect against that your cdn provider probably has some sort of ddos protection feature as well though.
Of course you should be monitoring for and logging these error responses from the origin and fixing them as soon as possible. The cdn response is just to provide cover. Again, that is if you need or want it. If you want to expose errors to the public go right ahead nobody is going to stop you.
404s could be cached, I think, but it's a risky one.
Real-life example I'm familiar with: Content being served from a busy NAS that buckled under load. You'll have some requests time out with 504s, some return 500s, some that make the files appear missing and so you get 404s. I know, braindead design that shouldn't happen, but it does happen.
If you're caching 400 (which many people have noted is against the RFC and can be troublesome) then you need to make sure you're only caching it for a matching request. If I send a poisoned header and you cache a 400 for my full request including that poisoned header, then that's one thing. If I send a poisoned header and you cache a 400 for all accesses to that URI whether or not they include the header that caused it to be a bad request, that's vulnerable to this BS.
“One of the main reasons for HHO and HMC CPDoS attacks lies in the fact that a vulnerable cache illicitly stores responses containing error codes such as 400 Bad Request by default. This is not allowed according to the HTTP standard.“
I would expect web servers to just discard weird headers and serve the regular page. And, at the same time, I would also expect CDN to not cache 400 bad requests or 500 server errors.