Zum Inhalt
wannacode

XSS: ein fremdes Skript in deiner Seite

Problem

Eine Suche, die stiehlt

Der Shop hat eine Suche. Die Ergebnisseite zeigt, was der Käufer getippt hat: „Ergebnisse für: Laufschuhe“. Der Server klebt den Suchtext in HTML:

using System.Net;
string SearchPage(string query)
{
return "<p>Ergebnisse für: <b>" + query + "</b></p>";
}
Console.WriteLine(SearchPage("Laufschuhe"));
Console.WriteLine(SearchPage("<img src=x onerror=\"steal(shopSession)\">"));
Ausgabe
<p>Ergebnisse für: <b>Laufschuhe</b></p>
<p>Ergebnisse für: <b><img src=x onerror="steal(shopSession)"></b></p>

Die erste Zeile ist eine gewöhnliche Seite. In der zweiten lautet die Suche <img src=x onerror="steal(shopSession)">, und sie ist vollständig im HTML angekommen. Der Browser sieht einen Bild-Tag, das Bild x lädt nicht, und er führt den Code in onerror aus. Hier ist steal eine erfundene Funktion; bei einem echten Angriff schickt der Code die Cookies oder die Sitzung an den Server des Angreifers.

Der Angreifer tippt nicht in seine eigene Suche. Er schickt den Käufern einen Link zu einer Suche im Shop mit diesem Text in der Adresse. Wer ihn öffnet, führt den fremden Code im Shop aus, mit der eigenen Sitzung. Das ist XSS (cross-site scripting): ein fremdes Skript in deiner Seite.

Die Idee

HTML ist eine weitere Sprache

Das ist die Injektion aus der vorigen Lektion, in einer weiteren Sprache. In HTML beginnt < einen Tag, und " schließt einen Attributwert. Der Code hat den Text des Käufers in die Sprache des Browsers geklebt, und die Zeichen im Text wurden Teil der Seite.

Zwei Arten kommen am häufigsten vor:

  • Reflektiert (reflected): Der Text kommt aus der Adresse und geht in der Antwort zurück, wie bei der Suche oben. Der Angreifer muss jemanden dazu bringen, den Link zu öffnen.
  • Gespeichert (stored): Der Text ist in der Datenbank gespeichert, zum Beispiel eine Bewertung, und wird jedem gezeigt, der das Produkt öffnet. Der Angreifer schreibt einmal, und das Skript läuft bei allen.

Der Server hat nichts ausgeführt. Der Code läuft im Browser des Käufers, mit dessen Sitzung, als wäre er Teil des Shops.

Sieh es dir an

Probier es selbst aus

Tippe einen Text in die Suche des Shops, wähle einen Schutz und sieh, was passiert. Die Seite liegt in einem isolierten iframe, ohne Netzwerk, und sieht nichts von dieser Website.

Sandbox: feindliche Eingabe in einer Shopseite

Schutz

Die Sandbox ist isoliert: kein Netzwerk, und sie sieht nichts von der Website. steal() ist hier der vorgetäuschte Server des Angreifers; ein echter Angriff schickt die Daten mit fetch.

Probiere die drei fertigen Eingaben ohne Schutz, dann mit Escaping, dann mit CSP. Sieh unter „Was beim Browser ankommt“ nach, wie das HTML in den drei Fällen aussieht.

Die Idee

Die Ausgabe escapen

Die Lösung ist das Escaping: Bevor du Text in HTML einsetzt, ersetzt du die fünf Zeichen, die HTML deutet, durch ihre Entitäten. Der Browser zeigt die Entität als Zeichen, liest sie aber nicht als Teil der Seite:

Zeichen Wird zu Warum
& &amp; beginnt eine Entität; wird als Erstes ersetzt
< &lt; beginnt einen Tag
> &gt; schließt einen Tag
" &quot; schließt einen Attributwert
' &#39; schließt einen Wert in einfachen Anführungszeichen
In C# ist die Funktion fertig: WebUtility.HtmlEncode aus System.Net ersetzt dieselben fünf Zeichen.
using System.Net;
string SearchPage(string query)
{
return "<p>Ergebnisse für: <b>" + WebUtility.HtmlEncode(query) + "</b></p>";
}
Console.WriteLine(SearchPage("Laufschuhe"));
Console.WriteLine(SearchPage("<img src=x onerror=\"steal(shopSession)\">"));
Ausgabe
<p>Ergebnisse für: <b>Laufschuhe</b></p>
<p>Ergebnisse für: <b>&lt;img src=x onerror=&quot;steal(shopSession)&quot;&gt;</b></p>

Jetzt kam die Suche als Text beim Browser an. Der Käufer sieht genau das, was er getippt hat, aber es gibt keinen Tag.

Die Regel lautet: die Ausgabe escapen, beim Anzeigen, an der Stelle, an der der Text in HTML gelangt. Nicht beim Entgegennehmen: Dann weißt du nicht, wohin der Text geht (in HTML, in JSON, in eine E-Mail), und in der Datenbank steht &lt; statt dessen, was der Käufer getippt hat.

