Security/CSP/Deploying

In this article, we'll recommend steps that can be taken to modify a web site so that it will support CSP base restrictions. We will also explain how to craft a CSP policy for a site that will provide a maximum amount of protection.

Supporting CSP base restrictions

While restricting content loads by source may help mitigate attacks, there are base restrictions needed to properly avoid more complex XSS script injection attacks. Let's take a look at all of the base restrictions employed by CSP and see how equivalent functionality can be obtained through other techniques.

Until a site has been converted to support these base restrictions, the "inline" and "eval" keywords in the script-src directive can be used to disable them. The effort required to support the base restrictions will vary by site, but we'll provide some guidelines on how to convert sites, one restriction at a time.

Removing Inline Scripts

You can skip this part by adding the inline keyword to the script-src directive in your site's CSP policy definition.

Inline scripts are more easily injected into a site than their externally sourced counterparts. This is a side effect of mixing code and content.

<script> tags with text child nodes

The Problem

Inline scripting is the most popular vector for XSS, and a site that is vulnerable to any content injection might be coerced into displaying an inline script node. Since CSP rules can't block this (like they can block the script file load from a third party site), it must be disabled.

General Solution

To prevent your inline scripts from being blocked, there are two techniques that can be employed.

Move all the inline scripts into one external script file, then reference the file with a <script src='thefile.js'> tag in the document's head.

Move each inline script block into an external script file, and reference them individually from script blocks.

The most straightforward conversion technique is (2), but will lead to more HTTP requests being sent. Both techniques will be discussed here.

Conversion Steps

If the inline script blocks are context-insensitive (i.e., don't require document.write and just provide some functionality), they can easily be moved into a page-level external file. If they require context, they might need to each be stored in a separate file.

Approach (1): moving scripts into a single external file

Collect the text content of each script block

Concatenate the contents into a new file "moved.js"

Replace the first inline script tag with <script src='moved.js'></script>. Alternatively, this can be placed in the head element of the document.

Approach (2): individual external files

Generate a unique ID for each script block

Create a JS file with the script block's contents, named with the given unique ID

A combination of the two techniques can be used to minimize the number of requests while still providing a bit of context.

Each inline code block is assigned a unique ID.

All the code blocks are placed in a single file.

The file must be loaded at the end of the document, right before the closing body tag.

Dummy HTML elements are inserted where the inline script blocks were, labeled with the unique IDs. This allows the code to find where it "used to be".

javascript: URIs

The Problem

URIs that use the javascript scheme are another popular method to inject and run arbitrary script on a web page. Like inline script, these can be injected into html tags causing arbitrary script execution, so CSP disables javascript: URIs, including in clickable resources (i.e., where CSP doesn't filter links to other sites).

General Solution

If javascript: URIs are used in your web site, they can often be converted to script-initiated code. Common uses of such URIs are:

href attribute of anchor tag (link)

action attribute of an HTML form

TODO: list other cases

Conversion Steps

TODO: on case-by-case basis

Event handling attributes in HTML tags

The Problem

There are many HTML event handling attributes (on*) that can contain strings to be evaluated as script. These values can be injected if there's a content injection flaw on a site (either reflected or persistent). As a result, they must be disabled and event handling attributes must be added through scripts served in whitelisted files.

General Solution

Equivalent event handling attributes should be added in an external script file (either new or added onto an existing one).

Conversion Steps

For each DOM element X with an event handling attribute, onevent='code', script must be added to an external script file with the following form:

Removing "eval()"-like features

You can skip this part by adding the eval keyword to the script-src directive in your site's CSP policy definition.

Code generated on the fly can accidentally (or intentionally) contain user-specified content; any strings converted into script code during the run-time of a web application has the potential to be augmented and abused by an attacker. As a result, these must be removed from a site.

eval()

The Problem

Strings passed to eval() may be constructed from user-provided or tainted data. As a result, data passed to eval() may contain arbitrary code supplied by an attacker if they're able to inject content into the DOM. As a result, CSP must disable eval()

General Solution

Don't use it. Instead, place the code directly in a javascript file, or create the strings on the server-side.

Conversion Steps

TODO: explain how to (1) assemble on server or (2) move into script files

setTimeout()

The Problem

When setTimeout() is called with a string argument, this is essentially the same as calling eval() As a result, setTimeout(STRING) is disabled with CSP, and only the variant of the function that takes a function as argument is allowed.

General Solution

Where possible, remove quotes around the argument in calls to setTimeout(STRING, delay), and wrap the contents in a function (setTimeout(function() { CODE }, delay) ). Where this is not possible, the code should be created on the server, wrapped in a function, and served in a .js file (either the same one serving the setTimeout call, or another one).

Conversion Steps

setInterval()

The Problem

General Solution

Conversion Steps

new Function()

The Problem

General Solution

Conversion Steps

Often Misused Feature Clean up

data: URIs

XBL bindings

Writing an effective policy

This section will describe how to write an effective policy. This is guidance, not an absolutely perfect algorithm, since the best policy for any given site is relative to the content served upon it and relationships the site has with other entities.

Best practice for writing a Content Security Policy is to start with the most restrictive policy possible, and expand it as needed.

allow 'none'

Expansion can be done on a per-use basis (a directive at a time), and then refined to a simpler policy with knowledge of which sources are ultimately trusted, and allowing all types of content from those sources.

Use-based Expansion

For each directive, identify the sources for the respective type of content, and enumerate them. For example, consider a situation where scripts are served from the same source and images from a separate subdomain.

allow 'none'; script-src 'self'; img-src 'self' images.mysite.com

As a policy gets more detailed, and the host lists for directives get long, wildcards can be introduced. While host segment wildcards will simplify the policy, they also open up the site to using sources that may not be anticipated. Wildcards should be used with caution, and only when the common superdomain is trusted and exerts control over its subdomains.

If all directives in a policy have common sources, those sources can be placed into the allow directive. Before doing this, it is important to analyze the directives list, and ensure that every kind of content may be loaded from that source.

In fact, many policies will start with allow 'self', to both simplify the policy and to open up the base document's source as a valid source for all types of content. Many sites serve all content from the same source, so this allow 'self' policy will work well.