Fetch the Flag CTF 2022: Write-up zu Disposable Message
Michael Aquilina
10. November 2022
0 Min. LesezeitVielen Dank, dass Sie Fetch mit uns gespielt haben! Glückwunsch an die Tausenden Spielerinnen und Spieler, die beiFetch the Flag CTF dabei waren. Ein großes Dankeschön auch an die Snyker, die die Challenges entwickelt, getestet und dokumentiert haben!
In diesem Blogbeitrag geht es um die Challenge Disposable Message bei Snyks Fetch the Flag CTF-Event 2022. Bei dieser Challenge ging es darum, mithilfe von CSS-Injection-Techniken eine anfällige Webseite auszunutzen und die Flag abzurufen. Disposable Message erwies sich als besonders schwierig, da eine strenge Content Security Policy (CSP) aktiv war. Der Schlüssel zur Umgehung dieser Richtlinie war schließlich, auszunutzen, dass Nachrichten nach dem Aufrufen abliefen.
In diesem Blogbeitrag beschreibe ich, wie ich einen funktionierenden Exploit für diese Challenge entwickelt habe. Ich erläutere meinen Weg – von der Untersuchung bis zur Entwicklung eines Exploits auf Grundlage der gewonnenen Erkenntnisse. Der Exploit ist in Python geschrieben. Ich gehe davon aus, dass Sie mit der Sprache vertraut sind. Kenntnisse in CSS, JavaScript und HTML sind ebenfalls hilfreich, aber ich erkläre die meisten erwähnten Konzepte.
Untersuchung
Zu Beginn der Challenge erhalten wir eine sehr kurze Beschreibung: „Diese Nachricht ist abgelaufen.“ Außerdem gibt es einen Link zu einer Webseite.
Die Beschreibung deutet darauf hin, dass die Webseite es uns ermöglicht, Nachrichten zu erstellen, die genau einmal aufgerufen werden können, bevor sie vom Server gelöscht werden. Dass dies in der Beschreibung erwähnt wird, ist wahrscheinlich ein deutlicher Hinweis darauf, dass es eine Rolle spielt (behalten Sie das für später im Hinterkopf).
Beim Aufrufen der Seite sehen wir Folgendes:

Auf der Seite gibt es ein Textfeld zum Verfassen von Nachrichten. Wenn wir eine Nachricht eingeben und auf die Schaltfläche Send! klicken, wird uns die folgende Seite angezeigt:

Wir sehen eine neue URL im Format /view/<message-id> (im Folgenden nennen wir sie die Seite Nachricht anzeigen). Außerdem gibt es eine Schaltfläche, mit der wir die Nachricht in die Zwischenablage kopieren können, sowie eine Schaltfläche Admin-Bot zum Aufrufen auffordern. Wenn wir die URL zum Anzeigen der Nachricht in unserem Browser öffnen, sehen wir die Nachricht, die wir erstellt haben. Rufen wir dieselbe Seite ein zweites Mal auf, antwortet der Webserver mit dem Statuscode 404.

Sehen wir uns nun die Schaltfläche Admin-Bot zum Aufrufen auffordern an. Wir können davon ausgehen, dass durch einen Klick darauf ein entfernter Admin-Bot die Nachricht in einem Browser aufruft. Das können wir uns ziemlich sicher sein, denn kurz nach dem Klick auf die Schaltfläche läuft unsere Nachricht ab. Auch der Name Admin-Bot legt stark nahe, dass dieser Browser die Flag enthält, die wir benötigen.
Werfen wir als Nächstes einen Blick auf den Quellcode der Seite und sehen wir uns an, was auffällt.
Position der Flag
Auf der Seite zum Anzeigen der Nachricht findet sich der folgende HTML-Codeausschnitt:
Laut dem Codekommentar wird das Attribut data-flag dieses div-Elements mit einem Wert aus unseren Cookies befüllt. Wir können das ganz einfach überprüfen, indem wir die Entwicklertools unseres Browsers öffnen und unsere Cookies bearbeiten.
Der naheliegendste Name für das Flag-Cookie ist „flag“. Wenn wir in einem Cookie namens „flag“ den Wert „SNYK{hello-world}“ festlegen und dann die Seite zum Anzeigen der Nachricht erneut öffnen, sehen wir, dass das Attribut korrekt befüllt wird:

CSS-Injection
Wenn wir uns das auf der Seite zum Anzeigen der Nachricht eingebundene Skript ansehen, finden wir außerdem den folgenden JavaScript-Code:
Das Skript prüft, ob in der URL ein Query-String-Parameter „color“ vorhanden ist. Falls ja, wird der Wert dieses Parameters in ein style-Tag eingefügt. Anschließend wird das style-Tag dynamisch in unsere Webseite eingefügt.
Bemerkenswert an diesem style-Tag ist, dass es mithilfe von String-Formatierung erstellt wird. Durch die String-Formatierung können wir das style-Tag verlassen und erweitern, um beliebige CSS-Werte einzufügen. Das wird als CSS-Injection-Schwachstelle bezeichnet.
Wir können ganz einfach testen, ob sich dies für eine CSS-Injection ausnutzen lässt, indem wir den Parameter color auf einen Wert wie color=00ff00;font-size:80px setzen und überprüfen, ob sich die Schriftgröße wie angegeben ändert:

