This is a personal blog. My other stuff: book | home page | Twitter | G+ | CNC robotics | electronics

June 07, 2010

Safari: a tale of betrayal and revenge

Looks like I am finally free to discuss the first interesting browser bug on my list - so here we go. I really like this one: its history goes back to 1994, and spans several very different codebases. The following account is speculative, but probably a pretty good approximation of what went wrong.

Let's begin with this simple URL:

http:example.com/

This syntax demonstrates an unintentional and completely impractical quirk in the URL parsing algorithm specified some 16 years ago in RFC 1630. Verbatim implementations are bound to parse this string as a relative reference to protocol = 'http', host = $base_url.host, path = 'example.com/'. It does not make a whole lot of sense, and indeed, in RFC 3986, Tim Berners-Lee had this to say:

"This is considered to be a loophole in prior specifications of partial URI [RFC1630]. Its use should be avoided but is allowed for backward compatibility."

Fast forward two years: the KDE team is working on a new open-source browser, Konqueror. Their browser uses KURL as the canonical URL parsing library across the codebase. This parser behaves in an RFC-compliant way when handling our weird input string, with one seemingly unimportant difference: if the current parsing context does not have a valid host name associated with it, the address is not rejected as unresolvable; the host name is simply set to an empty string. No big deal, right?

Well, somewhere around 2002, the renderer and the JavaScript engine used in Konqueror - KHTML and KJS - are forked off under the name of WebKit, and become the foundation for Safari. The fork contains almost all the necessary core components for a browser, with a notable exception of a built-in HTTP stack - and so, Apple decides to reuse their existing CFNetwork library for this purpose. When our special URL finally makes it to this library, it is interpreted in a far more intuitive, but technically less correct way - as protocol = 'http', host = 'example.com', path = '/'; HTTP cookies and other request parameters are then supplied accordingly.

The result? When you open two windows in Safari - one pointing to http:hairy-spiders.com, and the other pointing to http:fuzzy-bunnies.com - the HTTP stack will make sure they are populated with cookie-authenticated data coming from the two different servers named in the URLs; but the same origin checks within the browser will rely on KURL instead. Remember how KURL spews out an empty host name in both cases? Because empty strings always match, both pages are deemed to be coming from the same source, and can access each other at will. Oops.

Well, there's still a catch: this attack will only work as expected if the windows are opened by hand; in documents opened from a web page, the host name from the base URL will interfere with how the URLs are broken down. Thankfully, we can work around it, simply by hopping through a data: URL.

Reported to the vendor in January 2010, fixed in Safari 4.1 and 5.0 (APPLE-SA-2010-06-07-1, CVE-2010-0544). A simple and harmless proof-of-concept can be found here.

1 comment: