Zum Inhalt springen

Das Magazin für Web-Handwerker Sonntag, 30. August 2026

Sicherheit

Content-Security-Policy: eine Richtlinie bauen, die schützt, ohne die Seite zu zerlegen

Die Content-Security-Policy ist ein HTTP-Header, der die Auswirkung einer Skript-Injektion durch Filtern der erlaubten Quellen begrenzt. Dieser Leitfaden zeigt eine schrittweise Einführung: zentrale Direktiven, Nonces statt unsafe-inline und der Report-Only-Modus, um die Produktion nicht zu zerlegen.

Die Content-Security-Policy (CSP) ist ein HTTP-Antwort-Header, der dem Browser mitteilt, welche Inhaltsquellen er laden und ausführen darf. Gut gesetzt, verwandelt sie eine Skript-Injektionslücke in einen bloß blockierten Eintrag in der Konsole. Missverstanden, bleibt sie eine kopierte Zeile, die nichts aufhält oder die halbe Website zerlegt. Dieser Leitfaden beschreibt eine schrittweise Einführung, von den Direktiven bis zum Testmodus, für ein Fundament, das schützt, ohne die Produktion lahmzulegen.

Was eine Content-Security-Policy absichert

Cross-Site-Scripting (XSS) bleibt eine der verbreitetsten Web-Schwachstellen: Ein Angreifer schafft es, JavaScript in eine Seite einzuschleusen, und dieser Code läuft mit den Rechten des Opfers. Die CSP wirkt als Verteidigung in der Tiefe. Selbst wenn eine Injektion die Eingabevalidierung passiert, weigert sich der Browser, ein Skript auszuführen, dessen Herkunft nicht ausdrücklich von der Richtlinie erlaubt ist. Der Header ersetzt nicht das Escaping der Daten; er fügt eine zweite Barriere hinzu, die die Auswirkung eines Anwendungsfehlers stark begrenzt.

Anatomie des Headers: die wichtigen Direktiven

Eine Richtlinie ist eine durch Semikolons getrennte Liste von Direktiven. Jede Direktive benennt einen Ressourcentyp und seine Liste erlaubter Quellen, ausgedrückt als Schlüsselwörter ('self', 'none') oder als Domains.

Content-Security-Policy: default-src 'self'; img-src 'self' data:; style-src 'self'; connect-src 'self' https://api.beispiel.com; object-src 'none'
DirektiveRolle
default-srcRückfallwert für nicht gesetzte Direktiven
script-srcErlaubte Quellen für JavaScript
style-srcErlaubte Quellen für Stylesheets
img-srcBildquellen (oft 'self' data:)
connect-srcZiele für fetch, XHR, WebSocket, EventSource
frame-ancestorsWer die Seite in einem iframe einbetten darf (Anti-Clickjacking)
base-uriBeschränkt das <base>-Tag, oft 'self'
object-srcAlte Plugins, am besten auf 'none'

Die Direktive frame-ancestors verdient eine gesonderte Erwähnung: Sie ersetzt den alten Header X-Frame-Options und steuert, wer die Seite in einem Rahmen anzeigen darf, was Clickjacking-Angriffen den Boden entzieht.

Die Falle ‚unsafe-inline‘: Nonces und Hashes

Die häufigste Versuchung ist, 'unsafe-inline' zu script-src hinzuzufügen, damit die im HTML eingebetteten Skripte weiter funktionieren. Dieses Schlüsselwort hebt den Kern des Schutzes auf: Es erlaubt genau die Art von Skript, die ein Angreifer einzuschleusen sucht. Zwei Mechanismen erlauben, ein bestimmtes Inline-Skript zuzulassen, ohne allen anderen die Tür zu öffnen. Ein Nonce ist ein Zufallswert, für jede Antwort neu erzeugt, der zugleich im Header und am Tag steht.

<!-- Header: script-src 'nonce-r4Nd0mBase64' 'strict-dynamic' -->
<script nonce="r4Nd0mBase64">
  // dieser konkrete Block ist erlaubt, kein anderes Inline ist es
</script>

Der Hash ('sha256-…') folgt derselben Logik für ein Skript mit festem Inhalt. Das Schlüsselwort 'strict-dynamic' rundet das Konzept ab: Das einem Nonce-Skript gewährte Vertrauen überträgt sich auf die von ihm geladenen Skripte, was eine lange Domain-Liste erspart.

