XSS-Bremse Content Security Policy

Cross-Site-Scripting (XSS) ist eine der größten Plagen, mit denen Webmaster zu kämpfen haben. Der neue Standard "Content Security Policy" soll endlich Abhilfe schaffen.

In Pocket speichern vorlesen Druckansicht
Lesezeit: 14 Min.
Von
  • Hendrik Brummermann
Inhaltsverzeichnis

Moderne Webanwendungen akzeptieren allerhand Benutzereingaben. Ein prominentes Beispiel ist eine Suchfunktion, mit der man etwa das Warensortiment eines Onlineshops durchsuchen kann. Häufig wird die Eingabe auf der Ergebnisseite vom Server zurückgegeben – "Ihre Suche nach Suchbegriff ergab 7 Treffer". Hat der Webentwickler nicht aufgepasst, können Cyber-Ganoven auf diese Weise eigenen Code in die Seite einschleusen. Dann führen etwa Suchbegriffe wie <script>alert("Pwned!")</script> dazu, dass der Browser die Eingabe als Skript interpretiert und im Kontext der Seite ausführt. Deshalb müssen die unvorhersehbaren Benutzereingaben maskiert werden.

Leider ist es bei komplexen Webanwendungen eher die Regel als die Ausnahme, dass die Maskierung an irgendeiner Stelle vergessen wurde oder lückenhaft ist. Das XSS-Cheat-Sheet zeigt eindrucksvoll, auf wie viele Tricks böse Buben zurückgreifen können, um eigene Inhalte in fremde Sites zu schmuggeln. XSS ist ein ernstzunehmendes Sicherheitsrisiko: Angreifer können so etwa Cookies abgreifen, Schadcode verbreiten oder auch Phishing-Formulare in die verwundbare Webseite einbauen. Der Fantasie der Cyber-Kriminellen sind kaum Grenzen gesetzt.

Mit Hilfe einer Content Security Policy legt der Webmaster fest, welche Ressourcen der Browser beim Abruf der Seite laden darf.

Die Content Security Policy (kurz CSP) verhindert XSS, indem sie den Einsatz von Skripten im Quellcode der Seite, den sogenannten Inline-Skripten, verbietet. Sie werden stattdessen in eigene Dateien ausgelagert, wo man sie wesentlich besser im Griff hat. Anschließend legt man über eine Whitelist fest, von welchen Quellen der Browser beim Aufruf der Seite Skripte nachladen und ausführen darf – und ob überhaupt. Doch damit sind die Möglichkeiten der CSP noch längst nicht ausgeschöpft: Es gibt Whitelists für Frames, Bilder, Medien, Plug-ins, CSS-Dateien und Schriftarten. Außerdem kann man festlegen, mit welchen Servern der Browser über XMLHttpRequest, WebSocket und Server-Sent Events (EventSource) sprechen darf.

Eine Content Security Policy definiert der Webserver über den HTTP-Header der ausgelieferten Seite. Bei Apache-Servern definiert man den Header am einfachsten über eine htaccess-Datei, die sich im gleichen Ordner wie die auszuliefernde Webseite oder darüber befindet. Eine einfache Policy nach Version 1.0 der CSP-Spezifikation setzt man durch, indem man der Datei die folgende Zeile hinzufügt:

Header set Content-Security-Policy "default-src 'self'"

Damit erreicht man derzeit vor allem Nutzer von Google Chrome, da dieser Browser der Vorreiter in puncto CSP-Unterstützung ist. Firefox und Safari verstehen die meisten Befehle derzeit über die CSP-Vorabversionen X-Content-Security-Policy respektive X-Webkit-CSP. Um auch die Nutzer dieser Browser schon jetzt zu schützen, kann man dem Webserver befehlen, diese Varianten parallel mit auszuliefern:

Header set X-Content-Security-Policy "default-src 'self'"
Header set X-Webkit-CSP "default-src 'self'"

Traut man aktuellen Browser-Statistiken, profitieren von den obigen drei Zeilen zusammengenommen über 70 Prozent der Internetnutzer in Deutschland. Der Internet Explorer unterstützt seit Version 10 immerhin eine der CSP-Schutzfunktionen, nämlich die Sandbox. Opera kann derzeit noch nichts mit dem CSP-Header anfangen. Das dürfte sich aber mit dem vorstehenden Wechsel auf die WebKit-Engine, die auch Chrome und Safari nutzen, ändern.

