// HackTheBox
Curling
über secret.txt geleakte Joomla-Credentials → Template-Edit-RCE → curl -K Konfigurations-Injection über einen root-Cron-Job
Aufklärung
Zwei Ports, und nur einer davon ist eine echte Angriffsfläche:
| Port | Dienst |
|---|---|
| 22 | SSH |
| 80 | Apache + Joomla |
SSH gibt ohne Credentials nichts her, also ist die Joomla-Seite auf Port 80 die ganze Box. Joomla ist ein PHP-CMS, dessen Administrator-Panel Template-Quellcode direkt bearbeiten kann — wenn wir uns also als Admin einloggen können, ist dieses Panel ein direkter Weg zur Codeausführung. Die erste Aufgabe ist daher, ein Admin-Passwort zu finden.
Erstzugang
1. Geleaktes Passwort in secret.txt
Der Quelltext der Startseite enthält einen HTML-Kommentar, den ein normaler Besucher nie sieht, der aber im Markup ausgeliefert wird:
<!-- secret.txt -->
Der Aufruf von /secret.txt liefert einen einzelnen Base64-String,
Q3VybGluZzIwMTgh. Base64 ist trivial umkehrbar, also dekodieren wir ihn:
$ echo Q3VybGluZzIwMTgh | base64 -d
Curling2018!
Die Seite schreibt ihre Artikel dem Autor floris zu, was uns einen
Benutzernamen zum wiedergewonnenen Passwort Curling2018! liefert.
2. Admin-Login → Template-Edit-RCE
floris:Curling2018! loggt direkt in das Joomla-Administrator-Panel ein. Joomlas
Template-Manager erlaubt einem Super User, die PHP-Dateien des aktiven Templates
im Browser zu bearbeiten — der klassische Joomla-zu-RCE-Trick ohne CVE: PHP in
eine Template-Datei schreiben und diese Datei dann über das Web aufrufen.
Bearbeite die error.php des Protostar-Templates und injiziere eine einzeilige
Kommando-Shell:
<?php system($_REQUEST['c']); ?>
Das Speichern wird vom template.save-Task der com_templates-Komponente
verarbeitet, der neben dem bearbeiteten Quellcode die Felder
jform[extension_id] und jform[filename] sowie ein gültiges CSRF-Token
benötigt — Joomla verweigert das Schreiben stillschweigend, wenn eines davon
fehlt.
Die platzierte Datei führt nun jeden Befehl aus, der im Parameter c übergeben
wird. Der Aufruf von /templates/protostar/error.php?c=id führt id aus, und der
Code läuft als www-data, dem Apache/PHP-Benutzer.
3. password_backup → floris’ SSH-Passwort
Im Home-Verzeichnis von floris liegt eine Datei namens password_backup. Ihr
Inhalt ist ein Text-Hexdump — die druckbare Ausgabe von xxd — statt reiner
Binärdaten, der erste Schritt ist also, den Hexdump mit xxd -r wieder in Bytes
umzukehren. file auf dem Ergebnis zeigt, dass es bzip2-komprimiert ist, und jede
Schicht darunter entpuppt sich als weiterer Kompressor: die Verschachtelung ist
bzip2 → gzip → bzip2 → tar. Schäle sie eine nach der anderen mit dem passenden
Werkzeug ab und entpacke dann das Archiv:
$ xxd -r password_backup > stage1.bz2
$ bunzip2 -c stage1.bz2 > stage2.gz
$ gunzip -c stage2.gz > stage3.bz2
$ bunzip2 -c stage3.bz2 > stage4.tar
$ tar xf stage4.tar
$ cat password.txt
5d<wdCbdZu)|hChXll
5d<wdCbdZu)|hChXll ist floris’ SSH-Passwort, also verwandelt ssh floris@...
die www-data-Webshell in eine echte interaktive Sitzung als floris und liefert
user.txt.
Rechteausweitung — curl -K Konfigurations-Injection über root-Cron
Beim Durchsehen von floris’ Dateien taucht ~/admin-area/input auf, gehört
root:floris mit dem Modus rw-rw---- — für floris schreibbar. Es ist keine
Datendatei, sondern eine curl-Konfigurationsdatei (das Format, das curl mit
seinem -K-Flag liest), und ein root-Cron-Job führt in kurzen Abständen
curl -K input -o report aus und schreibt das Ergebnis nach report (das floris
lesen kann). Die Standardkonfiguration ruft einfach nur den lokalen Webserver ab:
url = "http://127.0.0.1"
Da curl-Konfigurationsdirektiven url umfassen und curls file://-Schema lokale
Dateien liest, können wir diesen Abruf auf eine beliebige Stelle im Dateisystem
richten. curl läuft über den Cron als root, kann also root-eigene Dateien lesen.
Überschreibe die URL der Konfiguration:
url = "file:///root/root.txt"
Beim nächsten Cron-Tick liest roots curl /root/root.txt und schreibt es nach
report, das floris dann lesen kann:
$ echo 'url = "file:///root/root.txt"' > ~/admin-area/input
$ cat ~/admin-area/report
Timing. Eine Anti-Tamper-Routine der Box setzt input alle ~10-15 Sekunden
auf den Standard zurück, und der curl-Cron feuert innerhalb desselben Fensters —
schreibe input und lies report also innerhalb weniger Sekunden voneinander,
bevor der Reset die injizierte URL überschreibt.
Der Ausgabepfad -o report ist im Cron-Befehl fest einkodiert, das Primitive ist
also streng nur lesend: wir können root beliebige Dateien in report einlesen
lassen, aber wir können diese Ausgabe nicht umleiten, um etwas zu überschreiben
(z. B. eine authorized_keys oder ein SUID-Binary) für eine vollständige Shell.
root.txt wird direkt erfasst, und da root keinen id_rsa zum Stehlen hat, ist
dieses Lese-Primitive genau genug, um die Box abzuschließen.