Quiz: Können Sie eine XSS-Schwachstelle abwehren?

vor 2 Jahren von David Grudl  

Testen Sie Ihr Wissen und Ihre Sicherheitsfähigkeiten in diesem Quiz! Können Sie verhindern, dass ein Angreifer die Kontrolle über eine HTML-Seite übernimmt?

In allen Aufgaben geht es um dieselbe Frage: Wie kann man die Variable $str in einer HTML-Seite richtig darstellen, ohne eine XSS-Schwachstelle zu schaffen. Die Grundlage der Verteidigung ist Escaping, d. h. das Ersetzen von Zeichen mit besonderer Bedeutung durch entsprechende Sequenzen. Wenn wir zum Beispiel eine Zeichenkette in HTML-Text ausgeben, in der das Zeichen < eine besondere Bedeutung hat (es zeigt den Anfang eines Tags an), ersetzen wir es durch die HTML-Entität &lt;, und der Browser zeigt das Symbol < korrekt an.

Seien Sie wachsam, denn die XSS-Schwachstelle ist sehr ernst. Sie kann dazu führen, dass ein Angreifer die Kontrolle über eine Seite oder sogar das Konto eines Benutzers übernimmt. Viel Glück und viel Erfolg beim Sichern der HTML-Seite!

Das erste Trio von Fragen

Geben Sie an, welche Zeichen im ersten, zweiten und dritten Beispiel wie behandelt werden müssen:

1) <p><?= $str ?></p>
2) <input value="<?= $str ?>">
3) <input value='<?= $str ?>'>

Wenn die Ausgabe in keiner Weise behandelt würde, würde sie Teil der angezeigten Seite werden. Wenn es einem Angreifer gelänge, die Zeichenfolge 'foo" onclick="evilCode()' in eine Variable einzufügen, und die Ausgabe nicht behandelt würde, würde sein Code beim Klicken auf das Element ausgeführt werden:

$str = 'foo" onclick="evilCode()'
❌ not treated: <input value="foo" onclick="evilCode()">
✅ treated:     <input value="foo&quot; onclick=&quot;evilCode()">

Lösungen für jedes Beispiel:

  1. Die Zeichen < und & stehen für den Anfang eines HTML-Tags und einer Entität; ersetzen Sie sie durch &lt; und &amp;
  2. die Zeichen " und & stehen für das Ende eines Attributwerts und den Anfang einer HTML-Entität; ersetzen Sie sie durch &quot; und &amp;
  3. die Zeichen ' und & stehen für das Ende eines Attributwerts und den Anfang einer HTML-Entität; ersetzen Sie sie durch &apos; und &amp;

Für jede richtige Antwort erhalten Sie einen Punkt. Natürlich können Sie in allen drei Fällen auch andere Zeichen durch Entities ersetzen; das schadet nicht, ist aber nicht notwendig.

Frage Nr. 4

Welche Zeichen müssen ersetzt werden, wenn eine Variable in diesem Zusammenhang angezeigt wird?

<input value=<?= $str ?>>

Die Lösung: Wie Sie sehen können, fehlen hier die Anführungszeichen. Am einfachsten ist es, die Anführungszeichen einfach hinzuzufügen und dann wie in der vorherigen Frage zu entschlüsseln. Es gibt auch eine zweite Lösung, nämlich das Ersetzen von Leerzeichen und aller Zeichen, die innerhalb eines Tags eine besondere Bedeutung haben, wie >, /, = und einige andere durch HTML-Entities.

Frage Nr. 5

Jetzt wird es noch interessanter. Welche Zeichen müssen in diesem Zusammenhang behandelt werden:

<script>
	let foo = '<?= $str ?>';
</script>

Die Lösung: Innerhalb des <script> Tags werden die Escape-Regeln von JavaScript bestimmt. HTML-Entities werden hier nicht verwendet, aber es gibt eine spezielle Regel. Welche Zeichen werden also umgangen? Innerhalb der JavaScript-Zeichenkette wird das Zeichen ', das die Zeichenkette abgrenzt, natürlich durch einen Backslash ersetzt, der durch \' ersetzt wird. Da JavaScript keine mehrzeiligen Strings unterstützt (außer als Template-Literale), müssen wir auch Zeilenumbrüche vermeiden. Beachten Sie jedoch, dass JavaScript neben den üblichen Zeichen \n und \r auch die Unicode-Zeichen \u2028 und \u2029 als Zeilenumbruchszeichen betrachtet, die wir ebenfalls escapen müssen. Schließlich noch die erwähnte Sonderregel: Die Zeichenkette darf nicht </script enthalten. Dies kann z. B. dadurch verhindert werden, dass man es durch <\/script ersetzt.