Dass noch nicht alle Browser den CSP-Header auswerten, ist kein Grund, mit der Umsetzung zu warten: Das Konzept ist voll abwärtskompatibel. Schenkt der Browser der Policy keine Beachtung, ist die Seite trotzdem uneingeschränkt nutzbar – allerdings ohne das Plus an Schutz.

Bei Verstößen gegen die Policy spricht der Browser Klartext. Das erleichtert die Fehlersuche beim Zusammenbau eigener Richtlinien.

Wer die Schutzfunktion erst mal probeweise einsetzen will, hängt ein "-Report-Only" an den Header-Namen. Dann blockiert der Browser eventuelle Verletzungen der Policy nicht. Sowohl im Report-Only- als auch im regulären Modus gibt der Browser über seine Konsole Laut, wenn es zu Policy-Verstößen kommt. Die Konsole erreicht man etwa bei Chrome und Firefox über Strg+Umschalt+J und das Menü. Die Berichte sind erfreulich ausführlich und eine wertvolle Hilfe auf dem Weg zur individuellen Sicherheitsrichtlinie. Einige der Meldungen gehen unter Umständen auf das Konto von Browser-Erweiterungen, die den Quellcode der geöffneten Seite manipulieren und dabei die CSP verletzen.

Das Beispiel Content-Security-Policy "default-src 'self'" beschränkt das Nachladen sämtlicher Inhaltstypen auf die Domain der Seite (default-src 'self'). Das ist eine gute Ausgangssituation, um eventuell nötige Ausnahmen zu definieren. Soll eine Seite etwa auch von heise.de Skripte nachladen dürfen, fügt man eine entsprechende Skriptquelle (script-src) hinzu:

default-src 'self'; script-src http://heise.de

Der Aufbau ist leicht durchschaubar: Dem Header-Feld "Content-Security-Policy" folgt die sogenannte Direktive des Inhaltstyps und dahinter kommen die Parameter. Jede Direktive kann mehrere Parameter enthalten, die von Leerzeichen getrennt werden. Zumeist handelt es sich bei den Parametern schlicht um Listen der erlaubten Quellen. Das können vollständige URLs, Domains oder auch nur URL-Schemata wie http: oder https: sein. Letzteres erlaubt etwa das Erzwingen von HTTPS.

Direktive Zweck
default-src Standardquellen für Inhalte, sofern keine speziellere Direktive definiert wurde
connect-src Ziele der JavaScript-Objekte XMLHttpRequest, WebSocket und EventSource
font-src erlaubte Quellen für Schriftarten
frame-src erlaubte Quellen für iFrames
img-src erlaubte Quellen für Bilder
media-src erlaubte Quellen für Video und Audio-Dateien
object-src erlaubte Quellen für Flash-, Java-Applets und andere Plug-ins
script-src erlaubte Quellen für JavaScript
style-src erlaubte Quellen für Stylesheets
report-uri Verstöße gegen die Policy werden hier berichtet
sandbox Sandbox-Regeln analog zum sandbox-Attribut in HTML5

Es gibt die folgenden Direktiven:

Wildcards nach dem Muster http://*.heise.de sind laut Spezifikation erlaubt, man sollte sie allerdings ausschließlich im Header "Content-Security-Policy" benutzen – bei den X-Varianten ignoriert der Browser nämlich alles hinter dem Sternchen. Das Beispiel würde dazu führen, dass alle Domains erlaubt sind. Beim Definieren der Ausnahmeregeln ist das CSP-Bookmarklet von Brandon Sterne hilfreich. Es analysiert, welche Inhaltstypen der Browser beim Seitenaufruf von externen Quellen nachlädt und generiert daraus eine passende Whitelist.

Das CSP-Bookmarklet erstellt das Grundgerüst für die eigene Policy.

Darüber hinaus gibt es Schlüsselwörter, die eigenwilligerweise in einfachen Anführungszeichen stehen müssen: 'self' bewirkt, dass der Inhaltstyp nur von der gleichen Quelle nachgeladen werden darf. Der Wert 'none' verbietet den Einsatz eines Inhaltstyps komplett.

Sobald eine Policy definiert wurde, führt der Browser keinen JavaScript-Code mehr aus, der direkt in den HTML-Code der Seite eingebettet ist – sogenannte Inline-Skripte. Das erfordert oft zwar Änderungen an der Seite (siehe "Umbaumaßnahmen"), ist aber auch durchaus sinnvoll: Es vereitelt die häufigste Form von XSS, die zu Beginn des Artikels geschildert wurde. Auch Inline-Stylesheets sind normalerweise durch CSP verboten, da Cyber-Ganoven darüber prinzipiell Daten abgreifen können. Mit 'unsafe-inline' schaltet man InlineSkripte (über die Direktive script-src) oder Inline-CSS (über style-src) wieder an. Wie sich aus dem führenden „unsafe-“ schon erschließt, sollte man von diesen Optionen nach Möglichkeit die Finger lassen.

