Skip to main content

Free 30-min security demo Book Now

Academy Content Security Policy
Intermediate · 25 min

Content Security Policy

Master CSP directives, understand common bypasses like unsafe-inline and JSONP, and implement a strict nonce-based policy.

1 CSP Bypasses: unsafe-inline, Wildcards, and JSONP

A Content Security Policy is a defense-in-depth header that restricts script execution. However, common misconfigurations make it trivially bypassable.

unsafe-inline bypass:

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

This allows inline scripts and event handlers. Any XSS that injects an inline script will execute, defeating the purpose of CSP entirely.

Wildcard script source bypass:

Content-Security-Policy: script-src 'self' https://*.example.com

If the attacker can upload a file to any subdomain of example.com (like static.example.com), they can include it as a script from an allowed origin.

JSONP endpoint bypass:

Content-Security-Policy: script-src 'self' https://apis.google.com
<!-- apis.google.com has JSONP endpoints -->
<script src="https://apis.google.com/oauth2?callback=alert(document.cookie)"></script>
<!-- Output: alert(document.cookie)(data) — XSS via allowed domain! -->

2 Strict CSP with Nonces

A strict nonce-based CSP eliminates most bypass vectors by only allowing scripts with a fresh server-generated nonce.

Server-side nonce generation:

import secrets

# Per-request nonce — never reuse!
nonce = secrets.token_urlsafe(16)
response.headers["Content-Security-Policy"] = (
    f"default-src 'none'; "
    f"script-src 'nonce-{nonce}' 'strict-dynamic'; "
    f"style-src 'nonce-{nonce}'; "
    f"img-src 'self' data:; "
    f"font-src 'self'; "
    f"connect-src 'self'; "
    f"frame-ancestors 'none'"
)

HTML using the nonce:

<script nonce="{{ nonce }}">
  // This script is allowed — it has the matching nonce
  const data = await fetch("/api/data");
</script>

<!-- Injected inline script has no nonce — blocked! -->
<script>alert(1)</script>  ← Blocked!

strict-dynamic: Allows scripts with the nonce to dynamically load other scripts (e.g., via createElement("script")), enabling SPAs while blocking attacker-injected scripts.

Knowledge Check

0/3 correct
Q1

Why does script-src 'unsafe-inline' make a CSP ineffective against XSS?

Q2

How can an attacker bypass a CSP that allows scripts from *.googleapis.com?

Q3

What is the main advantage of nonce-based CSP over hash-based CSP for inline scripts?

Code Exercise

Generate Nonce-Based CSP

The app has no Content Security Policy. Add middleware that generates a per-request nonce and sets a strict CSP header. The nonce should be available to templates as res.locals.nonce.

javascript

Put it into practice

Find Content Security Policy in your own code

Offensive360 SAST traces the tainted data flow behind this class of bug across 60+ languages and ships a secure fix with every finding. Public open-source projects scan free; teams can see it on their own repository in a 30-minute demo.