Content Security Policy praktisch umsetzen: CSP für JavaScript-Anwendungen
AI generated
JS
() =>
JavaScript · Web Security · HTTP Header
Content Security Policy praktisch umsetzen
von der ersten Direktive bis zum Enforcement

Eine Content Security Policy ist der wirksamste einzelne HTTP Header gegen Cross Site Scripting, wird aber in den meisten Projekten entweder gar nicht oder mit einer viel zu großzügigen Whitelist eingesetzt. Dieser Artikel zeigt, wie eine Content Security Policy von der ersten Direktive über Nonces und Hashes bis zum harten Enforcement Modus entsteht, ohne dass Inline Skripte oder Drittanbieter Tools plötzlich blockiert werden.

18 Min. Lesezeit script-src · nonce · strict-dynamic · Reporting Chrome · Firefox · Safari · Edge

1. Was Content Security Policy wirklich leistet

Wer eine Content Security Policy zum ersten Mal einführt, unterschätzt häufig, wie viele bestehende Codepfade implizit von einer fehlenden Absicherung profitiert haben. Genau deshalb lohnt sich eine schrittweise Einführung, statt die Policy an einem einzigen Tag vollständig scharf zu schalten.

Eine Content Security Policy ist ein HTTP Response Header, mit dem eine Anwendung dem Browser mitteilt, welche Quellen für Skripte, Styles, Bilder und weitere Ressourcen erlaubt sind. Anders als ein Web Application Firewall Regelwerk wirkt eine Content Security Policy direkt im Browser des Nutzers, nachdem die Seite bereits ausgeliefert wurde. Sie ist damit die letzte Verteidigungslinie gegen Cross Site Scripting, selbst wenn eine Injection Schwachstelle im Backend unentdeckt geblieben ist.

Der praktische Nutzen zeigt sich am deutlichsten in einem konkreten Szenario: Ein Angreifer schleust über ein ungefiltertes Formularfeld ein Skript Tag in eine Seite ein. Ohne Content Security Policy führt der Browser dieses Skript klaglos aus und sendet Session Cookies an eine fremde Domain. Mit einer korrekt konfigurierten Content Security Policy verweigert der Browser die Ausführung, weil das injizierte Skript keiner erlaubten Quelle, keinem gültigen Nonce und keinem passenden Hash entspricht. Genau dieser Unterschied macht eine Content Security Policy zu einem der wirksamsten Bausteine moderner JavaScript Sicherheit.

2. Die wichtigsten CSP Direktiven im Detail

Die zentrale Direktive jeder Content Security Policy ist script-src, die festlegt, aus welchen Quellen JavaScript ausgeführt werden darf. Fehlt script-src, greift als Fallback default-src, was in der Praxis selten präzise genug ist. Daneben regelt style-src Stylesheets und Inline Styles, img-src Bildquellen, connect-src Ziele für Fetch, XHR und WebSocket Verbindungen, und frame-ancestors ersetzt den veralteten X Frame Options Header für die Kontrolle über Einbettung in iframes.

Eine oft unterschätzte Direktive ist object-src 'none', die Plugins wie Flash oder veraltete Java Applets komplett unterbindet und in praktisch jeder modernen Content Security Policy gesetzt werden sollte. base-uri 'self' verhindert, dass ein Angreifer das base Element manipuliert und dadurch relative URLs auf eine fremde Domain umleitet. Die Kombination dieser Direktiven bildet das Grundgerüst, bevor überhaupt über Nonces oder Hashes nachgedacht wird.


Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.mironsoft.de;
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https://images.mironsoft.de;
  connect-src 'self' https://api.mironsoft.de;
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'self';
  upgrade-insecure-requests;

Diese Basis Konfiguration blockt bereits einen großen Teil naiver Injection Versuche, lässt aber noch Raum für Verbesserung: 'unsafe-inline' bei style-src ist ein Kompromiss, den man später durch Nonces oder Hashes ersetzen sollte, sobald das Projekt so weit ist.

3. Nonce basierte CSP für serverseitig gerenderte Skripte

Ein Nonce ist ein zufälliger, pro Request neu generierter Wert, der sowohl im Content-Security-Policy Header als auch im nonce Attribut jedes erlaubten Skript Tags erscheinen muss. Nur Skripte mit dem korrekten Nonce werden vom Browser ausgeführt, alle anderen Inline Skripte, auch von Angreifern eingeschleuste, werden verworfen. Der entscheidende Vorteil gegenüber einer reinen Domain Whitelist: Ein Angreifer kann den Nonce nicht erraten, weil er pro Response neu erzeugt wird und niemals wiederverwendet werden darf.

