Quiz: Können Sie sich vor der XSS-Schwachstelle schützen?
Testen Sie Ihr Wissen im Sicherheitsquiz! Können Sie verhindern, dass ein Angreifer die Kontrolle über eine HTML-Seite übernimmt?

In allen Aufgaben lösen Sie dieselbe Frage: Wie gibt man die Variable
$str
korrekt auf einer HTML-Seite aus, damit keine XSS-Schwachstelle entsteht?
Die Grundlage der Verteidigung ist das Escaping, was bedeutet, Zeichen
mit besonderer Bedeutung durch entsprechende Sequenzen zu ersetzen. Zum Beispiel
wird beim Ausgeben einer Zeichenkette in HTML-Text, in dem das Zeichen
<
eine besondere Bedeutung hat (signalisiert den Anfang eines
Tags), dieses durch die HTML-Entität <
ersetzt, und der
Browser zeigt das Symbol <
korrekt an.
Seien Sie auf der Hut, denn die XSS-Schwachstelle ist sehr ernst. Sie kann dazu führen, dass ein Angreifer die Kontrolle über die Seite oder sogar über das Benutzerkonto übernimmt. Viel Erfolg und möge es Ihnen gelingen, die HTML-Seite sicher zu halten!
Die ersten drei Fragen
Geben Sie an, welche Zeichen und auf welche Weise im ersten, zweiten und dritten Beispiel behandelt werden müssen:
1) <p><?= $str ?></p>
2) <input value="<?= $str ?>">
3) <input value='<?= $str ?>'>
Wenn die Ausgabe nicht behandelt würde, würde sie Teil der angezeigten
Seite werden. Wenn ein Angreifer die Zeichenkette
'foo" onclick="evilCode()'
in die Variable einschleusen würde und
die Ausgabe nicht behandelt würde, würde dies dazu führen, dass beim Klicken
auf das Element sein Code ausgeführt wird:
$str = 'foo" onclick="evilCode()'
❌ ohne Behandlung: <input value="foo" onclick="evilCode()">
✅ mit Behandlung: <input value="foo" onclick="evilCode()">
Lösung der einzelnen Beispiele:
- Die Zeichen
<
und&
stellen den Anfang eines HTML-Tags und einer Entität dar, wir ersetzen sie durch<
und&
. - Die Zeichen
"
und&
stellen das Ende des Attributwerts und den Anfang einer HTML-Entität dar, wir ersetzen sie durch"
und&
. - Die Zeichen
'
und&
stellen das Ende des Attributwerts und den Anfang einer HTML-Entität dar, wir ersetzen sie durch'
und&
.
Für jede richtige Antwort erhalten Sie einen Punkt. Natürlich können in allen drei Fällen auch weitere Zeichen durch Entitäten ersetzt werden, das schadet nicht, ist aber nicht notwendig.
Frage Nr. 4
Wir machen weiter. Welche Zeichen müssen bei der Ausgabe der Variablen in diesem Kontext ersetzt werden?
<input value=<?= $str ?>>
Lösung: Wie Sie sehen, fehlen hier Anführungszeichen. Am einfachsten ist
es, die Anführungszeichen einfach zu ergänzen und dann genauso zu escapen wie
in der vorherigen Frage. Es gibt auch eine zweite Lösung, nämlich Leerzeichen
und alle Zeichen, die innerhalb des Tags eine besondere Bedeutung haben, d.h.
>
, /
, =
und einige
andere, in der Zeichenkette durch HTML-Entitäten zu ersetzen.
Frage Nr. 5
Jetzt wird es interessanter. Welche Zeichen müssen in diesem Kontext behandelt werden:
<script>
let foo = '<?= $str ?>';
</script>
Lösung: Innerhalb des <script>
-Tags bestimmen die Regeln
für das Escaping JavaScript. HTML-Entitäten werden hier nicht
verwendet, es gilt jedoch eine spezielle Regel. Welche Zeichen escapen wir
also? Innerhalb einer JavaScript-Zeichenkette escapen wir natürlich das Zeichen
'
, das sie begrenzt, und zwar mit einem Backslash, also ersetzen
wir es durch \'
. Da JavaScript keine mehrzeiligen Zeichenketten
unterstützt (nur als Template-Literal),
müssen wir auch Zeilenumbruchzeichen escapen. Aber Achtung, neben den üblichen
Zeichen \n
und \r
betrachtet JavaScript auch die
Unicode-Zeichen \u2028
und \u2029
als Zeilenumbrüche,
die wir ebenfalls escapen müssen. Und schließlich die erwähnte spezielle
Regel: In der Zeichenkette darf </script
nicht vorkommen. Dies
kann z.B. durch Ersetzen durch <\/script
verhindert werden.
Wenn Sie das wussten, herzlichen Glückwunsch.
Frage Nr. 6
Der folgende Kontext sieht nur wie eine Variation des vorherigen aus. Was meinen Sie, wird sich die Behandlung unterscheiden?
<p onclick="foo('<?= $str ?>')"></p>
Lösung: Auch hier gelten die Regeln für das Escaping in
JavaScript-Zeichenketten, aber im Gegensatz zum vorherigen Kontext, wo nicht mit
HTML-Entitäten escaped wurde, wird hier im Gegenteil escaped. Das heißt, wir
escapen zuerst die JavaScript-Zeichenkette mit Backslashes und ersetzen dann die
Zeichen mit besonderer Bedeutung ("
und &
) durch
HTML-Entitäten. Achtung, die richtige Reihenfolge ist wichtig.
Wie Sie sehen, kann dasselbe JavaScript-Literal im
<script>
-Element anders kodiert sein als in einem
Attribut!
Frage Nr. 7
Kehren wir von JavaScript zurück zu HTML. Welche Zeichen müssen wir innerhalb eines Kommentars ersetzen und auf welche Weise?
<!-- <?= $str ?> -->
Lösung: Innerhalb eines HTML- (und XML-)Kommentars können alle
traditionellen Sonderzeichen wie <
, &
,
"
und '
vorkommen. Verboten ist, und das wird Sie
wahrscheinlich überraschen, das Zeichenpaar --
. Das Escaping
dieser Sequenz ist nicht spezifiziert, daher liegt es an Ihnen, wie Sie sie
ersetzen. Sie können sie mit Leerzeichen durchsetzen. Oder vielleicht durch
==
ersetzen.
Frage Nr. 8
Wir nähern uns dem Ende, also versuchen wir, die Frage abzuwandeln. Versuchen Sie darüber nachzudenken, worauf bei der Ausgabe der Variablen in diesem Kontext zu achten ist:
<a href="<?= $str ?>">...</a>
Lösung: Neben dem Escaping ist es wichtig zu überprüfen, dass die URL kein
gefährliches Schema wie javascript:
enthält, da eine so
zusammengesetzte URL nach dem Klicken den Code des Angreifers
aufrufen würde.
Frage Nr. 9
Zum Abschluss eine Perle für echte Feinschmecker. Es handelt sich um ein
Beispiel einer Anwendung, die ein modernes JavaScript-Framework verwendet,
konkret Vue. Mal sehen, ob Ihnen einfällt, worauf bei der Ausgabe der Variablen
innerhalb des Elements #app
zu achten ist:
<div id="app">
<?= $str ?>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
const app = new Vue({
el: '#app',
...
})
</script>
Dieser Code erstellt eine Vue-Anwendung, die in das Element #app
gerendert wird. Vue versteht den Inhalt dieses Elements als sein Template. Und
innerhalb des Templates interpretiert
es doppelte geschweifte Klammern, die die Ausgabe einer Variablen oder den
Aufruf von JavaScript-Code bedeuten (z.B. {{ foo }}
).
Das heißt, innerhalb des Elements #app
haben neben den Zeichen
<
und &
auch das Paar {{
eine
besondere Bedeutung, das wir durch eine andere entsprechende Sequenz ersetzen
müssen, damit Vue es nicht als sein Tag interpretiert. Das Ersetzen durch
HTML-Entitäten hilft in diesem Fall jedoch nicht. Wie geht man damit um? Ein
Trick funktioniert: Zwischen die Klammern fügen wir einen leeren HTML-Kommentar
{<!-- -->{
ein, und Vue ignoriert eine solche Sequenz.
Quiz-Ergebnisse
Wie haben Sie im Quiz abgeschnitten? Wie viele richtige Antworten haben Sie? Wenn Sie mindestens 4 Fragen richtig beantwortet haben, gehören Sie zu den besten 8 % der Löser – herzlichen Glückwunsch!
Um jedoch die Sicherheit Ihrer Website zu gewährleisten, ist es unerlässlich, die Ausgabe in allen Situationen korrekt zu behandeln.
Wenn Sie überrascht waren, wie viele verschiedene Kontexte auf einer normalen HTML-Seite auftreten können, dann wissen Sie, dass wir bei weitem nicht alle erwähnt haben. Das wäre ein viel längeres Quiz gewesen. Dennoch müssen Sie kein Experte für das Escaping in jedem Kontext sein, wenn Ihr Template-System dies beherrscht.
Lassen Sie uns sie also ausprobieren.
Wie schneiden Template-Systeme ab?
Alle modernen Template-Systeme rühmen sich der Funktion Autoescaping, die automatisch alle ausgegebenen Variablen escaped. Wenn sie dies korrekt tun, ist Ihre Website sicher. Wenn sie es falsch machen, ist die Website dem Risiko einer XSS-Schwachstelle mit all ihren schwerwiegenden Folgen ausgesetzt.
Wir testen beliebte Template-Systeme anhand der Fragen dieses Quiz, um herauszufinden, wie effektiv ihr Autoescaping ist. Der dTest der Template-Systeme für PHP beginnt.
Twig ❌
Als erstes kommt das Template-System Twig (Version 3.5) an die Reihe, das am
häufigsten in Verbindung mit dem Symfony-Framework verwendet wird. Wir geben
ihm die Aufgabe, alle Quizfragen zu beantworten. Die Variable $str
wird immer mit einer kniffligen Zeichenkette gefüllt, und wir schauen uns an,
wie es mit ihrer Ausgabe umgeht. Die Ergebnisse sehen Sie rechts. Sie können
seine Antworten und sein Verhalten auch im Playground
untersuchen.
{% set str = "<'\"&" %}
1) <p>{{ str }}</p>
2) <input value="{{ str }}">
3) <input value='{{ str }}'>
{% set str = "foo onclick=evilCode()" %}
4) <input value={{ str }}>
{% set str = "'\"\n\u{2028}" %}
5) <script> let foo = '{{ str }}'; </script>
6) <p onclick="foo('{{ str }}')"></p>
{% set str = "-- ---" %}
7) <!-- {{ str }} -->
{% set str = "javascript:evilCode()" %}
8) <a href="{{ str }}">...</a>
{% set str = "{{ foo }}" %}
9) <div id="app"> {{ str }} </div>
✅ <p><'"&</p>
✅ <input value="<'"&">
✅ <input value='<'"&'>
❌ <input value=foo onclick=evilCode()>
❌ <script> let foo = '"u{2028}; </script>
❌ <p onclick="foo('"u{2028})"></p>
❌ <!-- -- --- -->
❌ <a href="javascript:evilCode()">...</a>
❌ <div id="app"> {{ foo }} </div>
Twig ist in sechs von neun Tests durchgefallen!
Leider funktioniert das automatische Escaping von Twig nur in HTML-Text und Attributen, und das auch nur, wenn sie in Anführungszeichen eingeschlossen sind. Sobald Anführungszeichen fehlen, meldet Twig keinen Fehler und erzeugt eine XSS-Sicherheitslücke.
Dies ist besonders ärgerlich, da Attributwerte in populären Bibliotheken wie React oder Svelte so geschrieben werden. Ein Programmierer, der gleichzeitig Twig und React verwendet, kann daher ganz natürlich die Anführungszeichen vergessen.
Das Autoescaping von Twig versagt auch in allen anderen Beispielen. In den
Kontexten (5) und (6) muss manuell mit {{ str|escape('js') }}
escaped werden, für andere Kontexte bietet Twig keine Escaping-Funktion an. Es
verfügt auch nicht über einen Schutz vor der Ausgabe eines schädlichen Links
(8) oder Unterstützung für Templates für Vue (9).
Blade ❌❌
Der zweite Teilnehmer ist das Template-System Blade (Version 10.9), das eng mit Laravel und seinem Ökosystem integriert ist. Wir überprüfen erneut seine Fähigkeiten anhand unserer Quizfragen. Seine Antworten können Sie auch im Playground untersuchen.
@php($str = "<'\"&")
1) <p>{{ $str }}</p>
2) <input value="{{ $str }}">
3) <input value='{{ $str }}'>
@php($str = "foo onclick=evilCode()")
4) <input value={{ $str }}>
@php($str = "'\"\n\u{2028}")
5) <script> let foo = {{ $str }}; </script>
6) <p onclick="foo({{ $str }})"></p>
@php($str = "-- ---")
7) <!-- {{ $str }} -->
@php($str = "javascript:evilCode()")
8) <a href="{{ $str }}">...</a>
@php($str = "{{ foo }}")
9) <div id="app"> {{ $str }} </div>
✅ <p><'"&</p>
✅ <input value="<'"&">
✅ <input value='<'"&'>
❌ <input value=foo onclick=evilCode()>
❌ <script> let foo = '" ; </script>
❌ <p onclick="foo('" )"></p>
❌ <!-- -- --- -->
❌ <a href="javascript:evilCode()">...</a>
❌❌ <div id="app"> <?php echo e(foo); ?> </div>
Blade ist in sechs von neun Tests durchgefallen!
Das Ergebnis ähnelt dem von Twig. Es gilt wieder, dass das automatische
Escaping nur in HTML-Text und Attributen funktioniert und nur, wenn sie in
Anführungszeichen eingeschlossen sind. Das Autoescaping von Blade versagt auch
in allen anderen Beispielen. In den Kontexten (5) und (6) muss manuell mit
{{ Js::from($str) }}
escaped werden. Für andere Kontexte bietet
Blade keine Escaping-Funktion an. Es verfügt weder über einen Schutz vor der
Ausgabe eines schädlichen Links (8) noch über Unterstützung für Templates
für Vue (9).
Was jedoch überraschend ist, ist das Versagen der
@php
-Direktive in Blade, was dazu führt, dass eigener PHP-Code
direkt in die Ausgabe geschrieben wird, wie Sie in der letzten Zeile sehen.
Smarty ❌❌❌
Nun testen wir das älteste Template-System für PHP, nämlich Smarty (Version 4.3). Zur großen
Überraschung hat dieses System kein aktives automatisches Escaping. Sie müssen
also beim Ausgeben von Variablen entweder jedes Mal den Filter
{$var|escape}
angeben oder das automatische HTML-Escaping
aktivieren. Die Information dazu ist in der Dokumentation ziemlich
versteckt.
{$str = "<'\"&"}
1) <p>{$str}</p>
2) <input value="{$str}">
3) <input value='{$str}'>
{$str = "foo onclick=evilCode()"}
4) <input value={$str}>
{$str = "'\"\n\u{2028}"}
5) <script> let foo = {$str}; </script>
6) <p onclick="foo({$str})"></p>
{$str = "-- ---"}
7) <!-- {$str} -->
{$str = "javascript:evilCode()"}
8) <a href="{$str}">...</a>
{$str = "{{ foo }}"}
9) <div id="app"> {$str} </div>
✅ <p><'"&</p>
✅ <input value="<'"&">
✅ <input value='<'"&'>
❌ <input value=foo onclick=evilCode()>
❌ <script> let foo = '"\u2028; </script>
❌ <p onclick="foo('"\u2028)"></p>
❌ <!-- -- --- -->
❌ <a href="javascript:evilCode()">...</a>
❌ <div id="app"> {{ foo }} </div>
Smarty ist in sechs von neun Tests durchgefallen!
Das Ergebnis ähnelt auf den ersten Blick dem der vorherigen Bibliotheken.
Smarty kann automatisch nur in HTML-Text und Attributen escapen, und das auch
nur, wenn die Werte in Anführungszeichen eingeschlossen sind. Überall sonst
versagt es. In den Kontexten (5) und (6) muss manuell mit
{$str|escape:javascript}
escaped werden. Dies ist jedoch nur
möglich, wenn das automatische HTML-Escaping nicht aktiv ist, da sich diese
Escapings sonst gegenseitig stören. Smarty ist somit aus Sicherheitssicht
der absolute Verlierer dieses Tests.
Latte ✅
Das Trio schließt das Template-System Latte (Version 3.0) ab. Wir testen sein Autoescaping. Seine Antworten und sein Verhalten können Sie ebenfalls im Playground untersuchen.
{var $str = "<'\"&"}
1) <p>{$str}</p>
2) <input value="{$str}">
3) <input value='{$str}'>
{var $str = "foo onclick=evilCode()"}
4) <input value={$str}>
{var $str = "'\"\n\u{2028}"}
5) <script> let foo = {$str}; </script>
6) <p onclick="foo({$str})"></p>
{var $str = "-- ---"}
7) <!-- {$str} -->
{var $str = "javascript:evilCode()"}
8) <a href="{$str}">...</a>
{var $str = "{{ foo }}"}
9) <div id="app"> {$str} </div>
✅ <p><'"&</p>
✅ <input value="<'"&">
✅ <input value='<'"&'>
✅ <input value="foo onclick=evilCode()">
✅ <script> let foo = "'\"\n\u2028"; </script>
✅ <p onclick="foo("'\"\n\u2028")"></p>
✅ <!-- - - - - - -->
✅ <a href="">...</a>
✅ <div id="app"> {<!-- -->{ foo }} </div>
Latte hat in allen neun Aufgaben exzellent abgeschnitten!
Es kam mit fehlenden Anführungszeichen bei HTML-Attributen zurecht,
verarbeitete JavaScript sowohl im <script>
-Element als auch
in Attributen und konnte auch mit der verbotenen Sequenz in HTML-Kommentaren
umgehen.
Mehr noch, es verhinderte die Situation, dass ein Klick auf einen vom Angreifer eingeschleusten Link dessen Code ausführen könnte. Und es kam mit dem Escaping von Tags für Vue zurecht.
Bonus-Test
Eine der wesentlichen Fähigkeiten aller Template-Systeme ist die Arbeit mit
Blöcken und die damit verbundene Template-Vererbung. Wir versuchen daher, allen
getesteten Template-Systemen noch eine Aufgabe zu geben. Wir erstellen einen
Block description
, den wir in einem HTML-Attribut ausgeben. In der
realen Welt würde sich die Definition des Blocks natürlich im Child-Template
befinden und seine Ausgabe im Parent-Template, also zum Beispiel im Layout. Dies
ist nur eine vereinfachte Form, reicht aber aus, um das Autoescaping beim
Ausgeben von Blöcken zu testen. Wie haben sie abgeschnitten?
Twig: durchgefallen ❌ behandelt Zeichen beim Ausgeben von Blöcken nicht
{% block description %}
rock n' roll
{% endblock %}
<meta name='description'
content='{{ block('description') }}'>
<meta name='description'
content=' rock n' roll '> ❌
Blade: durchgefallen ❌ behandelt Zeichen beim Ausgeben von Blöcken nicht
@section('description')
rock n' roll
@endsection
<meta name='description'
content='@yield('description')'>
<meta name='description'
content=' rock n' roll '> ❌
Latte: bestanden ✅ hat problematische Zeichen beim Ausgeben von Blöcken korrekt behandelt
{block description}
rock n' roll
{/block}
<meta name='description'
content='{include description}'>
<meta name='description'
content=' rock n' roll '> ✅
Warum sind so viele Websites anfällig?
Autoescaping in Systemen wie Twig, Blade oder Smarty funktioniert so, dass es
einfach fünf Zeichen <>"'&
durch HTML-Entitäten ersetzt
und den Kontext nicht unterscheidet. Daher funktioniert es nur in einigen
Situationen und versagt in allen anderen. Naives Autoescaping ist eine
gefährliche Funktion, da es ein falsches Gefühl der Sicherheit
erzeugt.
Es ist daher nicht überraschend, dass derzeit mehr als 27 % der Websites kritische Schwachstellen aufweisen, hauptsächlich XSS (Quelle: Acunetix Web Vulnerability Report). Wie kommt man da raus? Verwenden Sie ein Template-System, das Kontexte unterscheidet.
Latte ist das einzige Template-System in PHP, das ein Template nicht nur als Zeichenkette betrachtet, sondern HTML versteht. Es versteht, was Tags, Attribute usw. sind. Es unterscheidet Kontexte. Und deshalb escaped es korrekt im HTML-Text, anders innerhalb eines HTML-Tags, anders innerhalb von JavaScript usw.
Latte stellt somit das einzige sichere Template-System dar.
Darüber hinaus bietet es dank seines Verständnisses von HTML die wunderbaren n:Attribute, die die Benutzer lieben:
<ul n:if="$menu">
<li n:foreach="$menu->getItems() as $item">{$item->title}</li>
</ul>
Um einen Kommentar abzugeben, loggen Sie sich bitte ein