Mit CSS-Injection können wir Informationen aus einer Seite im Browser eines Benutzers exfiltrieren. Bei diesem Injection-Angriff werden mehrere CSS-Selektoren erstellt, die URLs aufrufen, wenn Werte mit bestimmten Selektoren übereinstimmen.
Wenn wir zum Beispiel das Attribut data-flag eines HTML-div-Elements auslesen möchten, könnten wir den Parameter color auf einen Wert wie den folgenden setzen:
Der obige ^=-Selektor ruft die zugehörige URL auf, wenn der Wert des Attributs mit der angegebenen Zeichenfolge beginnt.
Das bedeutet: Wenn das Attribut data-flag des div-Elements beispielsweise mit a beginnt, ruft unser Browser http//evil.com/a auf. Beginnt der Wert mit b, wird http//evil.com/b aufgerufen usw.
Wenn der Server evil.com uns gehört, wissen wir, welche Seiten aufgerufen wurden. Mit genügend Versuchen und CSS-Selektoren können wir das Attribut data-flag Zeichen für Zeichen auslesen.
Content Security Policy
Moderne Webbrowser unterstützen eine Funktion namens Content Security Policy (CSP), die einschränkt, welche Inhalte auf einer Webseite verwendet werden dürfen. Bei dieser Challenge verhindert eine CSP, dass wir die oben beschriebenen CSS-Injection-Techniken direkt einsetzen können.
Wir können die CSP mithilfe der Entwicklertools unseres Browsers überprüfen und uns die Header der Seite ansehen:

