Clickjacking Chrome Extensions

Some webpages embed resources from other domains. Because of the web browser's storied and sordid history, it's possible for those resources to carry invisible, ambient authority without their creators' knowledge. One concrete example is Cross-Site Request Forgery: the JavaScript on a particular webpage can make an XMLHttpRequest to a resource on another domain and the browser will (in some circumstances) transmit its cookie along with that XMLHttpRequest . This allows an attacker's website to act with all of the user's authority on the second domain.

Another example is "clickjacking": by visually integrating an embedded resource into a webpage, for example by creating an iframe and styling it to be mostly transparent, you can trick a user to click on a button or link in the embedded document. Because embedded iframes carry ambient authority from their domains, clicking a button in a transparent iframe is the same as clicking a button on the original domain. In this way an attacker can trick a victim into visiting a webpage and performing some behavior on a webpage on an unrelated domain that they ordinarily wouldn't.

With ordinary webpages the mitigation for clickjacking is to include headers like X-Frame-Options or some Content-Security-Policy settings in the response from the server. Last year Blake Griffith and I realized that clickjacking Chrome extensions was possible by discovering a vulnerability in PrivacyBadger, a privacy-preserving browser extension published by the EFF for Chrome and Firefox.

Chrome extensions are compressed collections of files. One of those is a manifest.json ; that JSON file has a field web_accessible_resources . Here's what the Chrome docs say about it:

These resources would then be available in a webpage via the URL chrome-extension://[PACKAGE ID]/[PATH] , which can be generated with the extension.getURL method. Allowlisted resources are served with appropriate CORS headers, so they're available via mechanisms like XHR. A navigation from a web origin to an extension resource will be blocked unless the resource is listed as web accessible. Note these corner cases: When an extension uses the webRequest or declarativeWebRequest APIs to redirect a public resource request to a resource that is not web accessible, such request is also blocked.

The above holds true even if the resource that is not web accessible is owned by the redirecting extension.

In addition to being web accessible, the resources in the web_accessible_resources run with the ambient authority of the extension: they can alter state, load other resources, and modify the browser in certain ways. If a document in web_accessible_resources can perform any interesting behavior, an attacker can embed it in a webpage and trick visitors into triggering it.

In this case, in PrivacyBadger, the contents of the directory skin/ were web_accessible_resources . By loading skin/popup.html , the document that gets rendered when you click the the PrivacyBadger icon in the browser, in an iframe we could fool the user into clicking "Disable PrivacyBadger for this Website", opening up the user to additional tracking and undermining the function of PrivacyBadger.

A video demonstrating the transparent iframe overlaid over an attacker-controlled website, fading in and out. (In a real-life scenario an attacker could make it totally transparent, i.e. invisible).

The fix is easy: remove /skin/* from the web_accessible_resources . Blake wrote a PR for that and it was merged on June 5th, a day after we reported the issue.

The proof-of-concept Blake and I wrote is as follows. It will work on release 2017.5.9 or earlier.