Overflow is not an everyday term in Websec. Though I am going to share three interesting stories that are somewhat related to overflow when I was fiddling around Twitter.

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_id, AAA: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

Original report from HackerOne


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 Domain, Expires). 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.

Conclusion

Again, the golden rule is to always validate user inputs, including length. Fortunately this time Twitter fixed it with proper validation on inputs. Moreover, it is also important to know you don't let users to set arbitrary cookie values.


DOM based Cookie Bomb

Cookie API on the DOM is a mess. There's only one property to interact with (i.e. document.cookie). Developers need to spend extra attention to ensure it being used correctly. Here I would like to demonstrate yet another Cookie Bomb issue on Twitter.

Analysis

Twitter used to track hashtags and referers using cookies. This is how they did it:

function d(a) {  
[...]
        var b = document.referrer || "none",
            d = "ev_redir_" + encodeURIComponent(a) + "=" + b + "; path=/";
        document.cookie = d;
[...]
}
[...]
window.location.hash != "" && d(window.location.hash.substr(1).toLowerCase())  

In short, if a visitor comes from other sites, it will record the hashtag from document.hash and document.referrer to a cookie. The culprit is that the cookie value (referrer) is not encoded or validated, allowing attackers to set arbitrary cookie values. One thing to know is that browers encode certain characters in document.referrer. In order to maximize the impact, we need to be able to inject some critical characters, namely semi-colon (;) and equal sign (=), to manipulate cookie attributes. All browers seem to leave them untouched, however empty space is encoded. Without it we cannot assign a valid GMT date (Wdy, DD-Mon-YYYY HH:MM:SS GMT) to Expires. We could replace it with Max-age to tell the browser how many seconds later should the cookie be expired, but some browsers (e.g. Internet Explorer) do not recognize such attribute.

The following table shows what charactors can be used as a replacement of empty space in Expires in each browser:

Chrome
33 (!), 34 ("), 35 (#), 36 ($), 37 (%), 38 (&), 39 ('), 40 ((), 41 ()), 42 (*), 43 (+), 44 (,), 45 (-), 46 (.), 47 (/), 60 (<), 61 (=), 62 (>), 63 (?), 64 (@), 91 ([), 92 (\), 93 (]), 94 (^), 95 (_), 96 (`), 123 ({), 124 (|), 125 (}), 126 (~)
Firefox
9 (TAB), 40 ((), 41 ()), 44 (,), 45 (-), 47 (/), 91 ([), 93 (])
Internet Explorer
Anything except alphanumeric and semi-colon
Safari
9 (TAB)

So most of the browsers accept quirky dates. You can test it with something like document.cookie = 'foo=bar; expires=Thu,01[Jan/2025^00:00:01+GMT'.

Final Attack Steps

  1. Prepare a page with URL https://attacker.com/?AAA[...]AAA;domain=.twitter.com;expires=Thu,01[Jan/2025^00:00:01+GMT
  2. Redirect victims to https://twitter.com/#foo and https://twitter.com/#bar to set two large cookies (due to 4kB limit per cookie)
  3. Congratulations to victims, we cure their Twitter addiction!

Lessons learnt

Cookie API on the DOM is dangerous. Perhaps it's time for a proper API?


Final Thought

Over 8000!!!

Validating user inputs is no joke. Even a lengthy input can kill your application.