En este writeup vamos a resolver una de las máquinas de la plataforma Dockerlabs llamada Psycho, con el nivel de dificultad «Fácil».
Al final del write up te cuento un resumen de toda la máquina. 🙂
Si quieres conocer más sobre la plataforma Dockerlabs y ver cómo se configura, etc, en este post te cuento cómo hacerlo.
Básicamente los pasos son: 1 – Creas una carpeta con el nombre de la máquina (es opcional, yo las creo por organización), 2 – Descargas la máquina de Dockerlabs y la mueves al directorio que has creado anterior, y 3 – Descomprimes y despliegas.
Para todas las máquinas, la IP de Dockerlabs es siempre la misma: 172.17.0.2

Una vez hemos descargado la máquina víctima de Dockerlabs y desplegada en nuestro entorno, empezamos con el escaneo.
Estructura del contenido
RECONOCIMIENTO Y ESCANEO DE PUERTOS Y SERVICIOS
Comenzamos primero viendo si la máquina está activa y podemos conectarnos a ella, y después continuamos con el descubrimiento de puertos para luego hacer un escaneo de sus servicios y versiones.
¿Está la máquina víctima activa y nos podemos comunicar con ella?
Aquí lo que haremos es ejecutar estos comandos para ver si el «host is up» y ver el SO.

En este caso el TTL es de 64 por lo que estamos antes una máquina Linux.
A partir de aquí comenzamos con el escaneo de puertos con el siguiente comando (si quieres conocer el significado de cada comando, te recomiendo leer este artículo):
nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn 172.17.0.2 -oG Escaneo

El escaneo nos devuelve información de los puertos 22 SSH y 80 HTTP abiertos.
Analizamos ahora cuáles son los servicios que corren para esos puertos y sus versiones a través del comando:
nmap -sCV -p22,80 172.17.0.2

Analizamos de forma rápida con searchsploit si para esos servicios y versiones exite algún exploit público que nos ayude con la explotación.

No encontramos nada ni sobre las versión de SSH que es nueva, ni sobre la versión del servidor Apache 2.4.58.
En este punto pasamos a profundizar en el análisis del puerto 80 web, ya que como ocurre con la mayoría de laboratorios, será el que nos proporcione credenciales para acceder mediante ssh después.
Analizamos la web inspeccionando manualmente la página, revisando su código fuente, buscando directorios o archivos ocultos, y cualquier pista que nos ayude a descubrir vulnerabilidades.

Veo que los enlaces internos son url con referencia a la misma página, no útiles. Solo veo un mensaje de [!]ERROR[!] al final de la página, que por ahora dejo en stand-by. No veo nada raro en el código fuente ni en la info básica de la página con whatweb:

Sin más información a priori conocida, paso a hacer fuzzing web para descubrir directorios o archivos ocultos que no son accesibles desde la página raíz.
Para ello usaremos Gobuster:
gobuster dir -u http://172.17.0.2/ -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 30 -x php,html,txt,zip

Nos devuelve tres directorios/archivos, el index.php (es la propia home de la web), /server-status (página 403 que nos da la info del servidor que ya conocemos del escaneo inicial), y por último tenemos el /assets , que nos muestra una imagen en jpg que si la abrimos es totalmente negra.


Voy a descargarme la imagen y analizarla con Exiftool por si hubiera algo de información oculta en algún metadato de la imagen:

No veo nada de información relevante, uso la herramienta de «steghide» como método de esteganografía para extraer algo de info pero nos pide una contraseña que no tenemos…

Por lo que parece que esta parte del análisis de la imagen es un rabbit hole…
Si vamos de nuevo atrás cuando hicimos el primer fuzzing, vimos que el /index.php daba el error 403 con la información del servidor del Apache, pero a veces este podría aceptar parámetros en la URL (por ejemplo tipo index.php?EJEMPLO=valor).
Vamos a probar a hacer fuzzing con Dirbuster sobre este path de url:


Descubrimos un parámetro llamado secret que al ser llamado no devolvía error 404 sino una respuesta 200 (indicando que el parámetro existe).
Es decir, la URL http://172.17.0.2/index.php?secret=id parece ser reconocida por la aplicación.
Este seguramente es el parámetro oculto que causaba el mensaje de error al inicio.

Sin embargo al ejecutarla en el navegador, no refleja ningún valor por lo que no es inmediatamente vulnerable a XSS, ni comportamientos raros con comillas, lo que sugiere que SQLi tampoco.
LOCAL FILE INCLUSION (LFI)
Pero dado el tipo de error y que es un parámetro utilizado probablemente para incluir algún recurso, quizás sí sea vulnerable a LFI (Local File Inclusion).
LFI ocurre cuando una aplicación web incluye archivos locales del servidor basándose en un parámetro proporcionado por el usuario, sin sanitizar adecuadamente la ruta.
Si el parámetro secret se utiliza en un include o para leer archivos, quizás podamos aprovecharlo para leer archivos arbitrarios del sistema usando la clásica técnica de path traversal (../ para retroceder directorios).
Considerando lo anterior, procedemos a comprobar si realmente el parámetro secret es vulnerable a Inclusión de Archivos Locales (LFI). Para ello intentamos hacer la petición de un archivo sensible conocido del sistema, por ejemplo /etc/passwd.
Aplicamos path traversal usando cuatro «../» asumiendo que podríamos encontrar algún archivo en /var/www/html .
Para ello aplicamos una petición curl por consola con el siguiente comando:
curl -s "http://172.17.0.2/index.php?secret=../../../../etc/passwd"
y vemos que funcionó devolviendo en la respuesta el contenido del archivo /etc/passwd del servidor, es decir, que efectivamente es vulnerable a LFI.

También podríamos haber incluido la url directamente en el navegador y nos lo hubiera mostrado al final de la página:

En la salida vemos la lista de usuarios del sistema, entre ellos dos usuarios interesantes al final: luisillo y vaxei .

Ambos usuarios tienen shells asignados (/bin/bash y /bin/sh respectivamente). Parecen ser usuarios reales por lo que las consideraremos como cuentas objetivo para comprometer.
luisillo y vaxei pueden entrar al sistema (no tienen /usr/sbin/nologin) por lo que podríamos aplicarles fuerza bruta en busca de credenciales.
¿Qué ha pasado hasta aquí? (LFI en el parámetro secret) : la aplicación web devuelve un error ya que probablemente hace algo como
include($_GET['secret'])ofile_get_contents()sin sanitizar adecuadamente. Al darle una ruta con../../, logramos salir del directorio web y acceder a archivos del sistema. Este tipo de vulnerabilidad nos permite leer cualquier archivo al que el usuario del servidor web (www-data) tenga permiso de lectura. Archivos típicos a explotar incluyen: /etc/passwd, /etc/hosts, archivos de configuración de la web (wp-config.php por ejemplo en WordPress), y especialmente claves privadas SSH en /home de usuarios, si existen.
http://172.17.0.2/index.php?secret=id
http://172.17.0.2/index.php?secret=../../../../etc/passwd
En este punto ya tenemos información clave que nos permite pasar a la fase de explotación.
FUERZA BRUTA CON HYDRA
Vamos a intentar realizar un ataque de fuerza bruta con Hydra para extraer las contraseñas de esos dos usuarios y entrar por ssh:

parece que no encuentra nada en el diccionario de contraseñas, por lo que vamos a probar con otra opción, y es directamente a través de LFI.
Hay veces en las que los usuarios en Linux configuran autenticación por clave pública en SSH. Si existe una clave privada en ~/.ssh/ de luisillo o vaxei, y podemos leerla, podríamos usarla para autenticarnos sin necesidad de contraseña.
Probamos este curl con el usuario luisillo:
curl -s "http://172.17.0.2/index.php?secret=../../../../home/luisillo/.ssh/id_rsa"
Esto busca leer el archivo id_rsa (clave privada SSH por defecto) en el home de luisillo. La respuesta fue vacía por lo que probamos ahora con el otro usuario vaxei :
curl -s "http://172.17.0.2/index.php?secret=../../../../home/vaxei/.ssh/id_rsa"
y efectivamente nos devuelve la clave privada Openssh

Nos llevamos toda esa clave a un fichero nano (copiando y pegando literal incluyendo el BEGIN y el END) en consola y lo llamamos clave_vaxei .
Ajustamos los permisos del archivo a chmod 600 porque sino SSH rechazará usar una clave que pueda ser leída por otros.

Ahora aplicamos este comando y ya entramos dentro:
ssh -i clave_vaxei vaxei@172.17.0.2
Al correr este comando, no se nos pedirá contraseña (estamos usando autenticación por clave). Debemos asegurarnos de aceptar la huella digital de la máquina la primera vez. Si todo salió bien, obtendremos una sesión SSH interactiva como el usuario vaxei:

Como se trata de una shell básica no es necesario hacer un tratamiento de la tty (normalmente lo hacemos cuando obtenemos la shell por RCE).
MOVIMIENTO LATERAL (USERS PIVOTING)
Si pasamos a ver los privilegios del usuario vaxei y ejecutamos sudo -l, vemos que vaxei puede ejecutar /usr/bin/perl como el usuario luisillo, sin necesidad de contraseña.
Es decir, hay una regla de sudoers que nos permite cambiar al usuario luisillo usando Perl (sudo mal configurado).

Explicación: La sintaxis
(luisillo) NOPASSWD: /usr/bin/perlindica que usandosudo -u luisillo /usr/bin/perl ...podemos correr cualquier comando de Perl como si fuéramos luisillo. Dado que Perl es un lenguaje de programación potente que puede invocar shells, esto es prácticamente una puerta para cambiar de usuario.
Ahora conociendo esa información de escalada de privilegios, vamos a aprovechar la regla sudo para obtener una shell como luisillo.
ESCALADA DE PRIVILEGIOS
Sabiendo que el usuario vaxei tiene permisos para usar perl como luisillo, vamos a hacer pivoting de usuarios para ver si luisillo es en realidad el usuario de entrada, ya que en el usuario de vaxei no encontramos ningún archivo interesante cuando hice enumeración como archivo SUID ( find / -perm -4000 -type f 2>/dev/null ).
Si vamos a GTFObins que es una base de datos que muestra cómo abusar de binarios comunes para escalar privilegios, vemos que Perl puede ejecutar comandos de sistema como sudo: https://gtfobins.github.io/gtfobins/perl/#sudo


Por lo que vamos a ejecutar ese comando para cambiarnos al usuario luisillo y ejecutar como sudo el código Perl con /bin/bash, lo que nos da una shell como el usuario luisillo.
sudo -u luisillo /usr/bin/perl -e 'exec "/bin/bash";'

Ahora somos el usuario luisillo el cual puede ejecutar el script /opt/paw.py usando python3 como root (ALL) y sin necesidad de contraseña.
Si ejecutamos el script:
sudo python3 /opt/paw.py

Nos da un error al final de la respuesta ya que el script intenta ejecutar este código:
subprocess.run(['echo Hello!'], check=True)
El comando 'echo Hello!' fue pasado como una sola cadena en una lista, y subprocess.run() intenta ejecutar un binario llamado literalmente "echo Hello!", lo cual no existe.
Por eso el error final es:
FileNotFoundError: [Errno 2] No such file or directory: 'echo Hello!'
Esto rompe la ejecución del script, pero ocurre después de que Python ya haya importado todos los módulos, incluyendo:
import subprocess
En este punto, y aprovechando este fallo de programación, podemos hacer un ataque «Import Hijacking».
En Python, cuando importas un módulo, el intérprete busca primero en el directorio actual (y luego en PYTHONPATH, y finalmente en los módulos estándar).
Esto significa que si logramos colocar un archivo subprocess.py malicioso en el directorio que Python revisará primero, el script lo importará en lugar del módulo legítimo. Dado que vamos a ejecutar /opt/paw.py, es probable que Python considere /opt en su ruta de búsqueda de módulos. Para asegurarnos, colocaremos nuestro archivo malicioso directamente en /opt junto a paw.py.
La idea es que, al ejecutar paw.py con sudo, se importe nuestro código malicioso y nos dé acceso root antes de que el script haga cualquier otra cosa. Como payload simple, haremos que el import malicioso abra una shell root para nosotros.

