WebCheck

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-ancestors im Meta-Tag. Eine CSP lässt sich auch per meta http-equiv ins HTML schreiben, dort ignorieren Browser aber frame-ancestors, report-uri und sandbox. Clickjacking-Schutz gibt es nur per Header.
  • self ohne 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-ancestors und form-action seien 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.