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:

HTTP csp-basic.http
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:

DirektiveWas sie kontrolliert
default-srcDefault für alle nicht-spezifizierten Direktiven
script-srcJavaScript-Quellen (am wichtigsten für XSS-Schutz)
style-srcCSS-Quellen
img-srcBild-Quellen
connect-srcXHR/fetch/WebSocket-Ziele
font-srcSchrift-Quellen
media-srcAudio/Video-Quellen
object-srcPlugins (Flash, Java) — meist 'none'
frame-src / child-srcErlaubte iframe-Quellen
frame-ancestorsWer darf uns einbetten (Clickjacking-Schutz)
form-actionErlaubte Form-Ziele
base-uriErlaubte <base>-URLs
upgrade-insecure-requestsHTTP → HTTPS-Upgrade erzwingen
report-uri / report-toWohin Violations gemeldet werden

Source-Werte für Direktiven:

  • 'none' — gar keine Quelle erlaubt.
  • 'self' — gleiche Origin.
  • Konkrete URLshttps://cdn.example, https://api.partner.example.
  • Wildcards*.example.com (Subdomain-Wildcard), * (alles — vermeiden).
  • Schema-Wertehttps:, data:, blob:.
  • 'unsafe-inline' — erlaubt Inline-Skripte/-Styles (vermeiden — siehe nächster Abschnitt).
  • 'unsafe-eval' — erlaubt eval() und Function() (vermeiden).
  • 'nonce-&#123;wert&#125;' — erlaubt Skripte mit passendem nonce-Attribut.
  • 'sha256-&#123;hash&#125;' — 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:

HTML csp-nonce-html.html
<!-- Server-Render -->
<script nonce="r4nd0m-v4lu3-per-request">
  console.log('Legitimes Inline-Skript');
</script>
HTTP csp-nonce-header.http
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:

HTTP csp-hash.http
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:

HTTP csp-strict-dynamic.http
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.

HTTP csp-report-only.http
Content-Security-Policy-Report-Only: script-src 'nonce-xyz' 'strict-dynamic'; report-uri /csp-report

Der 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:

HTTP csp-report-to.http
Reporting-Endpoints: csp-endpoint="https://example.com/csp-report"
Content-Security-Policy: default-src 'self'; report-to csp-endpoint

Browser-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="...") durch addEventListener ersetzen.
  • eval/Function durch 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-OnlyContent-Security-Policy umschalten.
  • Weiter Reports beobachten — neue Violations können bei Feature-Updates auftauchen.

Häufige Verschlimmerungen:

  • 'unsafe-inline' in script-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 in img-src akzeptabel, nirgendwo sonst.
  • data: in script-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 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:

HTML sri-example.html
<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

/ Weiter

Zurück zu XSS & Content Injection

Zur Übersicht