Monday, January 13, 2014

Using Content-Security-Policy for Evil

TL;DR How can we use technique created to protect websites for Evil? (We used XSS Auditor for Evil before) There's a neat way: taking advantage of CSP we can detect whether URL1 does redirect to URL2 and even bruteforce /path of URL2/path. This is a conceptual vulnerability in CSP design (violation == detection), and there's no obvious way to fix it.

Demo & playground: http://homakov.github.io/csp.html


What is CSP
CSP header tells browsers what "inline scripts" and/or 3rd party scripts (you must specify hosts) can be loaded/executed for this page. So if browser sees anything not allowed by the header it raises an exception (Violation). 
Chrome 16+, Safari 6+, and Firefox 4+ support CSP. IE 10 has very limited support.

As soon as user hits HOST1 he gets 302 redirect to HOST2 and that's where violation happens, because HOST2 is not allowed by CSP. It sends POST request to /q with "blocked-uri":"http://HOST2" (only hostname, path is stripped for similar security reasons).


You can see the exception in console of Web Inspector (but you cannot catch it with JS).
Just console notice seemed not enough to standard's authors, thus they introduced "report-uri" directive which notifies host about the violation. User-agent sends POST request with JSON details about Violation.


All kinds of detections are bad.
Given HOST1 redirects to HOST2 for some users, we can detect if current user was redirected: let's specify HOST1 as allowed host in our CSP using following HTML:

<meta http-equiv="Content-Security-Policy" content="img-src HOST1;report-uri /q">
<img src="HOST1">


Achievement 1: we can detect if certain URL redirects to HOST2 listening to "report-uri" reports.


Privacy, Fingerprints and OAuth.
OAuth is based on redirects, and this is a huge framework design issue (more details in other posts).
Client redirects to Provider/auth?client_id=123&redirect_uri=Client/callback and in case this Client is already authorized for current User, Provider redirects to Client/callback automatically.

Using CSP trick we can check if current User has authorized certain Clients (Farmville, Foursquare or PrivateYahtClubApp). Quickly. We generate something like this:
<img src="facebook/?client_id=1&redirect_uri=Client1">
<img src="facebook/?client_id=2&redirect_uri=Client2">
<img src="facebook/?client_id=3&redirect_uri=Client3">
...
All authorized apps will cause CSP violation report having "blocked-uri":"http://Client1" in it.

Achievement 2: Using 100-500 most popular FB clients we can build sort of user's fingerprint: what apps you authorize and what websites you frequently visit.


Bruteforcing with CSP
The new implementation of CSP in Chrome (from 16 to current 31+ version)  can specify exact URLs, not just host names.
Using bunch of allowed URLs we can bruteforce user_id when http://HOST/id redirects to http://HOST/id12345
For this we will need map-reduce style probing (I used it before to brute <script>s with XSS Auditor). First we load few iframes with big bunches in CSP header: 1 to 10000, 10000 to 20000 etc.


We define in CSP ranges of URLs from id=1 to id=10000, from id=10000 to id=20000 and so on. Every violation will disclose if target id was listed in crafted CSP header. As soon as we reach bunch not raising an exception (10 seconds timeout is enough), we can split that bunch into 10 smaller bunches, 1000 per each, until we figure out target id:


<iframe src="/between?from=20000&to=21000"></iframe>
<iframe src="/between?from=21000&to=22000"></iframe>


For social network VK (m.vk.com/photos redirects to m.vk.com/photosMYID) process of guessing will take unfeasibly long while, because they have over 100 million accounts. But for smaller websites ranges are smaller and detection is real.
Achievement 3: with map-reduce style bruteforce we can detect /path (not ?query) of redirect destination (URL redirects to HOST2/path). Onerror/onload detection You might ask: "Will removal of report-uri directive fix this issue?" No. It is more high level issue. Since CSP blocks not allowed URLs, it doesn't fire up "onload" event, just as X-Frame-Options block doesn't fire it up. Exploit gets even faster: frame.src = 'data:text/html,<meta http-equiv="Content-Security-Policy" content="img-src '+allowed+';"><img src="'+url+'" onerror=parent.postMessage(1,"*") onload=parent.postMessage(2,"*")>' Achievement 4: we can really quickly detect if certain URL redirects to HOST2 utilizing onload/onerror events. Mitigation? IMO we should remove 'report-uri' and invoke 'onload' event even if URL was blocked by CSP. I know, it is confusing approach, but it's the only way to get rid of detection. My initial report (31 Oct) was marked as Severity: none by Chrome and not going to be fixed soon. It will require fixing the standard anyway. Related issue Pwning privacy with hash based URL detection

4 comments:

  1. Most instances of this exploit rely on trying to load an HTML page via an img tag. Wouldn't it be possible for the server to recognize this by the different Accept request header? So the exploit surface remaining is only real images being loaded, which could be dealt with by not doing redirects on images, or at least not doing redirects that expose information.

    Indeed the demo you posted no longer works, so Facebook and Github may already have implemented this counter measure.

    ReplyDelete
    Replies
    1. > Wouldn't it be possible for the server to recognize this by the different Accept request header?

      Maybe, but no one will spend time on protection from this, and by default they are all vulnerable

      I don't know what happened to demo but it's not fixed yet afaik.

      Delete
  2. I've spent some time on this and put together another proof of concept at http://myseosolution.de/test/csp-deanonymisierung.php?network=Facebook&lang=en

    Full explanation is available over here http://www.myseosolution.de/deanonymizing-facebook-users-by-csp-bruteforcing/

    @Egor: Would love to know what you think.

    Cheers
    Pascal

    ReplyDelete
    Replies
    1. It's good to remind people about this problem. I mentioned CSP bucket-based detection, and it can be really useful. But it's gonna be fixed soon, while similar issue with hash-based detection is not.

      Delete