Verbreitete Fehler beim Einsatz einer Blacklist gegen XSS-Lücken.

Cross-Site-Scripting Lücken entstehen auf einer Webseite an Stellen, an denen Inhalte, auf die ein Nutzer einen Einfluss hat, in die Webseite eingebunden werden. Durch speziell veränderte Eingaben kann ein Angreifer aus dem Kontext, wie einem HTML-Element oder einer JavaScript Anweisung, ausbrechen und dann eigenen JavaScript Code ausführen.
Kommt ein Nutzer dann z.B. über einen modifizierten Link auf eine auf diese Weise veränderte Seite, dann merkt er dies in der Regel nicht. Wir wissen alle, dass man keine merkwürdigen Links in Emails anklicken sollte, die auf unbekannte Seiten führen. Hier sieht der Link allerdings so aus, als ob er auf eine Seite führt, der wir vertrauen, weil wir sie kennen, oft benutzen oder schon einmal besucht haben und genau das ist das Fatale.
Enthält eine Webseite eine XSS-Lücke, so steht Kriminellen eine breite Palette an Möglichkeiten zur Verfügung. Einige verbreitete Angriffsszenarien sind z.B. die Ausführung von schädlichem Java-Script Code, die Veränderung von Inhalten und Verunglimpfung der Seiteninhaber oder eben die Weiterleitung auf genau die mit Schadcode infizierte Webseite, auf deren Link wir in der Email nicht geklickt hätten.

Nun existieren Cross-Site-Scripting Lücken schon sehr lange, es gibt verschiedenste Methoden um sie zu verhindern und doch halten Sie sich hartnäckig auch auf größeren und bekannten Seiten. Um XSS-Lücken effektiv zu verhindern, sollte man sich an den OWASP Regeln zu diesem Thema orientieren.

XSS Filter mit einer Blacklist


Eine Methode, die ich nicht empfehle, ist es, gefährliche Zeichen und Wörter aus den Nutzereingaben zu filtern, bevor man den Inhalt in die Webseite einfügt.
Dazu definiert man eine Blacklist mit den verbotenen Werten und überprüft die Eingaben auf genau diese.
Das könnte dann ungefähr so aussehen:

Start
Wenn Eingabe \ enthält entferne diesen Teil
Wenn Eingabe document.cookie enthält entferne diesen Teil
Wenn Eingabe ' enthält entferne diesen Teil
Ende

Wo liegt hier das Problem?


Abgesehen davon, dass eine Blacklist in den seltensten Fällen eine ausreichende Maßnahme darstellt, weil sie durch verschiedenste Methoden umgangen werden kann (Groß,- und Kleinschreibung, Kommentare in den Worten, ungewöhnliche Funktionsaufrufe, uvm.), existiert hier ein weiteres Problem.
Betrachten wir dazu eine beispielhafte JavaScript Anweisung in die eine Nutzereingabe über PHP eingebunden wird:

var suchwort = '##Nutzereingabe##';
wobei ##Nutzereingabe## die entsprechend gefilterte Nutzereingabe ist

Nutzereingabe:
';alert(document.cookie);t='

Start
Der Filter erkennt kein \ und geht zum nächsten Schritt.
Der Filter erkennt ein document.cookie, entfernt es und geht zum nächsten Schritt.
Der Filter erkennt ', entfernt es und geht zum nächsten Schritt.
Ende


gefilterte Nutzereingabe:
;alert();t=

Resultat:
var suchwort = ';alert();t=';

Unsere Blacklist hat ' und document.cookie erkannt und entfernt. Der eingefügte JavaScript Code wird nicht ausgeführt, da er einfach nur als String, also als Wort, interpretiert wird und alles scheint gut.

Doch was passiert, wenn der Angreifer die gefilterten Zeichenketten kombiniert?

Nutzereingabe:
'';alert(document.cookiedocument.cookie);t=''

Start
Der Filter erkennt kein \ und geht zum nächsten Schritt.
Der Filter erkennt ein document.cookie, entfernt es und geht zum nächsten Schritt.
Der Filter erkennt ', entfernt es und geht zum nächsten Schritt.
Ende


gefilterte Nutzereingabe:
';alert(document.cookie);t='

Resultat:
var suchwort = '';alert(document.cookie);t='';

Es erscheint ein JavaScript Fenster mit dem Inhalt der Cookies unserer Seite.

Was ist passiert?


Unsere Blacklist hat ' und document.cookie erkannt und entfernt. Allerdings durchlaufen wir unseren Filter nur ein einziges Mal und durch die doppelte Eingabe, bleibt jede verbotene Zeichenkette einmal übrig, was dazu führt, dass der JavaScript Code ausgeführt wird.
Wir brauchen also eine Schleife:

Start
Solange Eingabe \ enthält entferne diesen Teil
Solange Eingabe document.cookie enthält entferne diesen Teil
Solange Eingabe ' enthält entferne diesen Teil
Ende


Mal sehen, ob das ausreichend ist und gut funktioniert.

Nutzereingabe:
'';alert(document.cookiedocument.cookie);t=''

Start
Der Filter erkennt kein \ und geht zum nächsten Schritt.
Der Filter erkennt document.cookie und entfernt es.
Der Filter erkennt document.cookie und entfernt es.
Der Filter erkennt kein document.cookie mehr und geht zum nächsten Schritt.
Der Filter erkennt ' und entfernt es.
Der Filter erkennt ' und entfernt es.
Der Filter erkennt kein ' mehr und geht zum nächsten Schritt.
Ende


gefilterte Nutzereingabe:
;alert();t=

Resultat:
var suchwort = ';alert();t=';

Der Filter mit der Schleife scheint zu funktionieren, Richtig?

Falsch!


Betrachten wir dazu das folgende Beispiel.

Nutzereingabe:
</script><script>alert(docum'ent.cookie)</script>

Start
Der Filter erkennt kein \ und geht zum nächsten Schritt.
Der Filter erkennt kein document.cookie und geht zum nächsten Schritt.
Der Filter erkennt ' und entfernt es.
Der Filter erkennt kein ' mehr und geht zum nächsten Schritt.
Ende


gefilterte Nutzereingabe:
</script><script>alert(document.cookie)</script>

Resultat:
var suchwort = '</script><script>alert(document.cookie)</script>';

Wo liegt nun schon wieder der Fehler?


Abgesehen davon, dass unsere Blacklist unvollständig ist, was hier das Ausbrechen aus dem JavaScript Kontext mit einem schließenden Script-Tag ermöglicht, besteht auch hier ein anderes Problem. Dadurch, dass wir alle Filterschleifen nacheinander durchlaufen, erkennt die für die Zeichenkette document.cookie zuständige Schleife ihren Term nicht und führt keine Aktion durch. Die Schleife danach entfernt das Hochkomma aus dem docum'ent.cookie und macht dies so zu einem document.cookie, was wir ja ursprünglich filtern wollten.

Wir müssen also in jedem Schleifendurchlauf auf jede Zeichenkette aus unsere Blacklist prüfen und erst aufhören, wenn kein einziger mehr gefunden wurde.
Trotzdem bleibt das Problem, dass eine Blacklist in der Regel unvollständig ist und auf verschiedenste Wege umgangen werden kann, weshalb man zum Verhindern von XSS-Lücken einen anderen Ansatz wählen sollte.
Denis Werner
07.10.2014

zurück