Cloudflare-WAF vs XSS: Wer wird gewinnen?

Die Web-Application-Firewall (WAF) von Cloudflare kann potentiell gefährliche HTTP-Anfragen blockieren und somit vor Cross-Site-Scripting sowie SQL-Injections schützen. Jedoch sollte man sich nicht auf WAFs verlassen. Denn diese erkennen derartige Angriffe nicht immer zuverlässig. In diesem Beitrag versuchen wir mal die XSS-Protection zu umgehen 🙂

Let’s pick a Target

Als Ziel des Angriffs dient heute mal zap-hosting.com, ein recht bekannter Hosting-Anbieter für Gameserver, vServer etc. Damit die User ihren Account durch eine zusätzliche Sicherheitsstufe absichern können, gibt es eine 2-Faktor-Authentifizierung per Google Authenticator. Leider bringt diese Funktion nicht nur eine zusätzliche Sicherheitsmaßnahme sondern auch eine reflected XSS Lücke.

Nach dem Login erfolgt die Abfrage des OTP. Dieser wird mithilfe eines Ajax-Request im Backend validiert. Die URL lautet:

https://zap-hosting.com/interface/customer/_ajax/json_twoFactor.php?auth_info=123123

Diese URL ist ohne weiteres direkt im Browser aufrufbar. Völlig egal ob die 2-Faktor-Authentifizierung aktiviert ist oder nicht. Der JSON-Response trägt den Content-Type text/html und reflektiert den Parameter auth_info. Der Grundstein für eine XSS-Lücke ist somit gelegt.

WAF blockiert keine HTML-Injection

HTML-Tags wie bspw. <div>, <form> und <b> werden von der WAF nicht beachtet, solange diese keine Event-Attribute beinhaltet oder sonstige Aktionen ausführen.

Daher können Inhalte wie Grafiken und Videos ohne eine Blockierung seitens der WAF eigebunden werden. Die WAF mischt sich grundsätzliche erst ein, sobald JavaScript-Inhalte erkannt werden.

Cross-Site-Scripting trotz WAF?

Die gängigen XSS-Ansätze werden meist recht zuverlässig erkannt und blockiert. Hier z.B. eine <svg onload-Payload:

Dennoch gibt es immer wieder gewisse Payloads, welche von den XSS-Filtern nicht erkannt werden. Oft wird dazu in den Payloads eine ungewöhnliche Zeichenkodierung genutzt. Ein anderer Ansatz kann aber auch die Verwendung einer falschen HTML-Syntax sein. Die meisten Browser können auch mit Syntaxfehlern was anfangen und versuchen diese zu fixen, was erst dann zur Ausführung von JavaScript führt.

Bei der oben verwendeten Payload <svg onload="alert(0)" /> gilt es zunächst herauszufinden, welche Struktur die WAF dazu veranlasst, den Request zu blockieren. Am besten man nähert sich Schrittweise der Payload.

<svg />

Geht durch.

<svg onload />

Geht ebenfalls durch.

<svg onload= />

Block! Nun eine kleine Änderung. Das Slash am Ende fällt weg.

<svg onload= >

Geht erneut durch.

<svg onload=test=123 >

Auch das geht durch. Hier wird gezielt auf Anführungszeichen verzichtet. Denn diese sind für Attributwerte nicht zwingend erforderlich. Oder besser gesagt: Die meisten Browser fixen diesen Missstand automatisch. Eigentlich wurde nun bereits ein JavaScript-Statement ausgeführt. Nämlich die Anweisung test auf 123 zu setzen.

Ziel ist es aber, eine Funktion z.B. alert() aufzurufen.

<svg onload=alert(0)>

Block! Während…

<svg onload=alert>

…ohne Probleme durch geht. Schlussfolgerung: Die Klammern sind schuld!

<svg onload=alert0)>

Geht hingegen ebenfalls durch. Schlussfolgerung: ( führt hier zum Block!

Auf’s Encoding kommts an

In der Regel sind WAFs auch in der Lage die gängigen Encodings zu erkennen. Da gibts z.B. die HTML-Entities im Dezimal-Format (z.B. &#9986; für das Zeichen ✂) oder im Hexadzimal-Format (z.B. &#x2702), ebenfalls für das Zeichen ✂). Diese Kodierungen sind gängig zur Darstellung von Sonderzeichen in HTML. In einer URL führen diese jedoch schnell zur Verwirrung, da die Zeichen &# noch zusätzlich URL-encoded werden. Eine WAF muss also mehrere Encodings beachten, welche auch gemischt verwendet werden können.

Eine Klammer ( ergibt als Dezimale-HTML-Entity den Wert &#40; .

Das Ganze dann URL-Encoded: %26%2340%3B

Eingesetzt in die Payload:

<svg onload=alert%26%2340%3B0)>

Block! Leider wird auch das von der Cloudflare WAF erkannt 🙁

Man kann nun aber ein bisschen experimentieren. HTML-Entities entsprechen im Grunde nur &# + einer Zahl im Dezimal- oder Hexadezimal-Format. Diese Zahl stellt die Position auf der Unicode-Tabelle dar.

Der Browser verarbeitet diese Zahl vermutlich auch als Zahl, z.B. als Integer-Datentyp. Führende Nullen werden bekanntlich nicht beachtet.

Bedeutet: &#0000000040; wird vom Browser als &#40; interpretiert und ergibt ebenfalls ein ).

Das nun wieder URL-encoded ergibt %26%230000000040%3B und die Payload:

<svg onload=alert%26%230000000040%3B0)>
Yeyyyy! Cloudflare-XSS-Bypass:

Hier noch ein Blick, was der Server zurückgibt:

Und was der DOM-Parser daraus macht (hier in Chrome):

Funfact: Erst ab acht führenden Nullen geht der Request durch. Bei nur sieben führenden Nullen greift die WAF. Warum auch immer 🤷‍♂️

Fazit

Eine WAF kann viele Angriffe erkennen. Aber gerade bei Cross-Site-Scripting gibt es eine Vielzahl an möglichen Payloads. Das in Verbindung mit speziellen Encodings kann eine WAF aber schnell an ihre Grenzen bringen. Daher ersetzt eine WAF nicht die Einhaltung von Sicherheitsrichtlinien bei der Webseitenerstellung 🙂

ZAP-Hosting hat die Lücke übrigens sehr schnell und vorbildlich im Rahmen des Bug-Bounty-Programms gefixt und mit 50€ vergütet.

Kommentare

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.