[ Web Proxy ]
URL:
Viewing: https://web.dev/strict-csp/ [Back]  [Original]

Mitigate cross-site scripting (XSS) with a strict Content Security Policy (CSP)  |  Articles  |  web.dev Skip to main content
web.dev [web.dev]

Mitigate cross-site scripting (XSS) with a strict Content Security Policy (CSP) Stay organized with collections Save and categorize content based on your preferences.

Browser Support

Cross-site scripting (XSS), the ability to inject malicious scripts into a web app, has been one of the biggest web security vulnerabilities for over a decade.

Content Security Policy (CSP) is an added layer of security that helps to mitigate XSS. To configure a CSP, add the Content-Security-Policy HTTP header to a web page and set values that control what resources the user agent can load for that page.

This page explains how to use a CSP based on nonces or hashes to mitigate XSS, instead of the commonly used host-allowlist-based CSPs that often leave the page exposed to XSS because they can be bypassed in most configurations.

Key term: A nonce is a random number used only once that you can use to mark a <script> tag as trusted.

Key term: A hash function is a mathematical function that converts an input value into a compressed numerical value called a hash. You can use a hash (for example, SHA-256) to mark an inline <script> tag as trusted.

A Content Security Policy based on nonces or hashes is often called a strict CSP. When an application uses a strict CSP, attackers who find HTML injection flaws generally can't use them to force the browser to execute malicious scripts in a vulnerable document. This is because strict CSP only allows hashed scripts or scripts with the correct nonce value generated on the server, so attackers can't execute the script without knowing the correct nonce for a given response.

Note: To protect your site from XSS, make sure to sanitize user input and use CSP as an extra security layer. CSP is a defense-in-depth technique that can prevent the execution of malicious scripts, but it's not a substitute for avoiding and promptly fixing XSS bugs.

Why should you use a strict CSP?

If your site already has a CSP that looks like script-src www.googleapis.com, it's probably not effective against cross-site. This type of CSP is called an allowlist CSP. They require a lot of customization and can be bypassed by attackers.

Strict CSPs based on cryptographic nonces or hashes avoid these pitfalls.

Strict CSP structure

A basic strict Content Security Policy uses one of the following HTTP response headers:

Nonce-based strict CSP

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
er4BaGCJzBwDaESFKfZd.jpg [er4BaGCJzBwDaESFKfZd.jpg] How a nonce-based strict CSP works.

Hash-based strict CSP

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
Note: The basic strict CSP needs some tweaks to be effective across browsers. See Add a fallback to support Safari and older browsers for details.

The following properties make a CSP like this one "strict" and therefore secure:

Note: Another advantage of a strict CSP is that the CSP always has the same structure and doesn't need to be customized for your application.

Adopt a strict CSP

To adopt a strict CSP, you need to:

  1. Decide whether your application should set a nonce- or hash-based CSP.
  2. Copy the CSP from the Strict CSP structure section and set it as a response header across your application.
  3. Refactor HTML templates and client-side code to remove patterns that are incompatible with CSP.
  4. Deploy your CSP.

You can use Lighthouse (v7.3.0 and higher with flag --preset=experimental) Best Practices audit throughout this process to check whether your site has a CSP, and whether it's strict enough to be effective against XSS.

Lighthouse
  report warning that no CSP is found in enforcement mode. [Lighthouse report warning that no CSP is found in enforcement mode.] If your site doesn't have a CSP, Lighthouse shows this warning.

Step 1: Decide if you need a nonce- or hash-based CSP

Here's how the two types of strict CSP work:

Nonce-based CSP

With a nonce-based CSP, you generate a random number at runtime, include it in your CSP, and associate it with every script tag in your page. An attacker can't include or run a malicious script in your page, because they would need to guess the correct random number for that script. This only works if the number isn't guessable, and is newly generated at runtime for every response.

Use a nonce-based CSP for HTML pages rendered on the server. For these pages, you can create a new random number for every response.

Hash-based CSP

For a hash-based CSP, the hash of every inline script tag is added to the CSP. Each script has a different hash. An attacker can't include or run a malicious script in your page, because the hash of that script would need to be in your CSP for it to run.

