Web-Security: Mit Content Security Policy gegen Cross-Site Scripting, Teil 1
Seite 2: CSP Script Hashes
Viele Anwendungen nutzen Inline-Skriptblöcke, um legitimen JavaScript-Code zu laden. Das folgende Snippet zeigt ein vereinfachtes Beispiel:
// index.html
<button id="button">Say Hello!</button>
<script>
document.addEventListener("DOMContentLoaded", () => {
document.getElementById("button")
.addEventListener("click", () => { alert("Clicked")});
})
</script>
Die bisherige CSP-Richtlinie würde diesen JavaScript-Code blockieren, da es sich um einen grundsätzlich geblockten Inline-Skriptblock handelt. Um ihn ohne Sicherheitseinschränkung auszuführen, kann CSP Level 2 den Hash eines Skriptblocks in die Richtlinie einbeziehen.
Dazu wird ein Hash-Wert (z. B. SHA-256) des regulären Inline-Skripts berechnet, der als digitale Signatur für das Skript dient. Um das Skript freizugeben, fügt man den Hash-Wert in die CSP-Richtlinie der Webseite ein.
Folgende CSP-Richtlinie gestattet die AusfĂĽhrung des Codeblocks:
Content-Security-Policy:
script-src 'self' 'sha256-xd4UE9RSFsHsg3ZcbtC21uvtZ++ffUUSxChTCyIHZpA='
Wenn der Browser eine Seite lädt, berechnet er die Hash-Werte der Inline-Skripte und vergleicht sie mit den in der CSP angegebenen Werten. Nur wenn die Hash-Werte übereinstimmen, führt der Browser das Skript aus. Andernfalls blockiert er die Ausführung, da er den Code als nicht autorisiert betrachtet.
Die Sicherheit dieses Mechanismus beruht auf den Eigenschaften der Hashfunktion: Nur genau dieser JavaScript-Code erzeugt den Hash, den man in der Richtlinie definiert hat. Selbst eine minimale Änderung, wie das Hinzufügen eines Leerzeichens, würde den Hash des Codes komplett verändern.
Um mit Skript-Hashes festzulegen, welche spezifischen Inline-Skripte auf der Webseite ausgefĂĽhrt werden dĂĽrfen, muss man die Hashes aller Skripte generieren und dem CSP-Header hinzufĂĽgen. Als Hilfsmittel gibt es zahlreiche Onlinewerkzeuge wie den Online Script and Style Hasher von ReportURI. Normalerweise erzeugen automatisierte Build-Tools die Hashes.
In einem Chromium-basierten Browser findet sich der erwartete Hash in den Fehlermeldungen der Entwicklerkonsole:
Refused to execute inline script because it violates the following Content Security Policy directive: "script-src 'self'".
Either the 'unsafe-inline' keyword, a hash ('sha256-xd4UE9RSFsHsg3ZcbtC21uvtZ++ffUUSxChTCyIHZpA='), or a nonce ('nonce-...') is required to enable inline execution.
Das bedeutet Folgendes: Der Browser hat das Inline-Skript blockiert, weil es die CSP-Direktive script-src 'self' verletzt. Um den Code auszufĂĽhren, ist entweder das SchlĂĽsselwort 'unsafe-inline', der Hash sha256-xd4UE9RSFsHsg3ZcbtC21uvtZ++ffUUSxChTCyIHZpA= oder ein Nonce (nonce-...) erforderlich.
Wer sich sicher ist, dass der Hash in der Nachricht zu einem legitimen Codeblock gehört, kann ihn in die CSP-Richtlinie einfügen.
Auch wenn sich CSP-Hashes gut für die Kontrolle kleiner Inline-Skripte eignen, kann es sehr aufwendig sein, den Hash nach einer Änderung am Skript immer wieder neu zu berechnen. Darüber hinaus führen viele Inline-Skripte mit individuellen Hash-Werten zu einer aufgeblähten CSP. Daher gibt es noch eine weitere Variante, um die Ausführung von Inline-Skripten zu erlauben.
Hier kommen die Nonces ins Spiel, die beispielsweise folgendermaĂźen im HTML einer legitimen Anwendung erscheinen:
<button id="button">Say Hello!</button>
<script nonce=2104tk118mk>
document.addEventListener("DOMContentLoaded", () => {
document.getElementById("button")
.addEventListener("click", () => { alert("Clicked")});
})
</script>
Wenn der Server die Website generiert, erstellt er einen zufälligen Nonce-Wert. Dieser Wert wird sowohl in den CSP-Header der Webseite eingefügt als auch als Attribut in die entsprechenden <script>-Tags integriert:
Content-Security-Policy: script-src 'nonce=2104tk118mk'
Wenn der Browser eine Webseite lädt und auf ein Skript mit einem Nonce-Attribut stößt, vergleicht er den Nonce-Wert des Skripts mit dem in der CSP spezifizierten Wert. Nur wenn die Nonce-Werte übereinstimmen, führt der Browser das Skript aus. Das stellt sicher, dass ausschließlich die Skripte ausgeführt werden, die der Server speziell für diese Anfrage genehmigt hat.
Wichtig ist, dass Nonces bei jedem Seitenaufruf neu generiert werden. Nonces sollten aus einer kryptografisch sicheren Zufallsquelle stammen und dürfen niemals wiederverwendet werden. Andernfalls könnte ein Angreifer den Nonce vorhersagen und ihn in Schadcodeblöcken einbinden.
Im Gegensatz zu Hashes lassen sich Nonces auch bei script-Tags einsetzen, die ĂĽber das src-Attribut eine externe JavaScript-Datei in den Browser laden sollen:
<script src="https://my-site/index.js" nonce="nonce=2104tk118mk"></script>
Hierbei genĂĽgt ein Nonce im script-Tag, um dem Browser mitzuteilen, dass dieser Code legitim ist.
Unsichere Einbindung
In der oben gezeigten Fehlermeldung zum fehlenden Hash schlägt der Browser vor, der script-src-Direktive 'unsafe-inline' als Wert hinzuzufügen, statt CSP-Hashes oder CSP-Nonces zu verwenden. Dieses Vorgehen gilt jedoch als unsicher.
Der Wert 'unsafe-inline' bezieht sich speziell auf die Ausführung von Inline-Skripten und -Styles. Wer 'unsafe-inline' in der CSP-Richtlinie für Skripte (script-src) verwendet, erlaubt explizit die Einbettung und Ausführung von Inline-Skripten auf der Webseite. Das ermöglicht zwar die Ausführung legitimer Anwendungsskripte, öffnet aber gleichzeitig die Tür für eingeschleusten, bösartigen Code. Letztlich hebt 'unsafe-inline' die Schutzmaßnahmen von CSP weitgehend auf.
Trotzdem ist 'unsafe-inline' in vielen CSP-Richtlinien gängig. Das liegt daran, dass viele ältere und auch einige moderne Webanwendungen stark von Inline-JavaScript abhängig sind. Die Umstellung auf eine Architektur, die mit CSP ohne die explizite Erlaubnis zum Ausführen von Inline-Skripten kompatibel ist, erfordert häufig umfangreiche Änderungen im Code, um Skripte in externe Dateien auszulagern. 'unsafe-inline' ermöglicht diesen Anwendungen, CSP zu implementieren, ohne sofort umfangreiche Anpassungen vornehmen zu müssen.
Allerdings haben Legacy-Browser wie der Internet Explorer die CSP Level 2 nicht implementiert, was bedeutet, dass weder CSP Nonces noch CSP Hashes ausgewertet werden. Das fĂĽhrt zu ungewolltem Verhalten, wenn die CSP-Regeln nur auf diese Mechanismen setzen.
Daher findet man in vielen CSP-Richtlinien sowohl Nonces als auch 'unsafe-inline', obwohl Letzteres den Sicherheitsvorteil von CSP untergräbt und ausschließlich mit aufgenommen werden sollte, wenn man Legacy-Browser unterstützen muss. Moderne Browser ignorieren glücklicherweise ein gesetztes 'unsafe-inline', sobald ein CSP-Hash oder CSP-Nonce-Wert gesetzt ist.
Gute zweite Verteidigungslinie
Die Content Security Policy bietet einen zusätzlichen Schutz vor XSS-Schwachstellen. Die erste Verteidigungslinie muss jedoch die Webanwendung selbst übernehmen. Die ursprüngliche CSP schützt zwar vor Inline-Skripten, blockiert aber auch legitime Skripte. CSP Level 2 führt Hashes und Nonces ein, um die Skripte der Anwendung von eingeschleustem Code zu unterscheiden.
Die im ersten Teil des Zweiteilers beschriebenen Mechanismen bieten bereits eine gute Basis im Schutz vor Cross-Site Scripting. Allerdings bleiben noch einige Fragen offen:
Was passiert, wenn ein Skript, das beispielsweise von einem Content Delivery Network extern geladen wurde, weitere Skripte nachladen möchte? Sind diese in die CSP eingeschlossen? Kann man CSP nicht verwenden, wenn man die HTTP-Response-Header nicht eigenhändig setzen kann? Wie lässt sich der Nonce-basierte Schutz einsetzen, wenn Server und Frontend getrennt voneinander deployed und von verschiedenen Servern ausgeliefert werden?
Diesen Fragen wird sich der zweite Teil des Zweiteilers widmen.
(rme)