In serverseitig gerenderten Anwendungen, etwa mit Magento Templates oder einem Node basierten Rendering Layer, wird der Nonce zentral im Request Kontext erzeugt und sowohl in den Header als auch in jedes Template injiziert. Wichtig ist, dass der Nonce niemals in clientseitigem JavaScript berechnet wird, denn dann könnte ihn auch ein Angreifer aus dem Quelltext auslesen. Der Nonce muss aus einer kryptographisch sicheren Zufallsquelle stammen, mindestens 128 Bit Entropie besitzen und bei jedem einzelnen Seitenaufruf neu generiert werden.


// Node/Express middleware: generate a per-request nonce
const crypto = require('crypto');

app.use((req, res, next) => {
  // 16 random bytes, base64 encoded, unique per response
  res.locals.cspNonce = crypto.randomBytes(16).toString('base64');
  res.setHeader(
    'Content-Security-Policy',
    `script-src 'self' 'nonce-${res.locals.cspNonce}' 'strict-dynamic'; object-src 'none'; base-uri 'self';`
  );
  next();
});

// In the template engine, print the nonce on every allowed script tag
// <script nonce="<%= cspNonce %>">/* application bootstrap */</script>

4. Hash basierte CSP für statische Inline Skripte

Wenn Inline Skripte fest im Quelltext stehen und sich zwischen Deployments nicht ändern, ist ein Hash oft praktischer als ein Nonce. Die Content Security Policy erlaubt dabei nur Skripte, deren SHA256, SHA384 oder SHA512 Hash exakt mit dem im Header angegebenen Wert übereinstimmt. Ändert sich auch nur ein einziges Zeichen im Skript Inhalt, etwa Whitespace oder ein Kommentar, ändert sich der Hash, und die Content Security Policy blockiert das Skript beim nächsten Aufruf.

Diese Empfindlichkeit ist gleichzeitig Stärke und Schwäche des Hash Ansatzes: Sie verhindert zuverlässig, dass ein manipuliertes Skript unbemerkt durchrutscht, erfordert aber einen Build Schritt, der Hashes automatisch neu berechnet und in den Header oder in Meta Tags einträgt. Für kleine, stabile Inline Skripte wie ein Analytics Bootstrap Snippet oder ein Feature Detection Script ist der Hash Ansatz die robustere Wahl gegenüber einem Nonce, weil kein Server Kontext für die Generierung nötig ist.


# Compute the SHA256 hash of an inline script for CSP hash-source
# The hash must cover exactly the text node content between <script> tags
echo -n "console.log('inline bootstrap');" | \
  openssl dgst -sha256 -binary | openssl base64

# Resulting header entry:
# script-src 'self' 'sha256-<base64-hash-here>';

# Multiple hashes can be listed for several distinct inline scripts
# script-src 'self' 'sha256-<hash-one>' 'sha256-<hash-two>';

Ein Build Skript, das bei jedem Deployment automatisch über alle Inline Skripte iteriert und die aktuellen Hashes neu berechnet, verhindert, dass ein vergessenes Hash Update die Content Security Policy nach der nächsten Codeänderung lahmlegt. Diese Automatisierung ist besonders wichtig, sobald mehrere Teams gleichzeitig an unterschiedlichen Inline Skripten derselben Seite arbeiten.

5. strict-dynamic und die Ablösung von Domain Whitelists

Domain Whitelists wie script-src https://a.example.com https://b.example.com gelten heute als Anti Pattern, weil praktisch jede große Domain irgendwo eine offene Redirect oder JSONP Lücke besitzt, die ein Angreifer als Sprungbrett missbraucht. Das Schlüsselwort 'strict-dynamic' löst dieses Problem, indem es Vertrauen entlang der Ausführungskette weitergibt: Ein Skript, das mit gültigem Nonce oder Hash geladen wurde, darf selbst weitere Skripte per document.createElement('script') nachladen, ohne dass diese erneut explizit gelistet werden müssen.

Der Clou an 'strict-dynamic' liegt darin, dass klassische Domain Angaben in modernen Browsern dann ignoriert werden, was Rückwärtskompatibilität mit älteren Browsern über einen doppelten Eintrag sicherstellt: script-src 'nonce-xyz' 'strict-dynamic' https://fallback.example.com. Moderne Browser respektieren nur den Nonce und 'strict-dynamic', ältere Browser ohne Unterstützung fallen auf die Domain Liste zurück. Diese Kombination gilt inzwischen als Referenz Implementierung für Google, GitHub und andere große Plattformen.

Für Magento und Hyvä basierte Frontends bedeutet die Einführung von 'strict-dynamic' konkret, dass Layout XML generierte Inline Skripte zunächst mit einem serverseitig erzeugten Nonce versehen werden müssen, bevor die Content Security Policy überhaupt greifen kann. Erst danach lässt sich die Domain Whitelist schrittweise reduzieren, ohne bestehende Drittanbieter Integrationen im Checkout zu gefährden.