Use a hash-based CSP for HTML pages served statically, or pages that need to be cached. For example, you can use a hash-based CSP for single-page web applications built with frameworks such as Angular, React or others, that are statically served without server-side rendering.

Step 2: Set a strict CSP and prepare your scripts

When setting a CSP, you have a few options:

Option A: Nonce-based CSP

Set the following Content-Security-Policy HTTP response header in your application:

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
Caution: Replace the {RANDOM} placeholder with a random nonce that is regenerated on every server response.

Generate a nonce for CSP

A nonce is a random number used only once per page load. A nonce-based CSP can only mitigate XSS if attackers can't guess the nonce value. A CSP nonce must be:

  • A cryptographically strong random value (ideally 128+ bits in length)
  • Newly generated for every response
  • Base64 encoded

Here are some examples of how to add a CSP nonce in server-side frameworks:

const app = express();

app.get('/', function(request, response) {
  // Generate a new random nonce value for every response.
  const nonce = crypto.randomBytes(16).toString("base64");

  // Set the strict nonce-based CSP response header
  const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`;
  response.set("Content-Security-Policy", csp);

  // Every <script> tag in your application should set the `nonce` attribute to this value.
  response.render(template, { nonce: nonce });
});

Add a nonce attribute to <script> elements

With a nonce-based CSP, every <script> element must have a nonce attribute that matches the random nonce value specified in the CSP header. All scripts can have the same nonce. The first step is to add these attributes to all scripts so the CSP allows them.

Option B: Hash-based CSP Response Header

Set the following Content-Security-Policy HTTP response header in your application:

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

For multiple inline scripts, the syntax is as follows: 'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'.

Caution: You must replace the {HASHED_INLINE_SCRIPT} placeholder with a base64-encoded SHA-256 hash of an inline script that can be used to load other scripts (see next section). You can calculate SHA hashes of static inline <script> blocks with this tool. An alternative is to inspect the CSP violation warnings in Chrome DevTools, which contains hashes of blocked scripts, and add these hashes to the policy as 'sha256-'. The browser can now block any script an attacker injects, because it allows only the hashed inline script and any scripts it adds dynamically.

Load sourced scripts dynamically

You can load third-party scripts dynamically using an inline script.

B2YsfJDYw8PRI6kJI7Bs.jpg [B2YsfJDYw8PRI6kJI7Bs.jpg] An example of how to inline your scripts.
Allowed by CSP
<script>
  var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js'];

  scripts.forEach(function(scriptUrl) {
    var s = document.createElement('script');
    s.src = scriptUrl;
    s.async = false; // to preserve execution order
    document.head.appendChild(s);
  });
</script>
To let this script run, you must calculate the inline script's hash and add it to the CSP response header, replacing the {HASHED_INLINE_SCRIPT} placeholder. To reduce the amount of hashes, you can merge all inline scripts into a single script. To see this in action, refer to this example and its code.
Blocked by CSP
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script>
CSP blocks these scripts because they were not dynamically added and they have no integrity attribute which matches an allowed source.
Note: When calculating a CSP hash for inline scripts, whitespace characters between the opening and closing <script> tags matter. You can calculate CSP hashes for inline scripts using this tool.

Script loading considerations

The inline script example adds s.async = falseto ensure that foo executes before bar, even if bar loads first. In this snippet, s.async = false doesn't block the parser while the scripts load, because the scripts are added dynamically. The parser stops only while the scripts execute, as it would for async scripts. However, with this snippet, keep in mind:

  • One or both scripts might execute before the document has finished downloading. If you want the document to be ready by the time the scripts execute, wait for the DOMContentLoaded event before you append the scripts. If this causes a performance issue because the scripts don't start downloading early enough, use preload tags earlier on the page.
  • defer = true doesn't do anything. If you need that behaviour, run the script manually when it's needed.

Step 3: Refactor HTML templates and client-side code

Inline event handlers (such as onclick="", onerror="") and JavaScript URIs (<a href="javascript:">) can be used to run scripts. This means an attacker who finds an XSS bug can inject this kind of HTML and execute malicious JavaScript. A nonce- or hash-based CSP prohibits the use of this kind of markup. If your site uses any of these patterns, you'll need to refactor them into safer alternatives.

If you enabled CSP in the previous step, you'll be able to see CSP violations in the console every time CSP blocks an incompatible pattern.

CSP violation reports in the Chrome developer console. [CSP violation reports in the Chrome developer console.] Console errors for blocked code.

In most cases, the fix is straightforward:

Refactor inline event handlers

Allowed by CSP
<span id="things">A thing.</span>
<script nonce="${nonce}">
  document.getElementById('things').addEventListener('click', doThings);
</script>
CSP allows event handlers that are registered using JavaScript.
Blocked by CSP
<span >A thing.</span>
CSP blocks inline event handlers.

Refactor javascript: URIs

Allowed by CSP
<a id="foo">foo</a>
<script nonce="${nonce}">
  document.getElementById('foo').addEventListener('click', linkClicked);
</script>
CSP allows event handlers that are registered using JavaScript.
Blocked by CSP
<a href="javascript:linkClicked()">foo</a>
CSP blocks javascript: URIs.

Remove eval() from your JavaScript

If your application uses eval() to convert JSON string serializations into JS objects, you should refactor such instances to JSON.parse(), which is also faster.

If you can't remove all uses of eval(), you can still set a strict nonce-based CSP, but you have to use the 'unsafe-eval' CSP keyword, which makes your policy slightly less secure.

You can find these and more examples of such refactoring in this strict CSP codelab:

Step 4 (Optional): Add fallbacks to support old browser versions

Caution: Because all modern browser engines support strict CSP using strict-dynamic, there's no need to add fallbacks to your CSP unless you need to support users on outdated browser versions. Setting fallbacks doesn't reduce the security of your policy in modern browsers, but they can cause confusion because many developers aren't familiar with CSP's complex fallback mechanisms.

Browser Support

If you need to support older browser versions:

Content-Security-Policy:
  script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
  object-src 'none';
  base-uri 'none';
Note: https: and unsafe-inline don't make your policy less safe. Any browser that supports strict-dynamic knows to ignore them.

Step 5: Deploy your CSP

After confirming that your CSP doesn't block any legitimate scripts in your local development environment, you can deploy your CSP to staging, then to your production environment:

  1. (Optional) Deploy your CSP in report-only mode using the Content-Security-Policy-Report-Only header. Report-only mode is handy to test a potentially breaking change like a new CSP in production before you start enforcing CSP restrictions. In report-only mode, your CSP doesn't affect your app's behavior, but the browser still generates console errors and violation reports when it encounters patterns incompatible with your CSP, so you can see what would have broken for your end users. For more information, see Reporting API.
  2. When you're confident that your CSP won't break your site for your end-users, deploy your CSP using the Content-Security-Policy response header. We recommend setting your CSP using an HTTP header server-side because it's more secure than a <meta> tag. After you complete this step, your CSP starts protecting your app from XSS.
Note: Make sure the CSP you're using is "strict" by checking it with the CSP Evaluator or Lighthouse. This is very important because small changes to a policy can cause security issues.Caution: When enabling CSP for production traffic, you might see some noise in the CSP violation reports from browser extensions and malware.

Limitations

A strict CSP generally provides a strong added layer of security that helps to mitigate XSS. In most cases, CSP reduces the attack surface significantly, by rejecting dangerous patterns like javascript: URIs. However, based on the type of CSP you're using (nonces, hashes, with or without 'strict-dynamic'), there are cases where CSP doesn't protect your app as well:

Developers and security engineers should pay particular attention to such patterns during code reviews and security audits. You can find more details on these cases in Content Security Policy: A Successful Mess Between Hardening and Mitigation.

Note: Trusted Types complements strict CSP very well and can efficiently protect against some of its limitations. Learn more about how to use Trusted Types at web.dev.

Further reading

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2024-09-13 UTC.

[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2024-09-13 UTC."],[],[]]

Web Proxy Viewer  |  New URL  |  Original Page