Eine weitere Default-Einschränkung bei CSP ist das strikte Verbot der eval()-Funktion, die Zeichenketten in ausführbaren JavaScript-Code umwandelt – und damit das Einschleusen von Angriffscode erleichtert. Man schaltet eval() über das Schlüsselwort 'unsafe-eval' bei script-src wieder frei, wenn es sein muss. In der Firefox-Implementierung "X-Content-Security-Policy" werden die unsafe-Schalter nicht als Paramater der Direktiven angegeben, sondern nach folgendem Muster als "options" an die Policy gehängt:

X-Content-Security-Policy: default-src 'self'; options inline-script eval-script.

Inline-CSS ist bei der derzeitigen Firefox-Implementierung erlaubt.

Es sorgt für zusätzliche Sicherheit, den Einsatz nicht verwendeter Inhaltstypen durch den Wert 'none' zu verbieten. Wenn auf der Webseite etwa regulär keine Plug-ins zum Einsatz kommen, sorgt ein object-src 'none' dafür, dass der Browser unter keinen Umständen welche ausführt. Sollte es einem Angreifer beispielsweise gelingen, einen Java-Exploit in die Seite einzuschleusen, wird ein CSP-Browser diesen nicht starten. In Zukunft soll es die Möglichkeit geben, gezielt Plug-ins freizugeben.

Um die CSP wirkungsvoll einsetzen zu können, sind in der Regel kleinere oder größere Änderungen an der Webseite nötig. Statische <script>-Blöcke können relativ einfach in .js-Dateien ausgelagert und über <script type="text/javascript" src="skripte.js"></script> wieder in die Seite eingebettet werden.

Von dem Verbot eingebetteter Skripte sind aber auch alle attributbasierten EventHandler wie onchange betroffen. Der JavaScript-Code im folgenden HTML-Element muss also ebenfalls modifiziert werden:

<input id="i0" class="amount" onchange="updateSum()">

Damit das ausgelagerte Skript über Veränderungen des Elements benachrichtigt wird, muss es mit der Funktion addEventListener() einen Event-Handler registrieren. Allerdings funktioniert das nicht bei allen Browsern: So müsste man etwa beim Internet Explorer bis Version 8 auf attachEvent() ausweichen. Für diese und weitere Browserabweichungen gibt es eine komfortable Lösung: Die jQuery-Bibliothek fängt solche Eigenheiten ab und erlaubt darüber hinaus das bequeme Adressieren von Elementen über CSS-Selektoren. So lässt sich etwa für Input-Felder mit der CSS-Klasse "amount" ein onchange-Event-Handler wie folgt registrieren:

$().ready(function () {
$(".amount").change(function() {
updateSum();
});
});

Wer bislang noch nicht mit jQuery gearbeitet hat, bekommt auf der Projektseite Starthilfe.

Manchmal werden im JavaScript-Code Daten benötigt, die serverseitig dynamisch in den Code eingefügt werden. So schreibt etwa ein PHP-Skript dynamisch zur Laufzeit auf dem Server direkt den Wert 42 in die Variable price. Da der JavaScript-Code beim Einsatz einer CSP jedoch in statischen Dateien abgelegt werden sollte, funktioniert das dynamische Einbetten freilich nicht mehr.

Es gibt jedoch einen Trick: Man fügt die Daten weiterhin in den HTML-Code der Seite ein und greift über die ausgelagerten Skripte anschließend darauf zu. HTML5 erlaubt es, alle Elemente mit selbstdefinierten Attributen zu erweitern, sofern diese mit "data-" beginnen. Der Server kann also den folgenden HTML-Code generieren:

<input id="i1" class="amount" data-price="42">

Mit jQuery werden die data-Attribute dann wie normale Attribute über attr(name) angesprochen:

price=$("#i1").attr("data-price")

In alten Anwendungen, die keine HTML5- Funktionen verwenden dürfen, können unsichtbare input-Felder vom Typ "hidden" verwendet werden:

<input type="hidden" id="p3" value="9">

Die Standard-Policy verbietet es, Zeichenfolgen als JavaScript-Code zu interpretieren. Konkret bedeutet dies, dass die Verwendung von eval() und new Function() tabu ist. Die Funktionen setTimeout() und setInterval() dürfen nur noch mit einem function-Objekt als Parameter verwendet werden. Also beispielsweise statt