6. Report Only Modus und Violation Reporting

Bevor eine Content Security Policy im Enforcement Modus scharf geschaltet wird, sollte sie über den Header Content-Security-Policy-Report-Only beobachtet werden. In diesem Modus blockiert der Browser nichts, meldet aber jede Verletzung an einen konfigurierten Endpunkt. So lassen sich vergessene Inline Skripte, dynamisch nachgeladene Third Party Widgets und unbekannte Analytics Snippets identifizieren, bevor sie durch die scharfe Policy im Live Betrieb ausfallen.

Die moderne Reporting API ersetzt das ältere report-uri Format durch report-to in Kombination mit einem separaten Reporting-Endpoints Header, der strukturierte JSON Berichte liefert. Jeder Bericht enthält die blockierte URI, die verletzte Direktive und die Zeile im Dokument. In der Praxis lohnt es sich, Reports für einige Wochen im Report Only Modus zu sammeln, bevor der Wechsel zum Enforcement Modus erfolgt, weil seltene Code Pfade sonst leicht übersehen werden.


Reporting-Endpoints: csp-endpoint="https://mironsoft.de/csp-reports"
Content-Security-Policy-Report-Only:
  script-src 'self' 'nonce-abc123' 'strict-dynamic';
  report-to csp-endpoint;

7. Typische Fehler bei der CSP Einführung

Der häufigste Fehler ist der pauschale Griff zu 'unsafe-inline' für script-src, was die gesamte Schutzwirkung der Content Security Policy gegen Cross Site Scripting zunichte macht. Sobald 'unsafe-inline' gesetzt ist, kann jedes injizierte Inline Skript ausgeführt werden, unabhängig von Nonce oder Hash. Ein zweiter klassischer Fehler ist das Testen der Policy nur im eigenen Browser, ohne Safari und ältere Edge Versionen zu berücksichtigen, die manche Direktiven wie strict-dynamic unterschiedlich strikt auslegen.

Ein dritter Fehler betrifft Inline Event Handler wie onclick="tuEtwas()" in HTML Attributen: Diese werden von script-src ebenfalls als Inline Skript gewertet und ohne Nonce blockiert, was in Legacy Codebasen zu unerwarteten Ausfällen führt. Die Lösung ist die konsequente Migration zu addEventListener, was ohnehin dem modernen JavaScript Stil entspricht. Wer die Content Security Policy Schritt für Schritt über den Report Only Modus einführt, findet diese Stellen vor dem produktiven Rollout.

8. CSP und Drittanbieter Skripte im Zusammenspiel

Drittanbieter Skripte wie Tag Manager, Payment Widgets oder Chat Systeme sind die größte praktische Hürde bei der Einführung einer strengen Content Security Policy, weil sie häufig selbst wieder Skripte nachladen, Inline Styles setzen oder eigene iframes einbetten. Jedes dieser Verhaltensmuster muss explizit in der Policy berücksichtigt werden, sonst funktioniert das Drittanbieter Skript nicht mehr korrekt, ohne dass eine offensichtliche Fehlermeldung erscheint.

Ein bewährter Ansatz ist, jedes Drittanbieter Skript einzeln im Report Only Modus zu beobachten und dessen tatsächliche Anforderungen zu dokumentieren, statt präventiv eine große Domain Liste zu übernehmen. Viele Tag Manager Anbieter unterstützen inzwischen selbst 'strict-dynamic' kompatible Ladepfade, sodass ein einziger Nonce am initialen Tag Manager Skript ausreicht, um die gesamte nachgeladene Kette abzudecken. Diese Kompatibilität sollte vor der produktiven Einführung explizit mit dem Anbieter geklärt werden.

9. CSP Strategien im direkten Vergleich

Die drei gängigen Strategien, Domain Whitelist, Nonce und Hash, unterscheiden sich deutlich in Wartungsaufwand, Sicherheitsniveau und Eignung für bestimmte Skript Typen. Die folgende Tabelle fasst die praktischen Unterschiede zusammen.

Strategie Sicherheitsniveau Wartungsaufwand Geeignet für
Domain Whitelist Niedrig Gering, aber Lücken durch offene Redirects Legacy Projekte ohne Build Schritt
Nonce basiert Hoch Server muss Nonce pro Request erzeugen Dynamisch gerenderte Seiten
Hash basiert Hoch Build Schritt für Hash Neuberechnung nötig Statische, unveränderliche Inline Skripte
strict-dynamic + Nonce Sehr hoch Einmalige Einrichtung, danach gering Moderne Anwendungen mit dynamischem Nachladen

