CSP - Content Security Policy
Content Security Policy (CSP) is an HTTP header that instructs the browser which sources are allowed for scripts, stylesheets, images, and other resources. CSP prevents XSS attacks by blocking inline scripts and unknown external sources. Common misconfigurations: unsafe-inline, unsafe-eval, wildcard hosts, missing default-src directive. Recommended: Nonce-based CSP or Strict-Dynamic.
Content Security Policy (CSP) is a browser security mechanism controlled via the HTTP response header Content-Security-Policy. It allows website operators to precisely define which resources (scripts, stylesheets, images, frames) may be loaded from which sources. When implemented correctly, CSP is the most effective defense-in-depth measure against cross-site scripting (XSS).
CSP Basic Structure and Directives
Content-Security-Policy: <direktive> <quellen>; <direktive> <quellen>; ...
Important Directives
| Directive | Function |
|---|---|
default-src | Fallback for all resource types |
script-src | JavaScript sources |
style-src | CSS sources |
img-src | Image sources |
connect-src | Fetch/XHR/WebSocket connections |
font-src | Web font sources |
frame-src | iframe sources (also: frame-ancestors against clickjacking) |
object-src | Plugin sources (Flash, Java) - best: 'none' |
media-src | Audio/video sources |
base-uri | Allowed base-href values (base tag injection!) |
form-action | Allowed form targets |
Source Specifications
| Source Specification | Meaning |
|---|---|
'none' | Nothing allowed |
'self' | Same origin |
https: | All HTTPS sources |
https://cdn.example.com | Specific domain |
'nonce-<random>' | Unique random value (safest method!) |
| `'hash- | |
| ` | Allows specific inline code via hash |
'strict-dynamic' | Trusts scripts loaded by trusted scripts |
'unsafe-inline' | All inline scripts allowed (INSECURE!) |
'unsafe-eval' | Dynamic code execution allowed (INSECURE!) |
Common CSP misconfigurations
Misconfiguration 1 - unsafe-inline + script-src
Content-Security-Policy: script-src 'self' 'unsafe-inline';
'unsafe-inline'renders CSP USELESS against XSS!- All inline scripts (
<script>alert(1)</script>) allowed - An attacker can execute XSS despite CSP!
> Common justification: "We need inline scripts for analytics/tracking" - Solution: Nonce-based CSP!
Misconfiguration 2 - Wildcard hosts
Content-Security-Policy: script-src 'self' https: *.cdn.com;
https:allows ALL HTTPS sources worldwide → worthless!*.cdn.com: Attacker places a file on cdn.com and bypasses CSP
Bypass example:
CSP: script-src 'self' *.github.io;
→ Attacker creates: attacker.github.io/payload.js
→ Injected:<script src="https://attacker.github.io/payload.js"></script>
→ CSP allows it (github.io in wildcard)!
Misconfiguration 3 - JSONP Endpoint in Whitelist
CSP: script-src 'self' https://api.google.com;
- Public JSONP endpoint from google.com → CSP allows it!
- Callback parameter is executed
- Known JSONP bypass endpoints exist on major CDNs
Misconfiguration 4 - Missing Directives
Content-Security-Policy: script-src 'self';
- Without
default-src: other resources (img-src, style-src) are uncontrolled! - CSS injection possible: style-src not set
Misconfiguration 5 - object-src missing
Content-Security-Policy: script-src 'self'; default-src 'self';
- Without
object-src 'none': Browser plugins could be loaded - ALWAYS explicitly set
object-src 'none'!
Misconfiguration 6 - base-uri missing
Content-Security-Policy: script-src 'self';
- Without
base-uri 'self': Base tag injection possible <base href="https://evil.com/">→ all relative URLs point to evil.com!
Nonce-based CSP (Recommended)
How it works
- Server generates a random nonce per request (min. 128 bits!)
- Set the nonce in the CSP header and in the allowed script tag
- Browser executes only script tags with the correct nonce
- XSS payload has no nonce → blocked!
Python (Flask)
import secrets
from functools import wraps
def with_csp(f):
@wraps(f)
def wrapper(*args, **kwargs):
nonce = secrets.token_urlsafe(32) # 256-bit nonce!
g.csp_nonce = nonce
response = f(*args, **kwargs)
response.headers['Content-Security-Policy'] = (
f"script-src 'nonce-{nonce}' 'strict-dynamic'; "
"object-src 'none'; base-uri 'self';"
)
return response
return wrapper
Node.js (Express)
const crypto = require('crypto');
app.use((req, res, next) => {
res.locals.nonce = crypto.randomBytes(32).toString('base64');
res.setHeader('Content-Security-Policy',
`script-src 'nonce-${res.locals.nonce}' 'strict-dynamic'; ` +
`object-src 'none'; base-uri 'self';`);
next();
});
Nonce Security Rules
- NEVER reuse a nonce! (Regenerate one for each request)
- DO NOT hardcode the nonce in URLs, logs, or source code
- Nonce min. 128 bits (22+ Base64 characters) – brute-force-safe
- REMOVE
'unsafe-inline'if nonce is active (nonce makes it obsolete) 'strict-dynamic'+ nonce: dynamically loaded scripts inherit trust
CSP Strict-Dynamic Mode
Purpose
'strict-dynamic' allows dynamically loaded scripts from trusted scripts.
Without strict-dynamic: Trusted script dynamically loads widget.js → widget.js is not in the whitelist → BLOCKED! Many frontend frameworks fail with standard CSP!
With 'strict-dynamic':
Content-Security-Policy: script-src 'nonce-ABC' 'strict-dynamic';
- Nonce script may dynamically load additional scripts
- Loaded scripts inherit the trust of the parent script
- No more need to whitelist external hosts!
Recommended CSP Template (2026 State-of-the-Art)
Content-Security-Policy:
default-src 'none';
script-src 'nonce-{RANDOM}' 'strict-dynamic';
style-src 'self' 'nonce-{RANDOM}';
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.example.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none';
upgrade-insecure-requests;
frame-ancestors 'none': prevents clickjacking!upgrade-insecure-requests: automatically upgrades HTTP to HTTPS
CSP Testing and Deployment
1. CSP Evaluator (Google)
- csp-evaluator.withgoogle.com
- Enter CSP header → automatic vulnerability analysis
- Shows: which directives are missing, which insecure keywords are set
2. Report-Only Mode (Deployment Preparation!)
Content-Security-Policy-Report-Only: script-src 'self' 'nonce-XYZ';
report-to: /csp-violations
- Browser blocks NOTHING (no breaking changes!)
- Sends violation reports for every policy violation
- Collect + analyze what would be blocked
- Then: enable the actual CSP header
Violation Report (JSON):
{
"csp-report": {
"document-uri": "https://example.com/page",
"violated-directive": "script-src",
"blocked-uri": "https://evil.com/payload.js",
"original-policy": "script-src 'self'"
}
}
3. Burp Suite CSP Testing
- Burp Extension: CSP Auditor
- Checks for known bypasses: unsafe-inline, JSONP hosts, wildcards
4. Step-by-Step Migration
- Report-only mode without blocking (collect data for 1–2 weeks)
- Implement real CSP on less critical pages
- Analysis + adjustment
- Rollout to all pages
CSP and Single-Page Applications (SPA)
- React: Configure webpack nonce injection
- Next.js:
next.config.jsCSP header + script nonce in_document.tsx - Vue: Nonce injected into
index.htmlvia server variable - Angular:
angular.jsonsecurity-policy or nonce via server header