Summary
Reddit.com was vulnerable to an HTTP Response Splitting
vulnerability. As a result, it was possible to execute arbitrary
JavaScript and HTML on the reddit.com domain.
What is HTTP Response Splitting?
From Wikipedia:
HTTP response splitting is a form of web application vulnerability,
resulting from the failure of the application or its environment to
properly sanitize input values. It can be used to perform cross-site
scripting attacks, cross-user defacement, web cache poisoning, and
similar exploits.
The attack consists of making the server print a carriage return (CR,
ASCII 0x0D) line feed (LF, ASCII 0x0A) sequence followed by content
supplied by the attacker in the header section of its response,
typically by including them in input fields sent to the application.
Per the HTTP standard (RFC 2616), headers are separated by one CRLF
and the response’s headers are separated from its body by two.
Therefore, the failure to remove CRs and LFs allows the attacker to
set arbitrary headers, take control of the body, or break the response
into two or more separate responses—hence the name.
The Web Application Security Consortium also has a good writeup,
including sources with more details.
How Did The Vulnerability Work?
Reddit.com, like many sites on the Internet, has a redirect system built
into its login functionality. If you’re viewing a page on reddit.com and
choose to log in, the system will redirect you back to your original
page afterward. The redirect functionality appears to be limited to
pages on reddit.com and to reddit.com subdomains.
Under normal circumstances, a login URL with a redirect might look
something like this:
http://reddit.com/login?dest=/r/reddit.com
If a user is already logged in, that URL will skip the login step and go
straight to the redirection. The headers sent for that redirect look
like this:
| HTTP/1.1 302 Moved Temporarily
Content-Type: text/html; charset=UTF-8
Location: /r/reddit.com
Pragma: no-cache
Cache-Control: no-cache
Content-Encoding: gzip
Content-Length: 20
Server: '; DROP TABLE servertypes; --
Vary: Accept-Encoding
Date: Fri, 14 Jan 2011 03:02:59 GMT
Connection: keep-alive
|
Unfortunately, the vulnerability occurred because the “dest” parameter
of the URL allowed an attacker to include newline characters (\r\n, or
%0D%0A). Those characters were then parsed literally, which gave an
attacker control over part of the HTTP response being sent by reddit’s servers.
To illustrate the point, lets take a look at one of the proof of
concepts I developed to demonstrate the vulnerability.
The malicious URL we’re interested in is
http://www.reddit.com/login?dest=http://reddit.com/%0D%0ALocation:%20javascript:%0D%0A%0D%0A<script>alert(document.cookie)</script>.
Because of the newline characters being included in the “dest”
parameter, the response sent by reddit’s servers would now look
something like this
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | HTTP/1.1 302 Moved Temporarily
Content-Type: text/html; charset=UTF-8
Location: /r/reddit.com
Location: javascript:
<script>alert(document.cookie)</script>
Pragma: no-cache
Cache-Control: no-cache
Content-Encoding: gzip
Content-Length: 20
Server: '; DROP TABLE servertypes; --
Vary: Accept-Encoding
Date: Fri, 14 Jan 2011 03:02:59 GMT
Connection: keep-alive
|
Two sets of newlines in a row (%0D%0A%0D%0A) indicate that the HTTP
headers are done being sent and the remainder of the response is the
response body. Normally, the body of a redirect response is not rendered
by the browser: the redirect makes that impossible to do. However, I
used the second Location: header to try and confuse the browser, halting
the redirect and causing the browser to display the body, which now
contained JavaScript that I had included.
Proof of Concepts
I developed a number of proof of concepts, since browser-specific
behaviors heavily influenced whether a particular URL could trigger an
XSS vulnerability in a particular browser.
- http://www.reddit.com/login?dest=http://reddit.com/%0D%0ALocation:
javascript:%0D%0A%0D%0A<script>alert(document.cookie)</script>
This proof of concept worked in Firefox only. Firefox halts the
redirect and displays the body of the response if it encounters a
second Location header containing something invalid (like a redirect
to a JavaScript URI).
- http://www.reddit.com/login?dest=http://reddit.com/%00%0D%0A%0D%0A<script>alert(document.cookie)</script>
This proof of concept worked in both Firefox and Chrome. Both
browsers would not redirect and would display the body of the
response if the Location header contained a null byte.
- http://www.reddit.com/login?dest=http://reddit.com/%00%0D%0A%0D%0A<script
src=”http://ha.ckers.org/xss.js”></script>
This proof of concept worked in Safari only. It is similar to the
second proof of concept, but it contains a different JavaScript
payload: for some reason, Safari would not execute the JavaScript in
the second proof of concept (I didn’t investigate the exact cause
too much).
And of course, no XSS writeup would be complete without a picture. So,
here’s a screenshot of the first proof of concept in action:
In Firefox 3.6.x, it was possible to trigger JavaScript by
injecting a second, invalid Location header and a body containing <script> tags into the response.
In case anyone is curious, this vulnerability was patched within 48
hours of my original report.
Anything Else?
I want to thank reddit’s admins for supporting the responsible
disclosure of security vulnerabilities. 
Also, if you have any questions about HTTP Response Splitting or other
web application security vulnerabilities, feel free to leave them in the comments!