Content-Security-Policy einrichten: Direktiven, Nonces und Report-Only
Eine Content-Security-Policy sagt dem Browser, woher Deine Seite Skripte, Bilder und andere Inhalte laden darf. Ein eingeschleustes Skript läuft dann ins Leere.
Stand: 05.11.2026
Wogegen CSP schützt
Bei Cross-Site-Scripting (XSS) bringt ein Angreifer eigenen JavaScript-Code in Deine Seite, etwa über ein ungefiltertes Formularfeld oder ein verwundbares Plugin. Der Browser führt den Code aus, als käme er von Dir. So lassen sich Sitzungen übernehmen oder Eingaben mitlesen.
Die Content-Security-Policy (W3C, CSP Level 3) ist ein HTTP-Header mit einer Liste erlaubter Quellen. Was nicht darauf steht, blockiert der Browser. Die Lücke selbst musst Du trotzdem schließen. Bis dahin verhindert die Policy, dass ein eingeschleustes Skript läuft oder Code von fremden Servern nachlädt.
Über die Direktive frame-ancestors regelt die CSP außerdem, wer Deine Seite in einen Frame einbetten darf. Das schützt vor Clickjacking. Dabei legt eine fremde Seite Deine unsichtbar über einen harmlosen Knopf, und der Besucher klickt bei Dir, ohne es zu merken.
Aufbau des Headers
Eine Policy besteht aus Direktiven, getrennt durch Semikolon. Jede nennt die erlaubten Quellen: 'self' für die eigene Origin, Hosts wie https://cdn.beispiel.de oder 'none' für gar nichts. Schlüsselwörter stehen in einfachen Anführungszeichen.
| Direktive | Regelt |
|---|---|
default-src |
Rückfall für alle Ladearten ohne eigene Regel |
script-src |
JavaScript |
style-src |
CSS |
img-src |
Bilder |
connect-src |
Verbindungen per fetch, XHR und WebSocket |
object-src |
alte Plugin-Inhalte (object, embed), am besten 'none' |
base-uri |
erlaubte Werte für das base-Element |
form-action |
wohin Formulare senden dürfen |
frame-ancestors |
wer die Seite einbetten darf |
Die letzten drei fallen nicht auf default-src zurück. Fehlen sie, ist an dieser Stelle alles erlaubt.
Vor frame-ancestors gab es den Header X-Frame-Options mit DENY oder SAMEORIGIN. Er wirkt weiterhin, kennt aber keine Liste erlaubter Seiten. Sind beide gesetzt, gilt frame-ancestors.
Was eine Policy aufweicht
'unsafe-inline' in script-src erlaubt jedes Skript, das direkt im HTML steht. Genau so arbeitet eingeschleuster Code, und die Policy verliert ihren Hauptzweck. 'unsafe-eval' gibt eval() und verwandte Funktionen frei, die Text als Code ausführen. Ähnlich wirken Platzhalter wie *, https: oder data: in script-src: Ein Angreifer braucht dann nur irgendeinen Server mit HTTPS.
Bei style-src ist 'unsafe-inline' ein verbreiteter Kompromiss. Das Risiko ist dort deutlich kleiner.
Nonces, Hashes und 'strict-dynamic'
Für Inline-Skripte, die Du selbst brauchst, gibt es zwei saubere Wege. Eine Nonce ist ein Zufallswert, den der Server bei jeder Antwort neu erzeugt. Er steht im Header als 'nonce-…' und im Attribut nonce jedes erlaubten Skripts. Ein Angreifer kennt ihn nicht. Ein Hash ('sha256-…') erlaubt genau ein Skript mit genau diesem Inhalt und eignet sich für statische Schnipsel.
Steht eine Nonce oder ein Hash in der Direktive, ignorieren aktuelle Browser 'unsafe-inline'. Das Schlüsselwort bleibt dann nur als Rückfall für sehr alte Browser stehen. 'strict-dynamic' überträgt das Vertrauen auf Skripte, die ein erlaubtes Skript selbst nachlädt; Hostlisten und 'self' zählen dann nicht mehr. Das hilft bei Tag-Managern und Widgets, deren Quellen sich laufend ändern.
Content-Security-Policy: script-src 'nonce-k3Jd9fQ2xW' 'strict-dynamic'; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'self'; report-to csp
Reporting-Endpoints: csp="https://beispiel.de/csp-berichte"
Einführen mit Report-Only
Eine scharfe CSP, die etwas Wichtiges blockiert, legt im Zweifel den Warenkorb lahm. Diesen Fall fängt Content-Security-Policy-Report-Only ab. Der Browser wendet die Policy probeweise an, blockiert nichts und meldet Verstöße in der Konsole. Mit report-to und dem Header Reporting-Endpoints schickt er die Meldungen zusätzlich an eine Adresse Deiner Wahl. Ältere Browser kennen nur report-uri, deshalb setzen viele vorerst beides.
Ein Startpunkt für eine Seite ohne fremde Skripte:
Content-Security-Policy-Report-Only: default-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'
Kommen über einige Wochen keine unerwarteten Meldungen mehr, setzt Du dieselbe Policy als Content-Security-Policy. Eine CSP, die für immer im Beobachtungsmodus bleibt, schützt niemanden.
CSP auf einer WordPress-Seite
Auf einer typischen WordPress-Seite meldet der erste Lauf im Report-Only-Modus schnell Dutzende Verstöße. Themes und Plugins schreiben Inline-Skripte ins HTML. Dazu kommen Tag-Manager, Statistik, Cookie-Banner und eingebettete Videos. Geh die Meldungen durch und trag jede Quelle ein, die Du wirklich brauchst. Eine Quelle, die niemand kennt, ist schon ein Befund.
Nonces brauchen Unterstützung vom CMS oder einem Plugin, weil jedes Skript-Element das Attribut bekommen muss. Header und HTML müssen dieselbe Nonce tragen, und zwar bei jeder Antwort eine neue. Ein Seiten-Cache, der fertiges HTML ausliefert, verträgt sich damit schlecht.
Gelingt das nicht auf Anhieb, ist eine Policy mit konkreten Hosts besser als keine. object-src 'none', base-uri 'self' und frame-ancestors 'self' machen fast nie etwas kaputt und gehören sofort auf jede Seite. Bei form-action prüfst Du vorher, ob ein Formular an einen fremden Dienst sendet.
Typische Fehler
frame-ancestorsim Meta-Tag. Eine CSP lässt sich auch permeta http-equivins HTML schreiben, dort ignorieren Browser aberframe-ancestors,report-uriundsandbox. Clickjacking-Schutz gibt es nur per Header.selfohne Anführungszeichen. Der Browser liest das als Hostnamen.- Zwei Policies, etwa vom Webserver und von einem Plugin. Der Browser wendet beide an, jede Quelle muss in beiden erlaubt sein.
default-src 'self'gesetzt und angenommen,frame-ancestorsundform-actionseien damit erledigt.- Eine feste Nonce, die nie wechselt.
CSP prüfen
Gib Deine Adresse bei ZwoWebCheck ein. Die Prüfung ist passiv und wertet nur die Antwort-Header aus, die Dein Server ohnehin jedem Browser schickt. Der Bericht bewertet Deine CSP samt Clickjacking-Schutz neben den übrigen Sicherheits-Headern und zeigt, ob die Policy schon schützt oder Skripte zu großzügig zulässt.