Content-Security-Policy ist der mächtigste Header, den ein Browser bietet, und zugleich derjenige, der am häufigsten in einer Form ausgerollt wird, die fast keinen Schutz liefert.
Der Grund: “hat eine CSP” ist im Fragebogen binär und in der Realität ein Kontinuum. Hier ist die Leiter, was jede Stufe verhindert, und wo der Abbruch passiert.
Stufe 0: keine CSP
Keine Richtlinie. Jedes eingeschleuste Skript wird ausgeführt.
Häufiger als vermutet auf authentifizierten Anwendungs-Subdomains, weil die Marketingseite die Sicherheitsprüfung bekam und app. nicht.
Stufe 1: eine CSP mit unsafe-inline
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'
Das ist die Stufe, die die meisten Deployments erreichen und auf der sie bleiben. Sie besteht eine Header-Vorhandensein-Prüfung und verhindert praktisch nichts, denn unsafe-inline erlaubt jedes Inline-Skript, und genau das erzeugt eine Injektion.
Enthält Ihre Richtlinie unsafe-inline in script-src, stehen Sie auf Stufe 1, gleich wie lang der Rest des Headers ist. Behandeln Sie dieses Schlüsselwort als das Wichtigste, das es zu finden und zu entfernen gilt.
Stufe 2: eine Positivliste ohne unsafe-inline
Content-Security-Policy: script-src 'self' https://cdn.example.com https://analytics.example.com
Echter Schutz gegen trivial eingeschleustes Inline-Skript. Zwei strukturelle Probleme.
Die Positivliste wächst. Jeder neue Dienstleister ergänzt eine Domain, und niemand entfernt eine, sodass sie in Richtung “halbes Internet erlaubt” driftet.
Positivlisten sind über ihre eigenen Einträge umgehbar. Ein erlaubtes CDN, das beliebige Nutzerinhalte hostet, oder eine erlaubte Domain mit einem JSONP-Endpunkt oder einem veralteten Framework mit bekanntem Gadget wird zum Ausführungspfad. Das ist gut dokumentiert und der Grund, warum Positivlisten nicht mehr empfohlen werden.
Stufe 3: Nonce plus strict-dynamic
Content-Security-Policy:
script-src 'nonce-{zufall}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none'; base-uri 'self'
Die derzeit empfohlene Form. Jede Antwort trägt einen frischen Nonce; nur Skripte mit diesem Nonce werden ausgeführt. strict-dynamic lässt diese vertrauenswürdigen Skripte eigene Abhängigkeiten laden, ohne dass eine Domainliste gepflegt werden muss, wodurch die Richtlinie aufhört zu wachsen.
Das abschließende https: und 'unsafe-inline' sind bewusste Rückfälle für ältere Browser, die strict-dynamic nicht kennen. Browser, die es kennen, ignorieren beide. Das wirkt in einem Review alarmierend und ist korrekt.
Die begleitenden Direktiven zählen auf dieser Stufe:
object-src 'none'entfernt Ausführungspfade über Plugins.base-uri 'self'verhindert, dass eine<base>-Injektion sämtliche relativen URLs der Seite entführt.frame-ancestors 'none'behandelt Clickjacking genauer als X-Frame-Options.
Stufe 3 zu erreichen erfordert einen Nonce in Ihrer Template-Schicht, was bei einer gewachsenen Anwendung echte Arbeit bedeutet. Es ist die lohnendste Sprosse dieser Leiter.
Stufe 4: Trusted Types
require-trusted-types-for 'script';
trusted-types sanitizer default;
Die Stufen 1 bis 3 steuern, woher Skripte kommen. Keine davon adressiert DOM-XSS, bei dem gar kein Skript geladen wird: Eine Zeichenkette, der die Anwendung bereits vertraut, wird an innerHTML oder ein Äquivalent übergeben und wird zu Code.
Trusted Types lässt diese Senken Zeichenketten verweigern. Der einzige Weg, dorthin zu schreiben, führt über eine benannte Richtlinie, die Sie deklariert haben, wodurch sich “sind alle vierhundert Zuweisungen sicher” in “sind diese drei Richtlinien korrekt” verwandelt.
Beide Direktiven zählen. require-trusted-types-for aktiviert die Durchsetzung; trusted-types legt die Menge erzeugbarer Richtlinien fest, sodass ein kompromittiertes Drittanbieter-Skript keine eigene erzeugen kann. Durchsetzung ohne Positivliste ist ein unvollständiges Deployment.
Der vollständige Rollout-Weg, inklusive Report-only.
Stufe 5: Berichte, und die Integritätsschicht daneben
Eine Richtlinie ohne Berichte ist eine Richtlinie, die Sie nicht pflegen können, denn Verstöße bleiben still und Sie erfahren von Ausfällen durch Nutzer.
report-to csp-endpoint;
Reporting-Endpoints: csp-endpoint="https://example.com/_reports/csp"
Nutzen Sie report-to mit einem Reporting-Endpoints-Header. report-uri ist veraltet, wird aber weiterhin breit unterstützt, sodass es sinnvoll ist, während der Umstellung beide zu senden.
Auf dieser Stufe lohnt es auch, zu benennen, was CSP strukturell nicht kann: Sie schützt nicht vor serverseitigen Schwachstellen und nicht vor einer Lieferkettenkompromittierung einer Skriptquelle, die Sie bereits erlauben. Das Skript kommt von einer erlaubten Herkunft mit gültigem Nonce, und CSP hat keine Meinung zu seinem Inhalt. Diese Lücke deckt Subresource Integrity mit seinem Durchsetzungs-Header ab, keine CSP-Direktive.
Die Leiter auf einen Blick
| Stufe | Form | Verhindert |
|---|---|---|
| 0 | Kein Header | Nichts |
| 1 | unsafe-inline vorhanden | Praktisch nichts |
| 2 | Positivliste | Naive Injektion; über erlaubte Herkünfte umgehbar |
| 3 | Nonce + strict-dynamic | Ausführung eingeschleuster Skripte |
| 4 | + Trusted Types | DOM-XSS an der Senke |
| 5 | + Berichte, + SRI-Durchsetzung | Drift und Manipulation durch Dritte |
Ihre Stufe mit einem Befehl bestimmen
curl -sI https://ihredomain.de | grep -i content-security-policy
Dann der Reihe nach: Steht unsafe-inline in script-src? Gibt es einen Nonce? Ist strict-dynamic vorhanden? Ist require-trusted-types-for vorhanden? Gibt es einen Berichtsendpunkt?
Prüfen Sie Ihre Anwendungs-Subdomain getrennt von der Marketingseite. Sie stehen häufig auf unterschiedlichen Stufen, und diejenige mit den Kundendaten ist oft die niedrigere.
Kurzfassung
Header-Vorhandensein ist kein Kontrollmechanismus. unsafe-inline in script-src setzt Sie auf Stufe 1, gleich was die Richtlinie sonst sagt.
Stufe 3 ist die lohnendste Sprosse. Stufe 4 adressiert eine ganze Angriffsklasse, die die ersten drei nicht berühren.
Von der Theorie zur Praxis
Scannen Sie Ihre Domain kostenlos. Erste Ergebnisse in unter 10 Sekunden — ohne Registrierung.