Genauer, für Neugierige
  • In einem echten Projekt wird HTML mit Razor erzeugt, und es escapet selbst: @Model.Text ist sicher. @Html.Raw(…) schaltet das Escaping ab und ist die Stelle, die man im Code-Review prüft.
  • Das Escaping hängt von der Stelle ab. Die fünf Zeichen genügen für Text zwischen Tags und für einen Attributwert in Anführungszeichen. Ein Attribut ohne Anführungszeichen, Text innerhalb von <script>, Stile und eine Adresse in href haben andere Regeln. Am einfachsten: Setze fremden Text nur zwischen Tags und in Attribute mit Anführungszeichen.
  • Die Adresse in href. javascript:steal(shopSession) enthält keines der fünf Zeichen und führt beim Klick trotzdem Code aus. Für Adressen von Nutzern braucht es eine Allow-List, wie in der Lektion „Nicht vertrauenswürdige Eingaben“: Die Adresse beginnt mit https:// oder http://, sonst wird kein Link daraus.

Probier es aus

Finde das Feld

Die Seite mit den Bewertungen zeigt einen Titel, einen Autor und einen Text. Welches Feld kommt roh beim Browser an?

Wähle die Zeile mit dem Fehler.

Probier es aus

Was wird ausgegeben

Was gibt das Escaping für Tom & Jerry <3 aus?

using System.Net;
Console.WriteLine(WebUtility.HtmlEncode("Tom & Jerry <3"));
Antwort

Die Idee

CSP: die zweite Linie

Auf einer großen Website gibt es Hunderte Stellen, an denen Text in HTML gelangt. Es genügt, eine zu übersehen. Deshalb gibt es einen zweiten Schutz: Content-Security-Policy (CSP). Das ist ein Header in der Antwort, der dem Browser sagt, welche Skripte laufen dürfen:

Content-Security-Policy: script-src 'self'; object-src 'none'; base-uri 'none'

script-src 'self' bedeutet: nur Skripte aus Dateien derselben Website. Ein Skript, das in die Seite geschrieben ist (<script>…</script> oder onerror="…"), läuft nicht, und der Browser vermerkt den Verstoß. Deshalb muss auch das eigene JavaScript der Website in Dateien liegen. Wenn du doch ein Skript in der Seite brauchst, akzeptiert CSP eines mit einer nonce: einem Zufallswert, neu in jeder Antwort, den der Angreifer nicht kennt. So funktioniert der Schutz „CSP“ in der Sandbox.

CSP ersetzt das Escaping nicht. Sie stoppt das Skript, aber nicht fremdes HTML: Ein gefälschtes Formular „Melde dich erneut an“ oder ein ausgetauschter Preis ist kein Skript. Das Escaping ist die erste Linie, CSP das Netz darunter.

Genauer, für Neugierige
  • Probiere es, ohne etwas kaputt zu machen. Content-Security-Policy-Report-Only meldet nur, was die Richtlinie blockieren würde. So führt man CSP in eine alte Website ein: Erst schaut man sich die Verstöße an, dann schaltet man sie ein.
  • Das Cookie der Sitzung. Auch wenn das Skript läuft, ist ein Cookie mit dem Flag HttpOnly für JavaScript nicht sichtbar. Das kommt in der Lektion über die Sitzungen.

Übung

Die Bewertung

Löse die Aufgabe in deinem Editor.

Eine Bewertung mit Autor. Schreibe RenderReview(author, text). Sie gibt <article><h3>Autor</h3><p>Text</p></article> zurück, wobei sowohl der Autor als auch der Text escapet werden. Probiere eine gewöhnliche Bewertung und einen Autor, der <script>steal(shopSession)</script> heißt:

Erwartet
<article><h3>Anna</h3><p>Bequem &lt;3</p></article>
<article><h3>&lt;script&gt;steal(shopSession)&lt;/script&gt;</h3><p>Super!</p></article>

Weitere Aufgaben

  • Ein Link in der Bewertung. Füge der Bewertung ein Feld „Website des Autors“ hinzu. Zeige es nur dann als <a href="…">, wenn die Adresse mit https:// oder http:// beginnt; sonst zeige es als gewöhnlichen Text. Probiere javascript:steal(shopSession).
  • Doppeltes Escaping. Was sieht der Käufer, wenn die Bewertung einmal beim Speichern in der Datenbank und noch einmal beim Anzeigen escapet wird? Probiere <3.

Test

Prüfe dich

1. Die Suche zeigt nur, was der Käufer getippt hat. Warum führt <img src=x onerror="…"> in der Suche Code aus?
2. Das Escaping macht aus < ein &lt;. Was macht der Browser mit &lt;?
3. Warum ersetzt das Escaping & zuerst, vor < und >?
4. Eine Bewertung hat einen Link <a href="…"> mit einer Adresse vom Käufer, die durch das Escaping gegangen ist. Der Käufer schreibt javascript:steal(shopSession). Hält das Escaping ihn auf?
5. Die Seite hat CSP: script-src 'self'. Brauchst du das Escaping trotzdem noch?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·