In der Praxis führt der Weg fast immer über eine Kombination: Zunächst eine Domain Whitelist im Report Only Modus, dann schrittweise Migration zu Nonces für dynamische Inhalte und Hashes für stabile Inline Skripte, bis am Ende 'strict-dynamic' die letzten Whitelist Einträge überflüssig macht. Diese Migration lässt sich über Wochen verteilen, ohne den Live Betrieb zu gefährden.

Mironsoft

JavaScript Sicherheit, CSP Rollout und Frontend Hardening

Eine Content Security Policy, die wirklich schützt, ohne die Anwendung zu blockieren?

Wir analysieren bestehende Anwendungen, entwerfen eine passende Content Security Policy und begleiten die Migration von Report Only bis zum harten Enforcement Modus, inklusive Drittanbieter Integration.

CSP Audit

Analyse bestehender Skripte, Styles und Drittanbieter Integrationen

Nonce Integration

Serverseitige Nonce Generierung für Templates und Rendering Layer

Reporting Setup

Violation Reporting Endpunkt und Monitoring für laufenden Betrieb

10. Zusammenfassung

Eine Content Security Policy ist kein einmalig gesetzter Header, sondern ein iterativer Prozess von der ersten Domain Whitelist über Nonces und Hashes bis zum 'strict-dynamic' Enforcement Modus. Der Report Only Modus ist dabei das wichtigste Werkzeug, um Verletzungen sichtbar zu machen, bevor sie den Live Betrieb stören. Nonces eignen sich für dynamisch gerenderte Skripte, Hashes für stabile Inline Inhalte, und 'strict-dynamic' löst das Problem nachgeladener Skripte, ohne jede Domain einzeln pflegen zu müssen.

Der größte Hebel liegt darin, Drittanbieter Skripte frühzeitig einzeln zu prüfen und die Content Security Policy nicht als einmaliges Sicherheitsprojekt, sondern als dauerhaften Bestandteil des Deployments zu behandeln. Jede neue Integration, jedes neue Analytics Tool und jedes neue Widget muss gegen die bestehende Policy getestet werden, bevor es produktiv geschaltet wird. Diese Disziplin macht den Unterschied zwischen einer Policy, die auf dem Papier existiert, und einer, die tatsächlich vor Cross Site Scripting schützt.

Content Security Policy praktisch umsetzen — Das Wichtigste auf einen Blick

Direktiven

script-src, object-src 'none' und base-uri 'self' bilden das Fundament jeder Content Security Policy.

Nonce vs. Hash

Nonce für dynamisch gerenderte Skripte, Hash für stabile Inline Inhalte ohne Build Abhängigkeit vom Request Kontext.

strict-dynamic

Erlaubt Nachladen von Skripten entlang einer vertrauten Kette, ersetzt fragile Domain Whitelists.

Rollout

Immer zuerst Report Only Modus mit Reporting Endpunkt, erst danach in den Enforcement Modus wechseln.

11. FAQ: Content Security Policy praktisch umsetzen

1Was ist eine Content Security Policy?
Ein HTTP Header, der erlaubte Quellen für Skripte und Ressourcen festlegt und im Browser als letzte Verteidigungslinie gegen Cross Site Scripting wirkt.
2Warum ist unsafe-inline gefährlich?
Es erlaubt jedes Inline Skript ohne Prüfung und hebelt die gesamte Schutzwirkung gegen Cross Site Scripting aus.
3Nonce oder Hash?
Nonce für dynamisch gerenderte Seiten, Hash für statische, unveränderliche Inline Skripte.
4Was macht strict-dynamic?
Gibt Vertrauen entlang der Ausführungskette weiter, sodass geladene Skripte weitere Skripte nachladen dürfen.
5Wie teste ich eine neue Policy sicher?
Mit Content-Security-Policy-Report-Only: blockiert nichts, meldet aber jede Verletzung an einen Endpunkt.
6Blockiert CSP onclick Attribute?
Ja, ohne Nonce werden Inline Event Handler blockiert. Migration zu addEventListener ist die Lösung.
7Wie gehe ich mit Drittanbieter Skripten um?
Jedes Skript einzeln im Report Only Modus beobachten statt pauschal eine große Whitelist anzulegen.
8Ersetzt frame-ancestors X-Frame-Options?
Ja, mit feingranularerer Kontrolle über erlaubte Einbettungen in iframes.
9Wie oft neuen Nonce generieren?
Bei jedem Seitenaufruf neu, aus kryptographisch sicherer Zufallsquelle mit mindestens 128 Bit Entropie.
10Wie lange Report Only Phase?
Mehrere Wochen, damit seltene Code Pfade und Drittanbieter Integrationen erfasst werden.