‚unsafe-inline‘ zu script-src hinzuzufügen bedeutet, genau die Art Code zu erlauben, die ein XSS-Angriff einzuschleusen sucht.

Ausrollen ohne die Produktion zu zerlegen: der Report-Only-Modus

Eine strikte Richtlinie auf einen Schlag auf einer bestehenden Website zu aktivieren, führt fast immer zu Brüchen: ein Tracking-Tag, eine externe Schriftart oder ein Drittanbieter-Widget wird blockiert. Der Header Content-Security-Policy-Report-Only löst das. Er wendet die Richtlinie im Beobachtungsmodus an: Der Browser blockiert nichts, meldet aber jeden Verstoß an einen Sammelpunkt. Die Liste der abgelehnten Ressourcen erlaubt, die Richtlinie anzupassen, bevor sie wirklich durchgesetzt wird.

add_header Content-Security-Policy-Report-Only "default-src 'self'; report-uri /csp-report" always;

Achtung: Beginnen Sie auf einer produktiven Website immer mit Report-Only, lassen Sie ihn mehrere Tage laufen, um alle Pfade abzudecken, und wechseln Sie dann zum durchsetzenden Header. Prüfen Sie außerdem, dass der Proxy oder das CDN den Header nicht umschreibt, sonst gilt eine andere Richtlinie als vorgesehen.

Verstoßberichte sammeln und lesen

Der historische Mechanismus, report-uri, bittet den Browser, bei jedem Verstoß ein JSON-Dokument zu senden, das die betroffene Seite, die verletzte Direktive und die blockierte Ressource beschreibt. Der moderne Ansatz koppelt den Header Reporting-Endpoints an die Direktive report-to, die besser in die Reporting-API des Browsers integriert ist. Eine schlanke Server-Route genügt, um diese Sendungen zu sammeln.

{
  "csp-report": {
    "document-uri": "https://beispiel.com/konto",
    "violated-directive": "script-src 'self'",
    "blocked-uri": "https://cdn.dritt.com/widget.js"
  }
}

Das regelmäßige Lesen dieser Berichte bringt die Drittquellen ans Licht, die ausdrücklich erlaubt werden müssen: eine von einem CDN gelieferte Schriftart, eine eingebettete Karte oder ein Video, ein Skript zur Reichweitenmessung. Jede wird zu einer bewussten Zeile der Richtlinie, nie zu einem Freibrief für eine ganze Kategorie.

Ein vernünftiges Startfundament

Für eine klassische, von Nginx ausgelieferte Website kann eine restriktive, aber funktionsfähige Richtlinie als Basis dienen, die Direktive für Direktive nach den Meldungen des Beobachtungsmodus erweitert wird.

add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'; upgrade-insecure-requests" always;

Die Direktive upgrade-insecure-requests bittet den Browser, verbliebene HTTP-Anfragen auf HTTPS umzustellen, ein nützliches Netz während einer Migration. Jede hinzugefügte Quelle sollte einem erkannten Bedarf entsprechen, nie einer aus Bequemlichkeit mit 'unsafe-inline' gelösten Blockade.

Was bleibt

Die CSP ist eine Verteidigungsschicht, die die Auswirkung einer Injektion begrenzt, wenn alles andere versagt hat. Die tragfähige Methode ruht auf drei Grundsätzen: 'unsafe-inline' zugunsten von Nonces oder Hashes verbannen, zuerst in Report-Only ausrollen, um die legitimen Ressourcen zu kartieren, und dann Direktive für Direktive nachziehen. Eine in dieser Reihenfolge aufgebaute Richtlinie schützt dauerhaft, ohne jede Veröffentlichung in eine Jagd nach Regressionen zu verwandeln.

Auf den Websites, die ich betreue, lasse ich die CSP stets mindestens eine Woche im Report-Only-Modus laufen, bevor ich sie durchsetzend mache, und ich bündle die Berichte, um sie in Ruhe zu lesen. Das ist mühsam, aber der einzige Weg, den ich gefunden habe, um das klassische Szenario zu vermeiden: eine strikte Richtlinie an einem Freitagabend aktiviert, und am Montag antwortet das Kontaktformular nicht mehr. Die CSP ist kein Schalter, sie ist eine schrittweise Einstellung. — Simon Janvier

Weiterführend

Referenz der Direktiven und ihrer Syntax: Content-Security-Policy in den MDN Web Docs.

Ebenfalls auf Mail Studio

Teilen LinkedIn Bluesky Hacker News E-mail

Ebenfalls lesenswert