BEST DEAL

Sunday 11 December 2016

HTTP response splitting (advance part)

HTTP Response Splitting with Header Overflow

This issue clearly demonstrates how HTTP Response Splitting differs from CRLF injection. Normally 99 HTTP Response Splitting vulnerabilities are caused by CRLF injection, while this one abused a different flaw which did not involve injecting CR or LF.
It happened right after the last episode. Their monkey-patch approach finally paid off - inputs on the same page still lacked proper validations, which led to the discovery of this issue. When crafting a long enough value in one of the input parameters, the HTML source was directly shown.
What happened after crafting long enough input
This unusual scene was due to the missing Content-Type. In fact, half of the header fields that were supposed to be sent along the response were split in half. The server seemed to only handle the first 8kB of the response header, and then it sensed the response was probably incomplete or broken thus returned a HTTP 500 (Internal Server Error). The interesting thing is the truncated part got returned while reissuing the same request (let's call it second response). As we can control what value to be reflected in the response header, we can inject a string so that its length plus the preceding header fields' slightly exceeds 8kB, then suddenly the last copule of characters of our injected string becomes the beginning of the next response header.
Weird response splitting behavior
In this case, after injecting something like AAA[...]AAA:foobar in report_user_idAAA:foobar becomes a header field with name AAA and value foobar of the second response. Apparently we can replace it with a standardized header field (e.g. Access-Control-Request-Origin) to do real harms.
However, it was not easy to decide how many paddings should be added to fill up just 8k because sometimes the response header may vary. We need a deterministic way to have it truncated to wherever we want. A quick fuzzing reveals that the server consumes empty spaces (U+0020) in the header field name. In other words, we can use empty spaces as padding. So to sum up:
  1. Prepend a loads of empty spaces (slightly less than 8kB, otherwise server rejects the request with HTTP 414 (Request-URI Too Long)) to an header field for attack
  2. Put the crafted value into one of the input parameters
  3. Make victims visit the page twice, because only the second time actually triggers the payload
One final challenge was browsers encode empty space with %20. The problem is it costs 3 bytes instead of 1, and that makes our intended payload bloated (remember request URI needs to be less than 8kB as stated above). Luckily plus sign (+) is also accepted as encoded empty space.
To be honest I have no idea what the root cause of this was. This might be a buffer implementation bug, but I can't tell for sure. Anyway, this weird behavior was fixed but there's still no validation on inputs whatsoever.

Final attack payload

https://twitter.com/i/safety/report_story?next_view=report_story_start&source=reporttweet&reported_user_id=1&reporter_user_id=1&is_media=true&is_promoted=true&reported_tweet_id=+++[...]+++set-cookie:foobar
And the full response header:
HTTP/1.1 200 OK  
set-cookie: foobar  
x-response-time: 229  
x-connection-hash: 4f7c08fce85fe4801b3b24f05764fc84  
x-content-type-options: nosniff  
x-frame-options: SAMEORIGIN  
x-transaction: f9709a489ba395b5  
x-twitter-response-tags: BouncerCompliant  
x-xss-protection: 1; mode=block  

References


Denial of Service with Cookie Bomb

The above discovery was actually a byproduct of this one. Since the inputs are reflected in Set-Cookie, we can control the value of these particular cookies as well as their attributes (e.g DomainExpires). Previously this would mean a CSRF protection bypass (using comma (,) as cookie separator fwiw), but it's not the case anymore as Twitter now compares the token to the one in session. Still, attackers can exploit it with Cookie Bomb.
Cookie Bomb is a term introduced by Egor Homakov. The attack itself is nothing new but seldom people actually look into it. The main idea of it is that servers reject requests with an exceptionally large header. The exactly figure may vary on different servers but generally the request header can't be greater than 8kB. By abusing this feature, attackers can force victims into accepting a bunch of large cookies. What it does is that all requests to the corresponding website from victims will then contain a very large cookie, causing the server to reject any request from the victims (a.k.a. Denial of Service).
The attack can be further improved by manipulating different cookie attributes:
  • Domain: instead of just example.com.example.com will make cookies applied to all the sub-domains, paralyzing the entire services
  • Expires: by default a cookie will be destroyed after the browser restarts unless otherwise specified, conversely attackers can take advantage of it to lock victims out forever until they manually delete the cookies
  • Path: setting it to root path (/) can maximize the number of affected pages

Attack in Action

Attack payload (without encoding for readablility):
https://twitter.com/i/safety/report_story?next_view=report_story_start&reported_user_id=000[...]000;Expires=Wed, 02 Apr 2025 12:21:55 GMT;Path=/;Domain=.twitter.com&reporter_user_id=1&is_media=true&is_promoted=true&reported_tweet_id=000[...]000;Expires=Wed, 02 Apr 2025 12:21:55 GMT;Path=/;Domain=.twitter.com
Response header:
HTTP/1.1 200 OK  
[...]
set-cookie: reported_user_id=000[...]000;Expires=Wed, 02 Apr 2025 12:21:55 GMT;Path=/;Domain=.twitter.com  
set-cookie: reported_tweet_id=000[...]000;Expires=Wed, 02 Apr 2025 12:21:55 GMT;Path=/;Domain=.twitter.com  
[...]
You may wonder why to use two cookies rather than just one. That's because one single cookie can only contain at most 4kB of data.
Attack in action
After victims visit the page, our bomb is silently planted. No more twitting for them.

No comments:

Post a Comment