csp-part-2

CSP Part 2: Securing Inline Scripts with Nonces and Hashes

In Part 1 of the CSP series, we explored how CSP plays a major role in mitigating XSS and clickjacking attacks. Now that you’re familiar with the basics of setting up a CSP and its importance, let’s take it one step further.

Today, we’ll dive into two powerful CSP techniques: nonces and hashes. These allow us to safely run specific inline scripts while still blocking potentially malicious ones — all without compromising security.

Let’s get into it!


The Problem with 'unsafe-inline'

We all love a quick fix, and 'unsafe-inline' might seem like one. It lets your inline scripts run without much effort. But here’s the catch:

Content-Security-Policy: script-src 'self' 'unsafe-inline';

By allowing 'unsafe-inline', you’re letting any inline script execute, including those that attackers may inject using XSS.

This completely defeats the purpose of CSP.

Instead, we should use nonces or hashes to allow only those scripts that we explicitly trust.

Extras: Inline scripts

In case you are wondering what exactly are inline scripts, this section is for you.

Inline scripts are pieces of JavaScript code that are written directly inside your HTML file, within a <script> tag, instead of being linked from an external .js file.

Example of an Inline Script:

htmlCopyEdit<!DOCTYPE html>
<html>
<head>
  <title>Inline Script Example</title>
</head>
<body>
  <h1>Hello, User!</h1>
  
  <script>
    console.log('This is an inline script');
  </script>
</body>
</html>

This JavaScript code is inline because it’s embedded right inside the HTML document.


Using CSP Nonces

A nonce (number used once) is a randomly generated value that you assign to each inline script. This approach allows specific inline scripts to execute while blocking others, enhancing security.

Implementation Steps:

  • Generate a Unique Nonce: For each HTTP response, create a cryptographically secure random nonce.
  • Apply the Nonce to Inline Scripts: Include the nonce as an attribute in your inline <script> tags.
  • Set the CSP Header with the Nonce: Configure your server to include the nonce in the Content-Security-Policy header.

Example:

1. Generating a Nonce in Express (Node.js):

const crypto = require('crypto');
const app = require('express')();

app.use((req, res, next) => {
  // Generate a 16-byte random nonce
  res.locals.nonce = crypto.randomBytes(16).toString('base64');
  next();
});

app.get('/', (req, res) => {
  // Set the CSP header with the generated nonce
  res.setHeader(
    "Content-Security-Policy",
    `script-src 'self' 'nonce-${res.locals.nonce}';`
  );
  // Render your HTML with the nonce applied to inline scripts
  res.send(`
    <!DOCTYPE html>
    <html>
      <head>
        <title>Secure Page</title>
      </head>
      <body>
        <script nonce="${res.locals.nonce}">
          // Your inline script here
          console.log('Secure inline script executed.');
        </script>
      </body>
    </html>
  `);
});

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

2. CSP Header Configuration:

Content-Security-Policy: script-src 'self' 'nonce-randomlyGeneratedNonce';

Replace 'randomlyGeneratedNonce' with the actual nonce value generated for each request.

Benefits of Using Nonces:

  • Granular Control: Only scripts with the correct nonce can execute, reducing the risk of malicious code running on your site.
  • Flexibility: Allows necessary inline scripts to function without broadly enabling all inline code.

Key Considerations:

  • Do Not Reuse Nonces: Each HTTP response must have a unique nonce to maintain security integrity.
  • Avoid Static Nonces: Using a static nonce value across multiple responses defeats the purpose of nonce-based CSP and can expose your application to attacks. (Microsoft Learn)
  • Secure Generation: Utilize a reliable, cryptographically secure method to generate nonces, ensuring they are unpredictable and unique.

Hashes Instead of Nonces

If you’d rather not generate dynamic nonces, another secure method to allow specific inline scripts is using cryptographic hashes.

Instead of enabling all inline scripts using 'unsafe-inline' (which is insecure), you compute a hash of the exact script content and include it in your CSP header. When the browser loads the page, it compares the script’s hash with the one in the policy — and executes it only if they match exactly.

This method ensures that only the intended inline code runs, even if attackers inject malicious scripts elsewhere.

Following are the steps on how to compute a hash of an inline script

1. Identify the Inline Script:

<script>
  alert('Hello, world!');
</script>

2. Compute the Script’s Hash:

You can use one of two methods:

  • Using OpenSSL (Command Line):
echo -n "alert('Hello, world!');" | openssl dgst -sha256 -binary | openssl base64
  • Using Browser Dev Tools:
    • Apply a temporary CSP without 'unsafe-inline'
    • Let the browser block the script
    • Read the console message for the hash
Content-Security-Policy: script-src 'self';

You’ll see an error message like:

Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'". Either the 'unsafe-inline' keyword, a hash ('sha256-abc123...'), or a nonce ('nonce-...') is required to enable inline execution.

Use the sha256-abc123... part in your CSP header.

3. Update Your CSP Header:

Update your configuration to have a sha of the script you just calculated and now the script will run without any errors

Content-Security-Policy: script-src 'self' 'sha256-abc123...';

Important Considerations:

  • Exact Match: The hash must correspond precisely to the script’s content. Any change will require a new hash.
  • Supported Algorithms: CSP supports sha256, sha384, and sha512.

Final Thoughts

Both nonces and hashes are powerful tools that help maintain the security of your web applications without disabling all inline functionality.

Avoiding 'unsafe-inline' and switching to these mechanisms ensures that only explicitly trusted scripts can run, which significantly strengthens your defense against XSS.

Choose nonces if you’re okay with generating them dynamically. Choose hashes if your scripts are static and don’t change often.

In either case, you’re staying true to CSP’s mission: Stop bad scripts. Let good ones run.

Thats all for this blog post. I hope you enjoyed reading it! See you in the next one. Until then, happy defending!! 🙂

shreyapohekar

I’m Shreya Pohekar, a Senior Product Security Analyst at HackerOne. I enjoy sharing my thoughts and insights through blogging, turning complex security topics into engaging and accessible content for my readers.

Leave a Reply