Skip to main content

Free 30-min security demo  — We'll scan your real code and show live findings, no commitment Book Now

Offensive360
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