Wenn Sie das wussten, herzlichen Glückwunsch.

Frage Nr. 6

Der folgende Kontext scheint nur eine Variation des vorherigen zu sein. Glauben Sie, dass die Behandlung anders sein wird?

<p onclick="foo('<?= $str ?>')"></p>

Lösung: Auch hier gelten die Escape-Regeln für JavaScript-Zeichenfolgen, aber im Gegensatz zum vorherigen Kontext, in dem HTML-Entities nicht escaped wurden, werden sie hier escaped. Zuerst wird also die JavaScript-Zeichenkette mit Backslashes escaped und dann werden die Sonderzeichen (" und &) durch HTML-Entities ersetzt. Vorsicht, die richtige Reihenfolge ist wichtig.

Wie Sie sehen können, kann dasselbe JavaScript-Literal in einem <script> Element und in einem Attribut unterschiedlich kodiert werden!

Frage Nr. 7

Kehren wir von JavaScript zurück zu HTML. Welche Zeichen müssen wir innerhalb des Kommentars ersetzen und wie?

<!-- <?= $str ?> -->

Die Lösung: Innerhalb eines HTML- (und XML-) Kommentars können alle herkömmlichen Sonderzeichen wie <, &, " und ' erscheinen. Was verboten ist, und das mag Sie überraschen, ist das Zeichenpaar --. Es ist nicht vorgeschrieben, diese Zeichenfolge zu vermeiden, also bleibt es Ihnen überlassen, wie Sie sie ersetzen. Sie können sie durch Leerzeichen ersetzen. Oder Sie können sie zum Beispiel durch == ersetzen.

Frage Nr. 8

Wir nähern uns dem Ende, also lassen Sie uns versuchen, die Frage zu variieren. Versuchen Sie zu überlegen, worauf Sie achten müssen, wenn Sie eine Variable in diesem Zusammenhang ausdrucken:

<a href="<?= $str ?>">...</a>

Lösung: Zusätzlich zum Escaping ist es wichtig zu überprüfen, dass die URL kein gefährliches Schema wie javascript: enthält, denn eine so zusammengesetzte URL würde den Code des Angreifers ausführen, wenn er angeklickt wird.

Frage Nr. 9

Zum Schluss noch ein Leckerbissen für echte Kenner. Dies ist ein Beispiel für eine Anwendung, die ein modernes JavaScript-Framework verwendet, nämlich Vue. Mal sehen, ob Sie herausfinden können, worauf Sie achten müssen, wenn Sie eine Variable innerhalb des Elements #app ausgeben:

<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 interpretiert den Inhalt dieses Elements als sein Template. Und innerhalb des Templates interpretiert es doppelte geschweifte Klammern, die die Ausgabe von Variablen oder den Aufruf von JavaScript-Code darstellen (z.B., {{ foo }}).

Innerhalb des Elements #app hat also neben den Zeichen < und & auch das Paar {{ eine besondere Bedeutung, die wir durch eine andere geeignete Sequenz ersetzen müssen, um zu verhindern, dass Vue sie als eigenes Tag interpretiert. Das Ersetzen durch HTML-Entities hilft in diesem Fall nicht weiter. Wie geht man damit um? Es gibt einen Trick: Fügen Sie einen leeren HTML-Kommentar zwischen die geschweiften Klammern {<!-- -->{ein, und Vue ignoriert diese Sequenz.

Quiz-Ergebnisse

Wie haben Sie im Quiz abgeschnitten? Wie viele richtige Antworten haben Sie? Wenn du mindestens 4 Fragen richtig beantwortet hast, gehörst du zu den besten 8% der Löser – herzlichen Glückwunsch!

**Um die Sicherheit Ihrer Website zu gewährleisten, ist es jedoch erforderlich, die Ausgabe in allen Situationen richtig zu behandeln.

Wenn Sie überrascht waren, wie viele verschiedene Kontexte auf einer typischen HTML-Seite vorkommen können, sollten Sie wissen, dass wir bei weitem nicht alle erwähnt haben. Das würde das Quiz viel länger machen. Dennoch müssen Sie nicht in jedem Kontext ein Experte für Escaping sein, wenn Ihr Templating-System damit umgehen kann.

Also, lass sie uns testen.

Wie funktionieren die Templating-Systeme?

Alle modernen Templating-Systeme verfügen über eine autoescaping-Funktion, die alle ausgegebenen Variablen automatisch entschlüsselt. Wenn sie das richtig machen, ist Ihre Website sicher. Wenn sie es schlecht machen, ist die Website dem Risiko einer XSS-Schwachstelle mit all ihren ernsten Konsequenzen ausgesetzt.

Wir werden beliebte Templating-Systeme anhand der Fragen in diesem Quiz testen, um die Effektivität ihres Auto-Escapings zu bestimmen. Beginnen wir mit der Überprüfung der PHP-Templating-Systeme.

Twig ❌

Den Anfang macht das Templating-System Twig (Version 3.5), das am häufigsten in Verbindung mit dem Symfony-Framework verwendet wird. Wir werden es mit der Beantwortung aller Quizfragen beauftragen. Die Variable $str wird immer mit einer kniffligen Zeichenkette gefüllt, und wir werden sehen, wie es mit seiner Ausgabe umgeht. Die Ergebnisse sehen Sie auf der rechten Seite. Sie können auch die Antworten und das Verhalten des Systems auf dem Spielplatz 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>&lt;&#039;&quot;&amp;</p>
✅ <input value="&lt;&#039;&quot;&amp;">
✅ <input value='&lt;&#039;&quot;&amp;'>


❌ <input value=foo onclick=evilCode()>


❌ <script> let foo = &#039;&quot;u{2028}; </script>
❌ <p onclick="foo(&#039;&quot;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 bei HTML-Text und -Attributen, und auch dann nur, wenn sie in Anführungszeichen eingeschlossen sind. Sobald die Anführungszeichen fehlen, meldet Twig keinen Fehler und schafft eine XSS-Sicherheitslücke.

Dies ist besonders unangenehm, weil Attributwerte in beliebten Bibliotheken wie React oder Svelte auf diese Weise geschrieben werden. Ein Programmierer, der sowohl Twig als auch React verwendet, kann die Anführungszeichen natürlich vergessen.

Auch in allen anderen Beispielen versagt Twigs Autoescaping. In den Kontexten (5) und (6) ist ein manuelles Escaping mit {{ str|escape('js') }}erforderlich, während Twig für andere Kontexte nicht einmal eine Escaping-Funktion anbietet. Es fehlt auch ein Schutz gegen das Drucken eines bösartigen Links (8) oder Unterstützung für Vue-Vorlagen (9).

Blade ❌❌

Der zweite Teilnehmer ist das Templating-System Blade (Version 10.9), das eng mit Laravel und seinem Ökosystem integriert ist. Auch hier werden wir seine Fähigkeiten anhand unserer Quizfragen testen. Sie können auch seine Antworten auf dem Spielplatz erkunden.

   @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>&lt;&#039;&quot;&amp;</p>
✅ <input value="&lt;&#039;&quot;&amp;">
✅ <input value='&lt;&#039;&quot;&amp;'>


❌ <input value=foo onclick=evilCode()>


❌ <script>	let foo = &#039;&quot; ; </script>
❌ <p onclick="foo(&#039;&quot; )"></p>


❌ <!-- -- --- -->


❌ <a href="javascript:evilCode()">...</a>


❌❌ <div id="app"> &lt;?php echo e(foo); ?&gt; </div>

Klinge ist in sechs von neun Tests durchgefallen!

Das Ergebnis ist ähnlich wie bei Twig. Auch hier funktioniert das automatische Escaping nur bei HTML-Text und -Attributen und nur, wenn diese in Anführungszeichen eingeschlossen sind. Das automatische Escaping von Blade schlägt auch in allen anderen Beispielen fehl. In den Kontexten (5) und (6) ist manuelles Escapen erforderlich, indem man {{ Js::from($str) }}. Für andere Kontexte bietet Blade nicht einmal eine Escaping-Funktion an. Es fehlt auch der Schutz gegen das Drucken eines bösartigen Links (8) oder die Unterstützung für Vue-Vorlagen (9).

Überraschend ist jedoch das Versagen der @php Direktive in Blade, die die Ausgabe des eigenen PHP-Codes direkt in die Ausgabe bewirkt, wie in der letzten Zeile zu sehen ist.

Latte ✅

Das Trio wird durch das Schablonensystem Latte (Version 3.0) abgeschlossen. Wir werden sein automatisches Ausschneiden testen. Sie können auch seine Antworten und sein Verhalten auf dem Spielplatz erkunden.

   {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>&lt;'"&amp;</p>
✅ <input value="&lt;&apos;&quot;&amp;">
✅ <input value='&lt;&apos;&quot;&amp;'>


✅ <input value="foo onclick=evilCode()">


✅ <script> let foo = "'\"\n\u2028"; </script>
✅ <p onclick="foo(&quot;&apos;\&quot;\n\u2028&quot;)"></p>


✅ <!--  - -  - - -  -->


✅ <a href="">...</a>


✅ <div id="app"> {<!-- -->{ foo }} </div>

Latte hat in allen neun Aufgaben brilliert!

Sie schaffte es, fehlende Anführungszeichen in HTML-Attributen zu behandeln, verarbeitete JavaScript sowohl im <script> Element als auch in den Attributen und kam mit der verbotenen Sequenz in HTML-Kommentaren zurecht.

Darüber hinaus wurde verhindert, dass das Anklicken eines bösartigen Links, der von einem Angreifer bereitgestellt wurde, dessen Code ausführen konnte. Und es hat das Escaping von Tags für Vue gemeistert.

Bonus-Test

Eine der wesentlichen Fähigkeiten aller Template-Systeme ist die Arbeit mit Blöcken und die damit verbundene Template-Vererbung. Deshalb werden wir allen getesteten Templatesystemen eine weitere Aufgabe stellen. Wir werden einen description Block erstellen, den wir in einem HTML-Attribut ausgeben werden. In der realen Welt würde sich die Blockdefinition natürlich in der untergeordneten Vorlage befinden und ihre Ausgabe in der übergeordneten Vorlage, z. B. im Layout. Dies ist nur eine vereinfachte Form, aber sie reicht aus, um die automatische Groß- und Kleinschreibung bei der Ausgabe von Blöcken zu testen. Wie haben sie abgeschnitten?

Twig: fehlgeschlagen ❌ bei der Ausgabe von Blöcken, Zeichen werden nicht richtig escaped

{% block description %}
	rock n' roll
{% endblock %}

<meta name='description'
	content='{{ block('description') }}'>




<meta name='description'
	content=' rock n' roll '> ❌

Blade: Fehler ❌ bei der Ausgabe von Blöcken, Zeichen werden nicht richtig escaped

@section('description')
	rock n' roll
@endsection

<meta name='description'
	content='@yield('description')'>




<meta name='description'
	content=' rock n' roll '> ❌

Latte: ✅ bei der Ausgabe von Blöcken übergeben, problematische Zeichen werden korrekt behandelt

{block description}
	rock n' roll
{/block}

<meta name='description'
	content='{include description}'>




<meta name='description'
	content=' rock n&apos; roll '> ✅

Warum sind so viele Websites verwundbar?

Autoescaping in Systemen wie Twig, Blade oder Smarty ersetzt einfach fünf Zeichen <>"'& durch HTML-Entities und unterscheidet nicht zwischen dem Kontext. Daher funktioniert es nur in einigen Situationen und versagt in allen anderen. Naives Autoescaping ist eine gefährliche Funktion, weil sie ein falsches Gefühl von Sicherheit vermittelt.

Es ist daher nicht überraschend, dass derzeit mehr als 27 % der Websites kritische Schwachstellen aufweisen, hauptsächlich XSS (Quelle: Acunetix Web Vulnerability Report). Wie kann man sich aus dieser Situation befreien? Verwenden Sie ein Templating-System, das Kontexte unterscheidet.

Latte ist das einzige PHP-Schablonensystem, das eine Schablone nicht nur als eine Zeichenfolge betrachtet, sondern HTML versteht. Es weiß, was Tags, Attribute, etc. sind. Es unterscheidet zwischen Kontexten. Es unterscheidet zwischen Kontexten und kann daher in HTML-Text, in HTML-Tags, in JavaScript usw. korrekt escapen.

Latte ist somit das einzige sichere Template-System.


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>