setTimeout("alert('vorbei')", 100);

muss diese Variante verwendet werden:

setTimeout(function() { alert("vorbei"); }, 100);

Wer eval() zum Parsen von Daten im JSON-Format nutzt, sollte stattdessen zu der Methode JSON.parse() greifen, die alle aktuellen Browser bieten. Wenn Internet Explorer 7 und älter unterstützt werden sollen, können JSON-Libraries verwendet werden.

Mit Hilfe der Direktive report-uri können die Browser angewiesen werden, Verstöße gegen die Policy zu berichten. Dieses Feature ist zum einen während der Entwicklung und im Pilotbetrieb hilfreich, um Fehler in der Policy zu finden. Zum anderen erlaubt es die Erkennung von Angriffen im Produktivbetrieb. Als kleiner Wermutstropfen bleibt allerdings, dass auch Browser-Extensions Verstöße gegen die Policy erzeugen können. Somit handelt es sich nicht bei jedem Report um einen Policy-Fehler oder Angriff.

Das Ziel muss aus Sicherheitsgründen in der gleichen Domain wie die Webseite liegen. Damit der Browser die Berichte verschickt, ergänzt man die Policy zum Beispiel um report-uri rauchmelder.php. Das sorgt dafür, dass Verletzungen der Policy künftig als POST-Request an rauchmelder.php geschickt werden. Die Daten sind JSON-formatiert und sogar noch etwas ausführlicher als die Warnungen, die über die Konsole ausgegeben werden.

Das Skript rauchmelder.php könnte den Report zum Beispiel in das Error-Log des Webservers schreiben:

<?php
error_log('CSP-Report von '.$_SERVER['REMOTE_ADDR'].': User Agent: .$_SERVER['HTTP_USER_AGENT'].' Fehlerbericht: '.file_get_contents('php://input'));
?>

Auf die PHP-Fehlerberichte hat man häufig auch dann Zugriff, wenn man Webspace angemietet hat. Bei 1&1 etwa muss man dazu das Error-Reporting zunächst über eine Datei namens php.ini aktivieren, die sich im gleichen Verzeichnis wie das PHP-Skript befindet. In diesem Fall muss man als absoluten Pfad angeben, wo der Fehlerbericht gespeichert werden soll. Das Grundverzeichnis erfährt man über den PHP-Befehl echo($_SERVER['‚REDIRECT_DOCUMENT_ROOT']);. Damit nur der Webmaster erfährt, an welchen Stellen die Webseite potenziell verwundbar ist, muss der Fehlerbericht in einem geschützten Verzeichnis gespeichert werden.

Durch eine Content Security Policy können Webseitenbetreiber ihre Besucher mit überschaubarem Aufwand vor Cross-Site-Scripting und weiteren Angriffsformen schützen. Und das lohnt sich schon jetzt, weil man mit den oben beschriebenen Maßnahmen die meisten Nutzer erreicht.

Durch die umfangreichen Reporting-Funktionen kommt man Fehlern während und nach der Umstellung leicht auf die Schliche. Problematisch kann CSP allerdings für Nutzer in Mobilfunknetzen werden: Unter Umständen sorgen die Kompressionsproxies der Handynetzbetreiber dafür, dass ausgelagerte Skripte und Stylesheets wieder fest mit dem Quellcode der Seite verwoben werden. Wenn der Proxy den CSP-Header durchschleift, werden die ursprünglich ausgelagerten Elemente dann blockiert.

Trotz CSP darf man nicht vergessen, dass eine Policy allein den Webseitenbesucher schützt – und nicht die Webseite oder den Server. Als Betreiber muss man weiterhin alles dafür tun, um den Server abzusichern. Das bedeutet, vor allem sämtliche Komponenten auf dem aktuellen Stand zu halten und Nutzereingaben sorgfältig zu filtern – insbesondere dann, wenn sie in SQL-Statements münden.

Derzeit diskutiert die Web Application Security Working Group mögliche Erweiterungen für Version 1.1 der CSP-Spezifikation. Unter anderem wird überlegt, ob Script- und Style-Elemente mit einem Zufallswert versehen werden, um sie doch wieder direkt in den HTML-Code einbetten zu können. Außerdem steht ein Meta-Tag als Ersatz für den HTTP-Header zur Diskussion. Es soll zusätzlich weitere Direktiven geben, um das Ziel von form-actions und die MIME-Typen von Plug-ins einzuschränken. (rei)