Runners
Última actualización
Última actualización
¡Hola hacker! Bienvenido a una nueva resolución. En esta ocasión, voy a resolver una máquina que creé para nuestra comunidad de The Hackers Labs, la máquina Runners. Esta máquina marca mi primer proyecto como creador de CTFs. ¡Espero que la disfrutes y te ofrezca un buen reto!
Comenzamos como siempre lanzando una traza ICMP a la máquina objetivo para comprobar que tengamos conectividad.
Vemos que responde al envío de nuestro paquete, verificando de esta manera que tenemos conectividad. Por otra parte, confirmamos que estamos frente a una máquina Linux basandonos en el TTL (Time To Live).
Realizamos un escaneo con nmap
para descubrir que puertos TCP se encuentran abiertos en la máquina víctima.
Lanzamos una serie de script básicos de enumeración propios de nmap
, para conocer la versión y servicio que esta corriendo bajo los puertos.
Ingresamos a la web que esta corriendo bajo el puerto 80 y nos encontramos con un blog que habla sobre lo que es el mundo del running y distintas experiencias vividas por los autores de los artículos.
Navegando por la web, nos encontramos que al ingresar a un artículo existe un parámetro en la url, que al parecer corresponde al id
del artículo.
Probamos ingresar una comilla simple '
pero vemos que no ocurre ningun error, simplemente no encuentra artículos.
Probemos si este parámetro es vulnerable a inyección sql, para lo cual, intentemos descubrir cuantas columnas esta devolviendo la consulta que esta por detrás.
Probamos con 2, no pasa nada.
Probamos con 4 y vemos que no devuelve resultados.
Probamos con 3 y vemos que nos devuelve el artículo, por lo tanto sabemos que la consulta esta devolviendo un total de 3 columnas.
Utilicemos una clausula UNION SELECT
para ver que nos devuelve. Necesitamos ingresar un valor de id
que no exista, por ejemplo 100.
Observamos los números reflejados en el html.
Probemos ahora con el siguiente payload.
Genial, logramos ver el nombre de la base de datos.
NOTA:
Si te preguntas porque aparce dos veces el número 1 o el nombre de la base de datos, esto se debe a que el título se utiliza como valor del atributo alt de la imágen y como no se encuentra una url para cargar una imágen se muestra el valor de dicho atributo.
NOTA:
La cláusula
UNION SELECT
en SQL se utiliza para combinar los resultados de dos o más consultas SELECT en un solo conjunto de resultados.En pocas palabras, combina multiples consultas SELECT en una tabla de resultados única. A su vez, es importante tener en cuenta que la consulta debe devolver el mismo número de columnas y con tipos de datos compatibles en las posiciones correspondientes.
Otra forma de probar la vulnerabilidad es con una inyección basada en tiempo usando el siguiente payload.
Cómo no existe un artículo con id
100, se ejecuta el SLEEP(1)
, por lo que deberías notar que la consulta demora 1 segundo tal vez un poco más en responder confirmando la presencia de una inyección SQL.
Ahora que sabemos que el parámetro id
es vulnerable, podemos automatizar el proceso a ver que encontramos. En este caso, lo voy a realizar usando Python, pero también pudes hacerlo usando herramientas como sqlmap.
Utilizamos el siguiente script en Python para obtener las base de datos.
Expliquemos un poco como funciona el script.
En primer lugar, nos encontramos con la función def_handler
, la cual se encarga de manejar la señal SIGINT (usualmente enviado al presionar CTRL + C
para permitir una salida limpia del programa, mostrando un mensaje antes de cerrar).
Luego, se define la variable character
asignando un conjunto de caracteres que incluye dígitos, letras en minúscula y mayúscula, además de los caracteres _
y .
. Se usa para construir y validar cada carácter de los datos extraídos.
Seguidamente, definimos la variable url, con la url donde se encuentra la vulnerabilidad.
Posteriormente, definimos la función prinicipal makeSQLi
, la cual es la función principal del script.
En primera instancia, se incializan dos indicadores de progreso: - p1
: Muestra el estado general del ataque. - p2
: Muestra los datos extraídos hasta el momento.
En seguida, se crea la variable extracted_info
para almacenar el texto recuperado.
Luego de eso, comienza a definirse el proceso de extracción de información
Comenzamos definiendo un bucle for principal, para itera sobre un rango de bases de datos (controlado por LIMIT
en el payload SQL). En caso de que no sea la primera iteración, añade una coma al resultado (extracted_info
), para separar nombres de bases de datos.
Subsiguientemente, definimos un bucle for interno, encargado de la extracción carácter por carácter.
En este bucle, se intenta recuperar hasta 250 caracteres por entrada (nombre de base de datos, tablas o columna, como veremos más adelante). Dentro del bucle for más interno, se convierte cada carácter del conjunto characters
a su valor ASCII con ord()
para usarlo en el payload SQL.
Esta es la parte principal del código y donde se lleva a cabo la explotación de la inyección SQL.
(SELECT IF(...))
: Evalúa una condición:
Si la condición es verdadera (el carácter coincide), el servidor se "duerme" por 1 segundo (SLEEP(1)).
Si es falsa, responde normalmente (sin demora).
ASCII(SUBSTRING(schema_name, {position}, 1))
: Extrae el carácter en la posición actual (position
) de los nombres de bases de datos (schema_name
).
LIMIT {limit},1
: Selecciona el nombre de base de datos actual en el rango LIMIT
.
En el siguiente paso, combina la URL base con el payload y envía la petición HTTP.
En seguida de ello, se realiza el análisis de la respuesta.
Si el servidor demora más de 1 segundo en responder, significa que el carácter actual es correcto, actualiza el progreso (p2.status
) y avanza al siguiente carácter. Por el contrario, si no se encuentra un carácter válido en varias posiciones consecutivas, asume que el nombre actual ha terminado y pasa al siguiente.
Por ultimo, este bloque de código indica que cuando se ejecute el script, se llame a la función makeSQLi
.
Ahora que tenemos claro que es lo que realiza el script, lo ejecutamos.
Vemos que existe la base de datos blog
.
Para obtener las tablas de la base de datos blog
, un script similar con una pequeña modificación en el payload, donde utilizamos la tabla information_schema.tables
para obtener las tablas de la base de datos blog
.
users
Al igual que los scripts anteriores, realizamos una pequeña modificación, en este caso para obtener las columnas de la tabla users
de la base de datos blog
, para lo cual utilizamos la tabla information_schema.columns
.
Ahora que conocemos la base datos, la tabla y las columnas, realizamos una simple consulta la cual nos devuelve los usuarios y contraseñas.
Ejecutamos el script y obtenemos los hashes de los usuarios.
Crackeamos los hashes con john
y encontramos la contraseña de david
.
Nos conectamos con david
por ssh al puerto 2222.
Si miramos dentro del directorio home de david
, vemos que existe un directorio oculto .hidden
y dentro de este un archivo zip.
Descargamos el archivo a nuestra máquina atacante.
Utilizamos scp para descargar el archivo.
Al momento de descomprimir el archivo nos solicita una contraseña.
Utilizamos zip2john
para generar el hash y luego crackearlo con john
.
Genial, obtenemos la contraseña del archivo zip rockandroll
.
Descomprimimos el archivo.
Obtenemos un archivo credenciales.xlsx
Abrimos el archivo, en este caso con LibreOffice.
Dentro de este, encontramos unas credenciales, de david
la misma que ya teniamos y de maria
.
Utilizamos las credenciales de maria
para iniciar sesión en el sistema a través de ssh.
Logramos ingresar al sistema como el usuario maria
, pero aun estamos dentro del contenedor.
Si realizamos una enumeración básica del sistema, encontramos un script /opt/backup.sh
en el cual tenemos capacidad de escritura.
Si miramos el script, vemos que se esta realizando un backup de la base de datos blog
.
Podemos observar también utilizando pspy
que se esta ejecutando una tarea cron
la cual ejecuta este script y el usuario que lo esta ejecutando es root.
Subimos pspy.
Asignamos permisos de ejecución al binario .
Ejecutamos pspy.
Como somos el usuario maria
y tenemos la capacidad de escritura en el script backup, podemos agregar lo siguiente al script.
Asignado permisos suid al binario bash.
Esperamos que se ejecute el script y vemos que tenemos permisos suid en la bash.
De esta forma, logramos escalar privilegios en el contenedor.
Si miramos dentro del directorio home de root, encontramos un archivo TODO_LIST.txt
.
Dentro de esta lista de tareas se menciona el nombre de un usuario ian
y su posible contraseña iambatman
.
Probamos estas credenciales para conectarnos por ssh al host principal y efectivamente son validas.
Obtenemos el flag de user.txt
Si miramos dentro del directorio home del usuario elliot
encontramos un archivo miscredenciales.psafe3
.
Lo descargamos a nuestra máquina utilizando scp.
¿Qué es un archivo
.psafe3
?Un archivo
.psafe3
es el formato utilizado por Password Safe, una herramienta de gestión de contraseñas. Este archivo almacena de forma segura contraseñas y datos sensibles, encriptados con algoritmos como Twofish para protegerlos contra accesos no autorizados. Solo se puede acceder al contenido mediante una clave maestra definida por el usuario.
¿Qué es Password Safe (pwsafe)?
Password Safe es un software de código abierto diseñado para almacenar y gestionar contraseñas de manera segura. Ofrece características como:
Encriptación fuerte: Usa algoritmos como Twofish.
Portabilidad: Los archivos
.psafe3
pueden trasladarse fácilmente entre dispositivos.Autenticación centralizada: Se requiere una contraseña maestra para acceder a todas las credenciales.
Lo abrimos con pwsafe pero nos solicita una contraseña maestra para poder acceder.
Utilizamos pwsafe2john
y lo crackeamos con john
.
Genial! obtenemos la contraseña maestra.
Abrimos la boveda con pwsafe
.
Vemos que existen varias entradas de credenciales pero hay una particular que nos llama la atención, la entrada Blog.
Si le damos editar entrada, podemos ver que se revelan una serie de credenciales.
Usamos estas credenciales para conectarnos por ssh al sistema.
Efectivamente, las credenciales son validas.
Si realizamos una enumeración básica del sistema, podemos ver que el usuario elliot pertenece al grupo docker
por lo que podemos abusar de este.
Cremos un contenedor usando una imagen de Alpine Linux y montamos la raíz en el contenedor bajo el directorio host y cambiamos el directorio raíz.
Asignamos permisos suid a la bash.
Salimos del host y comprobamos que efectivamente se asignaron permisos suid al binario bash del host.
De esta manera, logramos escalar nuestros privilegios.
Leemos el flag de root.
De esta manera, concluimos la resolución de la máquina Runners.
Si te gustó este CTF, ¡cuentaselos a otros!.
Recuerda, los desafíos son solo una pieza del rompecabezas; sigue aprendiendo, explorando y compartiendo tu conocimiento.
¡Gracias por leer!
¡Happy Hacking!