Para ello vamos a crear un script en Python malicioso (ej. subprocess.py) en la cuenta luisillo con el siguiente contenido:
echo "import os; os.system('/bin/bash')" > subprocess.py

Cometí primero el error de aplicar el comando bajo la ruta /home/vaxei , que como el usuario luisillo no tiene permisos de escritura en ese directorio no lo ejecuta,
Por eso tenemos que irnos por ejemplo al directorio /home/luisillo donde luisillo tenga permisos de escritura. Ahora en /home/luisillo sí me deja ejecutarlo.
Este pequeño script (subprocess.py) al ser importado, importará os y luego ejecutará os.system('/bin/bash'). Como lo importaremos en contexto de root (via sudo), el /bin/bash que abra será un shell con privilegios root en nuestra sesión.
Aseguramos que se ha creado correctamente el archivo subprocess.py en el directorio actual (que es probablemente /home/luisillo). Ahora, lo movemos a /opt/ para que esté en el mismo directorio que paw.py .

Este paso es clave ya que al colocar nuestro archivo malicioso en /opt, al ejecutar el script, Python lo encontrará allí antes que el módulo estándar.
Esto explota la confianza de Python en cargar primero módulos locales. Es una técnica llamada Python Import Hijacking o DLL hijacking en contextos de Windows. Siempre que un programa con mayores privilegios importa algo potencialmente desde ubicaciones no seguras, abre la puerta a que un atacante inserte código malicioso camuflado con el mismo nombre del módulo.
Ahora ejecutamos el script como root. Python iniciará la ejecución de paw.py, verá import subprocess al comienzo, buscará en /opt (donde está el script) y encontrará nuestro subprocess.py antes que el módulo real. Lo importará y dentro de ese import se ejecutará os.system('/bin/bash'). Dado que el proceso corre con privilegios root, ese os.system nos va a dar una shell root.

Y efectivamente ya somos root en la shell.
Hemos logrado la escalada de privilegio final utilizando import hijacking. En esta máquina Psycho, la mala configuración fue permitir a un usuario de bajo privilegio ejecutar un script Python como root, combinado con un bug en el código (mala gestión de subprocess.run) que abrió la oportunidad de insertar nuestro código.
Eliminamos la carpeta de la máquina y todos sus elementos internos que alojamos durante su resolución mediante el comando rm -rf que borrará recursiva y forzadamente sin pedir confirmación:

RESUMEN DE LA MÁQUINA PSYCHO
| Primero hicimos un escaneo de puertos y vimos que solo estaban abiertos el 22 (SSH) y el 80 (web). Al visitar la web 80, notamos un comportamiento extraño y descubrimos una vulnerabilidad llamada LFI (Local File Inclusion), que nos permitió leer archivos del sistema. Gracias a eso, pudimos encontrar la clave privada del usuario vaxei y conectarnos por SSH sin necesidad de contraseña. Una vez dentro, vimos que vaxei podía ejecutar comandos como el usuario luisillo, lo que usamos para cambiar de usuario con un pequeño script en Perl. Luego, descubrimos que luisillo podía ejecutar un script Python como root sin contraseña. Este script estaba mal programado, así que aprovechamos ese fallo para insertar un archivo llamado subprocess.py con un comando que abría una shell como root. Al ejecutar el script, conseguimos acceso total a la máquina. |
