9

Is CSP enforced only during initial rendering, meaning there is no continuous coverage after your document loads? Here is an example of what I'm talking about:

Let's say your page, example.com, has some JS that takes the url parameter name and renders it on the page:

<script nonce="test123">
$(document).ready(function() {
    $("#id").html(nameParameterTakenFromURLWithoutSanitation);
});
</script>

If someone visits example.com?name=<script>alert('XSS');</script>, based on my tests, the script will still execute (popping up the alert) regardless of CSP. Only illegal scripts that exist inside of the initial server response are blocked.

I'm using a "strict" CSP approach using nonces. Here is the policy I'm using:

object-src 'none';
script-src 'nonce-test123' 'unsafe-inline' 'unsafe-eval' 'strict-dynamic' https: http:;
report-uri http://localhost:8080/csp-collector

Why is the script not blocked?

Anders
  • 64,406
  • 24
  • 178
  • 215
Andrew Schmitt
  • 195
  • 1
  • 8
  • Let us [continue this discussion in chat](http://chat.stackexchange.com/rooms/47481/discussion-between-anders-and-andrew-schmitt). – Anders Oct 26 '16 at 19:32
  • 1
    not csp related: jQuery is `eval()`'ing the script, and jq can do what it wants since the script is local. to wit: your script tag is not actually inserted into the document, but is handled behind the scenes by jq (via a sub-routine of `.html()`). CSP covers the document, not the whole JS runtime. if you were to manually add a script tag, it should not work. – dandavis Oct 26 '16 at 19:42
  • @dandavis So you can't use jquery and CSP together? Generating content in this manner is no fringe case. – Andrew Schmitt Oct 26 '16 at 20:08
  • CSP doesn't preclude jq per se. it's the `elm.html(code)` method that is problematic since it calls `eval` on any script tag body it finds in the new markup, but `elm.text(code)` and `elm.innerHTML=code` is safe. many plugins use `.html()`, so be careful. so do other jq methods like `.load(url)`... – dandavis Oct 26 '16 at 20:12
  • 2
    I should point out that allowing either `unsafe-inline` or `unsafe-eval` is a terrible idea and negates almost all of the security that CSP provides (particularly the first one). By turning these directives off you've disabled the protection that CSP provides. This is why your example works. – Polynomial Oct 26 '16 at 21:14
  • `unsafe-inline` is a bad idea because it removes CSP's protection against XSS attacks (arguably the primary benefit and reason for CSP), but `unsafe-eval` doesn't automatically allow XSS attacks to work. `unsafe-eval` is only a problem when the page has bad eval-using javascript. There exist valid uses for eval and there's no need to have to choose between avoiding them wholesale or avoiding CSP. – Macil Oct 26 '16 at 21:21
  • 1
    Yes, though the point is that `unsafe-eval` allows unbounded JavaScript to execute in certain contexts, which is particularly egregious in the case of 3rd party libraries where you often wouldn't notice where eval or eval-like (CSP blocks anonymous functions too) code being used. – Polynomial Oct 26 '16 at 21:23

3 Answers3

10

Introduction

CSP should be enforced by the browser as long as you stay on the page, and not just while it is originally loaded or rendered. Everything else would make it quite toothless.

So the behaviour you are experiencing has nothing to do with when the script is loaded. Instead it is about what loads it. There are two issues.

Problem 1: strict-dynamic

I'll let Mozilla explain it:

The strict-dynamic directive specifies that the trust explicitly given to a script present in the markup, by accompanying it with a nonce or a hash, shall be propagated to all the scripts loaded by that root script.

So, when you trust a script by giving it a nonce, it can in turn load other scripts. That means that if you have a DOM XSS vulnerability in a script with a nonce, the CSP will not save you. The solution is to remove 'strict-dynamic' (but that will off course be problematic if you rely on this feature).

Problem 2: unsafe-eval

To load the script, you use jQuery .html(). Somewhere deep in the jQuery source code (line 343 to be exact) it makes a call to the good old eval().

So if you run the code

$(".test").html("<scr" + "ipt>alert('XSS');</scr" + "ipt>");

jQuery will for some reason (don't ask me why, I have tried and failed to follow the source code) run the following:

eval("alert('XSS');");

This means that the CSP no longer applies - after all you are not creating a new script tag, you are evaluating a string.

Here the solution is to remove 'unsafe-eval' from the CSP, but that would break jQuery. The solution to that might be to upgrade to jQuery 3, that upon cursory inspection of the source seems to have fixed this.

Conclusion

This means that all code (using jQuery 2) on the form

x.html(unsafeFromURL);

can be vulnerable to XSS even if a CSP is set. I think this is unexpected to a lot of people (it was for me). But it serves as a reminder of another important point: You should not use CSP as your only line of defence against XSS.

Also do note that this is not an issue with how CSP works, but with how jQuery works.

Anders
  • 64,406
  • 24
  • 178
  • 215
1

Your example starts by disabling CSP's inline script and eval protection, which are its primary two security controls; particularly the inline one as it is the main way of preventing XSS.

Then you run a script which writes a <script> tag into the page, causing it to be placed into the page. If you'd have manually written the same script tag into the page via server-side code, then you'd have got the same result. The fact that you did it with JavaScript is mostly irrelevant here.

By disabling CSP's key protection features, you removed all protection against XSS, then got XSS.

Polynomial
  • 132,208
  • 43
  • 298
  • 379
  • 2
    The `'unsafe-inline'` is not the issue, if that is what you suggest - the `'nonce-test123'` negates that (on supporting browsers). And manually writing the same script into the source of the page would not work. [See this](https://developer.mozilla.org/en-US/docs/Web/Security/CSP/CSP_policy_directives#strict-dynamic). – Anders Oct 26 '16 at 21:32
  • 2
    The reason that `'unsafe-inline'` is there is to make the script run on browsers that do not support CSP 2.0. Browsers that do support it ignores `'unsafe-inline'` if a `'nonce-*'` is present. – Anders Oct 26 '16 at 21:34
  • My header is for the "Strict" CSP implementation suggested by Google. You include 'unsafe-inline' and 'unsafe-eval' as fallback directives in the event the user agent doesn't support 'nonce-{hash value}'. For more info on Strict CSP click [here](https://csp.withgoogle.com/docs/strict-csp.html) – Andrew Schmitt Nov 04 '16 at 03:48
0

To answer the title of your question: No, CSP is enforced throughout the lifetime of the HTML document/resource unless you go to a completely different URL.

The reason your script is not blocked because of the presence of strict-dynamic on browsers that support it and on browsers that don't support it unsafe-inline will cause it not be blocked.

Let's see the case of browsers that support strict-dynamic:

The main purpose of strict-dynamic is to get rid of the whitelist (which is not so secure due to JSONP, etc . See here) and also to allow dynamic loading of scripts which is done by a lot of APIs out on the web.

Basically, if you are laoding a script dynamically by creating a script tag, it will be blocked if you are using nonces. So, what strict-dynamic does is if a script that has a nonce and is thus trusted, is allowed to load scripts like this (script tag creation) dynamically.

So, in your case:

<script nonce="test123">
    $(document).ready(function() {
        $("#id").html(nameParameterTakenFromURLWithoutSanitation);
    });
</script>

Creates a script tag if the nameParameter is say: <script>alert('XSS');</script> so by definition of strict-dynamic it should execute.

The main concern here for me is why are you using .html()? Do you expect name to have some HTML markup? If not then use .text() which escapes any HTML markup and hence prevents XSS.

tapananand
  • 340
  • 3
  • 17