Latte ist ein Synonym für Sicherheit
Latte ist das einzige PHP-Templating-System mit einem wirksamen Schutz gegen die kritische Cross-Site-Scripting (XSS)-Schwachstelle. Dies ist dem sogenannten kontextsensitiven Escaping zu verdanken. Lassen Sie uns reden,
- Was ist das Prinzip der XSS-Schwachstelle und warum ist sie so gefährlich?
- was Latte so effektiv gegen XSS macht
- warum Twig, Blade und andere Templates leicht kompromittiert werden können
Cross-Site Scripting (XSS)
Cross-Site Scripting (kurz XSS) ist eine der häufigsten Sicherheitslücken in Websites und eine sehr gefährliche dazu. Es ermöglicht einem Angreifer, ein bösartiges Skript (Malware genannt) in eine fremde Website einzufügen, das im Browser eines ahnungslosen Benutzers ausgeführt wird.
Was kann ein solches Skript tun? Es kann zum Beispiel beliebige Inhalte von der kompromittierten Website an den Angreifer senden, einschließlich sensibler Daten, die nach der Anmeldung angezeigt werden. Es kann die Seite ändern oder andere Anfragen im Namen des Benutzers stellen. Wenn es sich beispielsweise um Webmail handelt, könnte es sensible Nachrichten lesen, den angezeigten Inhalt ändern oder Einstellungen ändern, z. B. die Routing von Kopien aller Nachrichten an die Adresse des Angreifers aktivieren, um Zugriff auf künftige E-Mails zu erhalten.
Dies ist auch der Grund, warum XSS die Liste der gefährlichsten Sicherheitslücken anführt. Wenn eine Sicherheitslücke auf einer Website entdeckt wird, sollte sie so schnell wie möglich beseitigt werden, um eine Ausnutzung zu verhindern.
Wie entsteht die Sicherheitslücke?
Der Fehler tritt an der Stelle auf, an der die Webseite generiert wird und die Variablen gedruckt werden. Stellen Sie sich vor, Sie erstellen eine Suchseite, und am Anfang steht ein Absatz mit dem Suchbegriff in der Form:
echo '<p>Search results for <em>' . $search . '</em></p>';
Ein Angreifer kann eine beliebige Zeichenfolge, einschließlich HTML-Code wie
<script>alert("Hacked!")</script>
in das Suchfeld und damit in die Variable $search
schreiben. Da die Ausgabe in keiner Weise bereinigt wird, wird sie Teil der angezeigten Seite:
<p>Search results for <em><script>alert("Hacked!")</script></em></p>
Anstatt den Suchstring auszugeben, führt der Browser JavaScript aus. Und so übernimmt der Angreifer die Seite.
Man könnte einwenden, dass das Einfügen von Code in eine Variable zwar JavaScript ausführt, aber nur im Browser des Angreifers. Wie gelangt der Code zum Opfer? Unter diesem Gesichtspunkt können wir mehrere Arten von XSS unterscheiden. In unserem Beispiel einer Suchseite handelt es sich um reflektiertes XSS. In diesem Fall muss das Opfer dazu verleitet werden, auf einen Link zu klicken, der bösartigen Code im Parameter enthält:
https://example.com/?search=<script>alert("Hacked!")</script>
Es erfordert zwar ein gewisses Maß an Social Engineering, um den Benutzer dazu zu bringen, auf den Link zuzugreifen, aber das
ist nicht schwer. Benutzer klicken auf Links, ob in E-Mails oder in sozialen Medien, ohne viel nachzudenken. Und die Tatsache,
dass etwas Verdächtiges in der Adresse steht, kann durch URL-Verkürzer maskiert werden, so dass der Nutzer nur
bit.ly/xxx
sieht.
Es gibt jedoch eine zweite und viel gefährlichere Form des Angriffs, die als stored XSS oder persistent XSS bekannt ist und bei der es einem Angreifer gelingt, bösartigen Code auf dem Server zu speichern, so dass er automatisch in bestimmte Seiten eingefügt wird.
Ein Beispiel hierfür sind Websites, auf denen Benutzer Kommentare veröffentlichen. Ein Angreifer sendet einen Beitrag, der Code enthält, und dieser wird auf dem Server gespeichert. Wenn die Website nicht sicher genug ist, wird der Code dann im Browser eines jeden Besuchers ausgeführt.
Das Ziel des Angriffs scheint es zu sein, die <script>
String in die Seite zu bekommen. In der Tat gibt es
viele Möglichkeiten, JavaScript einzubetten":https://cheatsheetseries.owasp.org/…t_Sheet.html.
Nehmen wir ein Beispiel für die Einbettung mittels eines HTML-Attributs. Nehmen wir eine Fotogalerie, in die Sie eine
Bildunterschrift einfügen können, die im Attribut alt
ausgegeben wird:
echo '<img src="' . $imageFile . '" alt="' . $imageAlt . '">';
Ein Angreifer braucht nur eine geschickt konstruierte Zeichenfolge " onload="alert('Hacked!')
als Beschriftung
einzufügen, und wenn die Ausgabe nicht bereinigt wird, sieht der resultierende Code wie folgt aus:
<img src="photo0145.webp" alt="" onload="alert('Hacked!')">
Das gefälschte Attribut onload
wird nun Teil der Seite. Der Browser führt den darin enthaltenen Code aus, sobald
das Bild heruntergeladen ist. Gehackt!
Wie verteidigt man sich gegen XSS?
Alle Versuche, einen Angriff mit Hilfe einer schwarzen Liste zu erkennen, z. B. durch Blockieren der
<script>
Zeichenfolge usw. sind unzureichend. Die Grundlage für eine wirksame Verteidigung ist eine
konsequente Bereinigung aller Daten, die innerhalb der Seite ausgegeben werden.
Dazu gehört zunächst einmal das Ersetzen aller Zeichen mit besonderer Bedeutung durch andere passende Sequenzen, was in der
Umgangssprache escaping genannt wird (das erste Zeichen der Sequenz wird als Escape-Zeichen bezeichnet, daher der Name). In
HTML-Text wird zum Beispiel das Zeichen <
has a special meaning, which, if it is not to be interpreted as the
beginning of a tag, must be replaced by a visually corresponding sequence, the so-called HTML entity <
verwendet. Und der Browser gibt ein Zeichen aus.
Es ist sehr wichtig, den Kontext zu unterscheiden, in dem die Daten ausgegeben werden. Denn verschiedene Kontexte bereinigen Zeichenketten unterschiedlich. Verschiedene Zeichen haben in verschiedenen Kontexten eine besondere Bedeutung. Zum Beispiel ist das Escaping in HTML-Text, in HTML-Attributen, innerhalb einiger spezieller Elemente usw. unterschiedlich. Wir werden dies gleich im Detail besprechen.
Am besten ist es, das Escaping direkt bei der Ausgabe der Zeichenkette in der Seite vorzunehmen, um sicherzustellen, dass es tatsächlich und nur einmal durchgeführt wird. Am besten ist es, wenn die Verarbeitung automatisch direkt durch das Templating-System durchgeführt wird. Denn wenn die Verarbeitung nicht automatisch erfolgt, kann der Programmierer sie vergessen. Und eine Unterlassung bedeutet, dass die Website angreifbar ist.
XSS wirkt sich jedoch nicht nur auf die Ausgabe von Daten in Vorlagen aus, sondern auch auf andere Teile der Anwendung, die
nicht vertrauenswürdige Daten richtig behandeln müssen. Zum Beispiel darf JavaScript in Ihrer Anwendung nicht
innerHTML
in Verbindung mit ihnen verwenden, sondern nur innerText
oder textContent
.
Besondere Vorsicht ist bei Funktionen geboten, die Zeichenketten auswerten, wie z. B. JavaScript, das eval()
ist,
aber auch setTimeout()
, oder die Verwendung von setAttribute()
mit Ereignisattributen wie
onload
, usw. Dies geht jedoch über den von den Vorlagen abgedeckten Bereich hinaus.
Die ideale 3-Punkte-Verteidigung:
- Erkennen des Kontexts, in dem die Daten ausgegeben werden
- bereinigt die Daten gemäß den Regeln dieses Kontextes (d.h. “kontextbewusst”)
- macht dies automatisch
Kontextabhängiges Escaping
Was genau ist mit dem Wort Kontext gemeint? Es handelt sich um eine Stelle im Dokument mit eigenen Regeln für die Behandlung der auszugebenden Daten. Er hängt von der Art des Dokuments ab (HTML, XML, CSS, JavaScript, einfacher Text, …) und kann in bestimmten Teilen des Dokuments variieren. In einem HTML-Dokument gibt es zum Beispiel viele solcher Stellen (Kontexte), an denen sehr unterschiedliche Regeln gelten. Sie werden überrascht sein, wie viele es sind. Hier sind die ersten vier:
<p>#text</p>
<img src="#attribute">
<textarea>#rawtext</textarea>
<!-- #comment -->
Der ursprüngliche und grundlegende Kontext einer HTML-Seite ist HTML-Text. Wie lauten die Regeln hier? Die Sonderzeichen
<
and &
stehen für den Anfang eines Tags oder einer Entität, so dass wir sie durch die
HTML-Entität (<
with <
, &
with &
) ersetzen müssen.
Der zweithäufigste Kontext ist der Wert eines HTML-Attributs. Er unterscheidet sich von Text dadurch, dass die besondere
Bedeutung hier dem Anführungszeichen "
or '
zukommt, das das Attribut abgrenzt. Dieses muss als Einheit
geschrieben werden, damit es nicht als Ende des Attributs angesehen wird. Andererseits kann das Zeichen <
bedenkenlos in einem Attribut verwendet werden, da es hier keine besondere Bedeutung hat; es kann nicht als Anfang eines Tags oder
Kommentars verstanden werden. Aber Vorsicht, in HTML können Sie Attributwerte ohne Anführungszeichen schreiben, in diesem Fall
hat eine ganze Reihe von Zeichen eine besondere Bedeutung, es handelt sich also um einen anderen, separaten Kontext.
Es mag Sie überraschen, aber es gelten besondere Regeln innerhalb der <textarea>
und
<title>
Elementen, wo die <
character need not (but can) be escaped unless followed by
/
. Aber das ist eher eine Kuriosität.
Interessant ist es innerhalb der HTML-Kommentare. Hier werden HTML-Entities nicht für das Escaping verwendet. Es gibt nicht einmal eine Spezifikation, die besagt, wie man in Kommentaren escapen soll. Man muss nur die etwas merkwürdigen Regeln befolgen und bestimmte Zeichenkombinationen in ihnen vermeiden.
Kontexte können auch geschichtet werden, was geschieht, wenn wir JavaScript oder CSS in HTML einbetten. Dies kann auf zwei verschiedene Arten geschehen: durch Elemente oder Attribute:
<script>#js-element</script>
<img onclick="#js-attribute">
<style>#css-element</style>
<p style="#css-attribute"></p>
Zwei Wege und zwei verschiedene Arten des Escapings von Daten. Innerhalb der <script>
und
<style>
Elementen, wie im Fall von HTML-Kommentaren, wird kein Escaping mit HTML-Entities durchgeführt. Beim
Escapen von Daten innerhalb dieser Elemente gibt es nur eine Regel: Der Text darf nicht die Sequenz </script
bzw.
</style
enthalten.
Dagegen werden die Attribute style
und on***
mit Hilfe von HTML-Entitäten escaped.
Und natürlich gelten innerhalb von eingebettetem JavaScript oder CSS die Escaperegeln dieser Sprachen. Eine Zeichenkette in
einem Attribut wie onload
wird also zuerst gemäß den JS-Regeln und dann gemäß den HTML-Attributregeln
escaped.
Igitt… Wie Sie sehen, ist HTML ein sehr komplexes Dokument mit mehreren Ebenen von Kontexten, und ohne genau zu wissen, wo ich die Daten ausgeben will (d. h. in welchem Kontext), kann man nicht sagen, wie man es richtig macht.
Wollen Sie ein Beispiel?
Nehmen wir eine Zeichenkette Rock'n'Roll
.
Wenn Sie sie in HTML-Text ausgeben, brauchen Sie in diesem Fall keine Ersetzungen vorzunehmen, da die Zeichenkette keine Zeichen mit besonderer Bedeutung enthält. Anders verhält es sich, wenn Sie sie in ein HTML-Attribut schreiben, das in einfache Anführungszeichen eingeschlossen ist. In diesem Fall müssen Sie die Anführungszeichen in HTML-Entities umwandeln:
<div title='Rock'n'Roll'></div>
Das war einfach. Eine viel interessantere Situation tritt auf, wenn der Kontext geschichtet ist, zum Beispiel wenn die Zeichenkette Teil von JavaScript ist.
Also schreiben wir sie zunächst in das JavaScript selbst. Das heißt, wir verpacken sie in Anführungszeichen, wobei wir die darin enthaltenen Anführungszeichen mit dem Zeichen “escapen”:
'Rock\'n\'Roll'
Wir können einen Funktionsaufruf hinzufügen, damit der Code etwas tut:
alert('Rock\'n\'Roll');
Wenn wir diesen Code in ein HTML-Dokument einfügen, indem wir <script>
einfügen, brauchen wir nichts weiter
zu ändern, da die verbotene Sequenz </script
nicht vorhanden ist:
<script> alert('Rock\'n\'Roll'); </script>
Wenn wir ihn jedoch in ein HTML-Attribut einfügen wollen, müssen wir die Anführungszeichen in HTML-Entities umwandeln:
<div onclick='alert('Rock\'n\'Roll')'></div>
Der verschachtelte Kontext muss jedoch nicht nur JS oder CSS sein. Es handelt sich auch häufig um eine URL. Parameter in URLs werden durch die Umwandlung von Sonderzeichen in Sequenzen, die mit “%” beginnen, geschützt. Beispiel:
https://example.org/?a=Jazz&b=Rock%27n%27Roll
Wenn wir diese Zeichenkette in einem Attribut ausgeben, wenden wir immer noch Escaping entsprechend diesem Kontext an und
ersetzen &
with &
:
<a href="https://example.org/?a=Jazz&b=Rock%27n%27Roll">
Wenn Sie bis hierher gelesen haben, herzlichen Glückwunsch, es war anstrengend. Jetzt haben Sie eine gute Vorstellung davon, was Kontexte und Escaping sind. Und Sie müssen sich keine Sorgen machen, dass es kompliziert wird. Latte macht das automatisch für Sie.
Latte vs. Naive Systeme
Wir haben gezeigt, wie man in einem HTML-Dokument richtig escaped und wie wichtig es ist, den Kontext zu kennen, d.h. wo man die Daten ausgibt. Mit anderen Worten, wie kontextsensitives Escaping funktioniert. Dies ist zwar eine Voraussetzung für eine funktionierende XSS-Abwehr, aber Latte ist das einzige Templating-System für PHP, das dies tut.
Wie ist das möglich, wo doch heute alle Systeme behaupten, dass sie automatisch escapen? Automatisches Escaping ohne Kenntnis des Kontexts ist ein Schwachsinn, der ein falsches Gefühl von Sicherheit erzeugt.
Templating-Systeme wie Twig, Laravel Blade und andere sehen keine HTML-Struktur in den Templates. Daher sehen sie auch keine Kontexte. Im Vergleich zu Latte sind sie blind und naiv. Sie verarbeiten nur ihr eigenes Markup, alles andere ist für sie ein irrelevanter Zeichenstrom:
░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░
░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░
░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░{{ text }}░░░░░░░░░░░
░░░░░░░░░░░░░░░░░░░{{ text }}░░░░
- in text: <span>{{ text }}</span>
- in tag: <span {{ text }} ></span>
- in attribute: <span title='{{ text }}'></span>
- in unquoted attribute: <span title={{ text }}></span>
- in attribute containing URL: <a href="{{ text }}"></a>
- in attribute containing JavaScript: <img onload="{{ text }}">
- in attribute containing CSS: <span style="{{ text }}"></span>
- in JavaScriptu: <script>var = {{ text }}</script>
- in CSS: <style>body { content: {{ text }}; }</style>
- in comment: <!-- {{ text }} -->
Naive Systeme wandeln die Zeichen von < > & ' "
einfach mechanisch in HTML-Entities um, was in den
meisten Fällen eine gültige Art des Escapings ist, aber bei weitem nicht immer. Daher können sie verschiedene
Sicherheitslücken nicht erkennen oder verhindern, wie wir weiter unten zeigen werden.
Latte sieht die Vorlage auf die gleiche Weise wie Sie. Es versteht HTML, XML, erkennt Tags, Attribute usw. Und weil das so ist, unterscheidet es zwischen Kontexten und behandelt die Daten entsprechend. So bietet es einen wirklich effektiven Schutz gegen die kritische Cross-Site-Scripting-Schwachstelle.
Live-Demonstration
Links sehen Sie die Vorlage in Latte, rechts den generierten HTML-Code. Die Variable $text
wird mehrmals
ausgegeben, jedes Mal in einem etwas anderen Kontext. Und daher auch ein bisschen anders escaped. Sie können den Code der Vorlage
selbst bearbeiten, z.B. den Inhalt der Variablen ändern usw. Probieren Sie es aus:
Ist das nicht großartig! Latte macht kontextabhängiges Escaping automatisch, so dass der Programmierer:
- Er muss nicht nachdenken oder wissen, wie man Daten entschlüsselt
- sich nicht irren kann
- es nicht vergessen kann
Dies sind noch nicht einmal alle Kontexte, die Latte bei der Ausgabe unterscheidet und für die es die Datenbehandlung anpasst. Wir werden jetzt weitere interessante Fälle durchgehen.
Wie man naive Systeme hackt
Wir werden anhand einiger praktischer Beispiele zeigen, wie wichtig die Kontextdifferenzierung ist und warum naive Templating-Systeme im Gegensatz zu Latte keinen ausreichenden Schutz gegen XSS bieten. Wir werden Twig als Vertreter eines naiven Systems in den Beispielen verwenden, aber das gilt auch für andere Systeme.
Anfälligkeit für Attribute
Versuchen wir nun, bösartigen Code in die Seite einzuschleusen, indem wir das HTML-Attribut wie oben gezeigt verwenden. Wir haben eine Vorlage in Twig, die ein Bild anzeigt:
<img src={{ imageFile }} alt={{ imageAlt }}>
Beachten Sie, dass die Attributwerte nicht in Anführungszeichen gesetzt sind. Der Programmierer hat sie vielleicht vergessen, was einfach passiert. In React wird der Code zum Beispiel so geschrieben, ohne Anführungszeichen, und ein Programmierer, der die Sprache wechselt, kann die Anführungszeichen leicht vergessen.
Der Angreifer fügt eine geschickt konstruierte Zeichenfolge foo onload=alert('Hacked!')
als Bildunterschrift ein.
Wir wissen bereits, dass Twig nicht erkennen kann, ob eine Variable in einem HTML-Textstrom, in einem Attribut, in einem
HTML-Kommentar usw. ausgegeben wird; kurz gesagt, es unterscheidet nicht zwischen den Kontexten. Und es konvertiert einfach
mechanisch < > & ' "
Zeichen in HTML-Entities. Der resultierende Code sieht also so aus:
<img src=photo0145.webp alt=foo onload=alert('Hacked!')>
Es wurde eine Sicherheitslücke geschaffen!
Ein gefälschtes onload
-Attribut ist Teil der Seite geworden und der Browser führt es sofort nach dem
Herunterladen des Bildes aus.
Sehen wir uns nun an, wie Latte die gleiche Vorlage behandelt:
<img src={$imageFile} alt={$imageAlt}>
Latte sieht die Vorlage auf die gleiche Weise wie Sie. Im Gegensatz zu Twig versteht es HTML und weiß, dass eine Variable als ein Attributwert gedruckt wird, der nicht in Anführungszeichen steht. Deshalb fügt es sie hinzu. Wenn ein Angreifer dieselbe Überschrift einfügt, sieht der resultierende Code wie folgt aus:
<img src="photo0145.webp" alt="foo onload=alert('Hacked!')">
Latte hat XSS erfolgreich verhindert.
Drucken einer Variablen in JavaScript
Dank des kontextsensitiven Escapings ist es möglich, PHP-Variablen nativ in JavaScript zu verwenden.
<p onclick="alert({$movie})">{$movie}</p>
<script>var movie = {$movie};</script>
Wenn die Variable $movie
die Zeichenkette 'Amarcord & 8 1/2'
speichert, erzeugt sie die folgende
Ausgabe. Beachten Sie das unterschiedliche Escaping in HTML und JavaScript und auch im Attribut onclick
:
<p onclick="alert("Amarcord & 8 1\/2")">Amarcord & 8 1/2</p>
<script>var movie = "Amarcord & 8 1\/2";</script>
Link-Prüfung
Latte prüft automatisch, ob die in den Attributen src
oder href
verwendete Variable eine Web-URL
(d.h. das HTTP-Protokoll) enthält und verhindert das Schreiben von Links, die ein Sicherheitsrisiko darstellen könnten.
{var $link = 'javascript:attack()'}
<a href={$link}>click here</a>
Schreibt:
<a href="">click here</a>
Die Prüfung kann mit einem Filter nocheck ausgeschaltet werden.
Grenzen der Latte
Latte ist kein vollständiger XSS-Schutz für die gesamte Anwendung. Wir wären unglücklich, wenn Sie bei der Verwendung von Latte aufhören würden, über Sicherheit nachzudenken. Das Ziel von Latte ist es, sicherzustellen, dass ein Angreifer die Struktur einer Seite nicht verändern, HTML-Elemente oder Attribute manipulieren kann. Es prüft jedoch nicht die inhaltliche Korrektheit der ausgegebenen Daten. Oder die Korrektheit des Verhaltens von JavaScript. Das liegt außerhalb des Aufgabenbereichs des Templating-Systems. Die Überprüfung der Korrektheit von Daten, insbesondere von solchen, die vom Benutzer eingegeben werden und daher nicht vertrauenswürdig sind, ist eine wichtige Aufgabe für den Programmierer.