DEV Community

Cover image for Mitigating cross-site scripting with Content Security Policy
masters.πŸ€”
masters.πŸ€”

Posted on

Mitigating cross-site scripting with Content Security Policy

In this post, we're going to look at using Content Security Policy (CSP) as a defense-in-depth technique to block script injection attacks.

When building website that hosts user-generated content, such as:

Great to be here!
<script>window.location='https://example.com'</script>
Enter fullscreen mode Exit fullscreen mode

It's necessary to encode user-generated content so that browsers don't mistake it for markup, and execute an untrusted script. This is easy to do for plain text, but what if a page needs to render user-generated HTML? Here's an example of HTML that contains inline Javascript, which browsers could execute:

<p>Great to <b>be</b> here!</p>
<img src="" onerror="alert(0)" />
<a href="javascript:alert(0)">Hi</a>
<script>window.location='https://example.com'</script>
Enter fullscreen mode Exit fullscreen mode

This content must be sanitized before rendering. Libraries such as HTMLAgilityPack or DOMPurify provide a way to parse the HTML and strip out elements or attributes known to execute scripts.

Sanitization is important, but what if an attacker has discovered a way around the filter? This is where Content Security Policy comes in.

If the Content-Security-Policy header is present when retrieving the page, and contains a script-src definition, scripts will be blocked unless they match one of the sources specified in the policy. A policy might look something like:

script-src 'self'; object-src 'none'; base-uri 'none';
Enter fullscreen mode Exit fullscreen mode

This policy disallows:

  • External scripts not hosted on the same domain as the current page.
  • Inline script elements, such as <script>
  • Evaluated Javascript, such as <img src="" onerror="alert(0)" />
  • Base elements, which could break scripts loaded from a relative path
  • Object elements, which can host interactive content, such as Flash

Whitelisting inline scripts

Sometimes it is necessary to run inline scripts on your page. In these cases, the nonce attribute on script elements can be used to whitelist scripts that you control.

<script nonce="00deadbeef">doSomething()</script>
Enter fullscreen mode Exit fullscreen mode

A matching nonce must be present in the CSP for the script to run. For compatibility with older browsers, unsafe-inline allows scripts to run if the nonce tag is unsupported.

script-src 'self' 'nonce-00deadbeef' 'unsafe-inline'; object-src 'none'; base-uri 'none';
Enter fullscreen mode Exit fullscreen mode

It is critical that this nonce is derived from a cryptographic random number generator so that an attacker can't guess a future nonce. In .NET, RNGCryptoServiceProvider.GetBytes can be used to fill a 16 byte array:

using (var random = new RNGCryptoServiceProvider())
{
    byte[] nonce = new byte[16];
    random.GetBytes(nonce);
    return Convert.ToBase64String(nonce);
}
Enter fullscreen mode Exit fullscreen mode

Whitelisting external scripts

strict-dynamic can be used to allow scripts hosted on a third-party domain to be loaded by scripts that you control. However, at the time of writing, this isn't supported by all major browsers, so a host whitelist should be used as well as a fallback until it has broad support.

script-src 'self' 'nonce-00deadbeef' 'unsafe-inline' 'strict-dynamic' https://example.com; object-src 'none'; base-uri 'none';
Enter fullscreen mode Exit fullscreen mode

When using strict-dynamic, you will also need to add a nonce to any external scripts that are referenced.

<script nonce="00deadbeef" src="https://example.com/analytics.js" />
Enter fullscreen mode Exit fullscreen mode

Note:

Be careful what sources you whitelist. If any endpoints return JSONP and fail to sanitize the callback, it is possible to inject code. For example:

<script src="https://example.com/getuser?callback=window.location='http://google.com';test"></script>

Might return window.location='http://google.com';test({}) in the JSONP response, which would cause arbitrary code to be executed!

There are other policies that you can define to strengthen your site's security, such as restricting where stylesheets are loaded from. This post only focuses on mitigating cross-site scripting attacks.

Further Reading

Thanks to Bradley Grainger and Kyle Sletten for reviewing this implementation.

Top comments (6)

Collapse
 
ben profile image
Ben Halpern

Tremendous overview Dustin

Collapse
 
dustinsoftware profile image
masters.πŸ€”

Thanks Ben! I'm glad it was helpful :)

Collapse
 
dustinsoftware profile image
masters.πŸ€”

Agreed. It seems like browser add-ons might be a good fit for changing the page content or interacting with the page for most cases where you'd use a bookmarklet though :)

Collapse
 
sd profile image
Ali Razzaq 123

window.location='example.com'

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
Sloan, the sloth mascot
Comment deleted