Beachten Sie, dass im Abschnitt img-src der Richtlinie nur „self“ und „data“ zugelassen sind. Das bedeutet, dass wir nur URLs verwenden können, die zur selben Domain gehören wie die Disposable-Message-Seite. Insbesondere können wir keine Domain verwenden, die uns gehört, um zu prüfen, welche URLs durch CSS-Injection aufgerufen wurden. Stattdessen können wir jedoch die einmalig aufrufbaren Nachrichten der Challenge nutzen, um diese Einschränkung zu umgehen.
Wir wissen, dass jede Disposable Message nur einmal aufgerufen werden kann. Wurde eine Nachricht bereits aufgerufen, gibt sie den Statuscode 404 zurück. Wir können daher URLs von Disposable Messages in unseren CSS-Injection-Selektoren verwenden und anhand des Statuscodes erkennen, ob sie bereits aufgerufen wurden.
Zusammenfassung der Untersuchung
Das sind die wichtigsten Erkenntnisse aus der Untersuchung, die uns beim Erstellen unseres Exploits helfen. Falls Ihnen der vorherige Abschnitt nicht ganz klar war (oder Sie ihn nur überflogen haben), genügt es, wenn Sie Folgendes verstehen, um mit dem nächsten Abschnitt fortfahren zu können:
Die Seite zum Anzeigen der Nachricht ist anfällig für CSS-Injection über den Query-String-Parameter
?color=. Das bedeutet, dass wir beliebige Stile auf der Seite festlegen können.Die Content Security Policy lässt nur URLs derselben Domain zu. Das bedeutet, dass unser Browser alle externen URLs blockiert.
Eine Nachricht kann nur einmal aufgerufen werden. Das bedeutet, dass wir den Statuscode 200 erhalten, wenn eine Nachricht noch nie aufgerufen wurde, und den Statuscode 404, wenn sie bereits aufgerufen wurde.
Die Flag befindet sich im Attribut
data-flagauf der Seite zum Anzeigen der Nachricht. Dieser Wert wird durch den Cookie-Parameter „flag“ im Browser bereitgestellt. Wahrscheinlich hat der Admin-Bot dieses Cookie mit der richtigen Flag gesetzt.
Sehen wir uns an, wie wir all diese Details miteinander kombinieren können, um einen Exploit zu erstellen.
Der Exploit
Mit CSS-Injection können wir über das Attribut data-flag Informationen zur Flag Zeichen für Zeichen auslesen. Dafür müssen wir jedoch eine URL innerhalb derselben, von der Challenge bereitgestellten Domain verwenden. Beim Exfiltrieren von Zeichen per CSS-Injection kommt es darauf an, festzustellen, ob eine URL aufgerufen wurde, um herauszufinden, welche Zeichen exfiltriert werden.
Zum Glück können wir für jedes Zeichen Nachrichten innerhalb der Domain der Disposable Messages erstellen. Jede URL lässt sich verwenden und CSS-Selektoren mit den jeweiligen Zeichen zuordnen:
Das können wir in Python beispielsweise so umsetzen:
Die Funktion generate_payload muss einen CSS-Selektor erstellen, der die zugehörige URL aufruft, wenn unsere Vermutung stimmt. Die Funktion sieht so aus:
Die Funktion generate_message muss über den Endpunkt POST /new eine neue Nachricht erstellen, der durch die Schaltfläche Send! aufgerufen wird. Anschließend müssen wir die vom Aufruf erzeugten URLs für den Admin-Bot und zum Anzeigen der Nachricht speichern. Diese URLs lassen sich mithilfe eines regulären Ausdrucks aus der Antwort extrahieren:
Sobald unsere Nachrichten und die zugehörigen CSS-Selektor-Payloads erstellt wurden, müssen wir noch eine Nachricht erstellen, die der Admin-Bot aufruft. Diese Nachricht empfängt unsere Payload und ruft abhängig davon, ob unsere Vermutung mit dem Attribut „data-flag“ übereinstimmt, unsere Nachrichten-URLs auf.
Nachdem wir den Admin-Bot ausgelöst haben, müssen wir einige Sekunden warten, bis der Bot die Seite abruft und rendert. Danach prüfen wir die Statuscodes aller zugehörigen Vermutungs-URLs und suchen nach einer Antwort mit Statuscode 404. Finden wir eine solche, wissen wir, dass der Admin-Bot die URL aufgerufen hat, und können unsere Vermutung als richtig markieren.
Den Query-String codieren
Vielleicht ist Ihnen beim Lesen des obigen Codes aufgefallen, dass wir quote_plus verwenden, um den gesamten Query-String zu codieren – einschließlich des Parameternamens und des Query-String-Trennzeichens ?color=. Der Grund dafür ist, dass Payloads, die über den Query-String an den Admin-Bot übergeben werden, ignoriert werden.
Um dieses Problem zu umgehen, können wir den Admin-Bot dazu bringen, den Query-String für einen Teil des URL-Pfads zu halten. Dadurch wird er in die URL zum Anzeigen der Nachricht aufgenommen, die der Bot aufruft.
Wenn wir zum Beispiel den folgenden URL-Pfad haben: /admin-bot/f785781-55f4-4eca-8565-8511f12a4ffc**?color=**ffffff%7D…
Dann ignoriert der Admin-Bot den Query-String-Parameter und ruft einfach den URL-Pfad /view/f785781-55f4-4eca-8565-8511f12a4ffc auf.
Wenn wir den Query-String-Parameter jedoch vollständig codieren, wird er Teil des URL-Pfads:
/admin-bot/f785781-55f4-4eca-8565-8511f12a4ffc**%3Fcolor%3D**ffffff%7D…
Der Webserver scheint diesen Wert zu decodieren, bevor er ihn an den Bot zum Aufrufen weitergibt. Das bedeutet, dass die Seiten-URL, die er aufruft, dann lautet:
/view/f785781-55f4-4eca-8565-8511f12a4ffc?color=ffffff}...
Die vollständige Flag abrufen
Jetzt, da wir wissen, wie wir Vermutungen richtig anstellen, müssen wir den Vorgang so lange wiederholen, bis unsere Vermutung vollständig mit der Flag übereinstimmt. Sobald wir ein } erreichen, wissen wir, dass wir aufhören können.
Aus früheren Flags anderer Challenges wissen wir außerdem, dass Snyk-Flags mit 'SNYK{' beginnen und der Inhalt zwischen den geschweiften Klammern aus UUIDs besteht. Das ist nützlich, denn so können wir unser Alphabet auf die Zeichen „a-f“ und „0-9“ beschränken. Dadurch verringert sich die Zeit, die für die Vermutungen bei jeder Iteration benötigt wird, erheblich.
Exploit-Code
Fügen wir all das zusammen: Der fertige Quellcode sieht so aus:
Wenn wir diesen Code ausführen, erleben wir den erfreulichen Moment, in dem die SNYK-Flag Zeichen für Zeichen offengelegt wird:

Nachricht entsorgt, Challenge erledigt
Wir haben herausgefunden, dass die Disposable-Message-Challenge für CSS-Injection anfällig war. Eine restriktive Content Security Policy machte die Aufgabe zwar schwierig, doch wir konnten einen Exploit entwickeln, um die Flag auszulesen. Dazu nutzten wir den Statuscode 404, den bereits aufgerufene Nachrichten zurückgeben.
Ich hoffe, Ihnen hat dieser CTF-Write-up gefallen und Sie haben vielleicht sogar etwas Neues gelernt! CTF-Challenges sind eine großartige Möglichkeit, mehr über Exploits aus der Praxis zu erfahren. Dadurch können Sie sich besser vor solchen Angriffen in Ihren eigenen Systemen schützen. Möchten Sie erfahren, wie wir alle anderen Flags gefunden haben? Auf unserer Seite Fetch the Flag-Lösungen erfahren Sie, wie wir dabei vorgegangen sind.



