Ghostlink
Executive summary — Ghostlink (HTB Business CTF 2026, "Ghost Protocol Zero" theme) is a domain controller with a very unusual attack surface: an anonymous MQTT broker orchestrating health checks for internal infrastructure. The full chain strings together half a dozen very different techniques: poisoning a retained MQTT message to coerce NTLM authentication (SSRF → NTLM), relaying that NTLM back to an internal file-share through a custom authenticated HTTP proxy, a double URL-encoded path traversal leaking a KeePass database, CVE-2025-8110 in Gogs for authenticated RCE, cracking PBKDF2 within a search space narrowed by the organization's own password policy, and finally AD CS ESC11 combined with coercing the DC's own machine account to land DCSync.
| Platform | Hack The Box |
| Operating system | Windows Server 2019 |
| Difficulty | Hard |
| Status | Retired |
| Target IP (edge) | 10.129.37.254 |
Attack map
[1883] MQTT broker (EMQX) — anonymous subscribe/publish
│ poison a retained healthcheck message → NTLM coercion of SVC_CANARY
▼
[RELAY] ntlmrelayx HTTP (persistent authenticated proxy) → internal file-share
│ DOUBLE URL-encoded path traversal → db.zip (KeePass) → vroth's creds
▼
[Gogs] CVE-2025-8110 (authenticated RCE, symlink+hook) → 'git' shell
│ gogs.db → cracked PBKDF2 (rockyou trimmed to policy ≥20) → nvirelli (user.txt)
▼
[PIVOT] chisel (only route to 172.16.20.0/24) → AD CS ESC11 (unencrypted ICPR/RPC)
│ DFSCoerce (DC01$) + certipy relay → DC01$'s certificate
▼
[ROOT] PKINIT → DC01$'s NT hash → DCSync → Administrator → root.txt
1. Reconnaissance
nmap -sT -p- --min-rate 3000 -T4 -Pn -oN allports.txt 10.129.37.254
nmap -sT -sCV -p80,88,135,139,389,445,1883,2179,5985 -Pn \
--script "smb-os-discovery,ldap-rootdse" 10.129.37.254
53,88,135,139,389,445,464,593,636,3268,3269,9389 → Domain Controller
80/tcp Microsoft IIS 10.0 → "Ghost Protocol Zero" (landing)
1883/tcp mqtt → MQTT broker (anonymous)
2179/tcp vmrdp → Hyper-V VMRDP
5985/tcp Microsoft HTTPAPI 2.0 → WinRM
dnsHostName: dc01.ghostlink.htb
defaultNamingContext: DC=ghostlink,DC=htb
echo "10.129.37.254 dc01.ghostlink.htb ghostlink.htb DC01" | sudo tee -a /etc/hosts
Port 80's site is just a static landing page for a fictional APT group themed around "Ghost Protocol Zero", with no real functionality. This machine's genuine vector is MQTT — a publish/subscribe messaging protocol meant for IoT/telemetry, an unusual attack surface for a Windows/AD-focused CTF.
nmap already hints that the broker allows anonymous subscription (mqtt-subscribe) and exposes client-status topics ($SYS/brokers/client_status/..., client mqttui), typical of EMQX. With no MQTT client installed locally, a small paho-mqtt script is enough to dump every topic:
import paho.mqtt.client as mqtt, sys, time
seen=set()
def on_connect(c,u,f,rc,*a): c.subscribe("#"); c.subscribe("$SYS/#")
def on_message(c,u,m):
p=m.payload.decode('utf-8','replace'); k=(m.topic,p[:80])
if k in seen: return
seen.add(k); print(f"[{m.topic}] {p[:400]}")
cl=mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="sub-%d"%int(time.time()))
cl.on_connect=on_connect; cl.on_message=on_message
cl.connect("10.129.37.254",1883,60); cl.loop_start(); time.sleep(20); cl.loop_stop()
python3 mqttsub.py | grep -v client_status
GhostProtocolZero/systems/node/domain/healthcheck url: dc01.ghostlink.htb/healthcheck ip 10.129.37.254
GhostProtocolZero/systems/node/repository/healthcheck url: gpz-op26-toolkits.ghostlink.htb ip 172.16.20.20
GhostProtocolZero/systems/node/secureshare/healthcheck url: gpz-op26-secure.ghostlink.htb ip 172.16.20.10
The topics reveal telemetry for critical-infrastructure "nodes", and with it, two internal hosts on the
172.16.20.0/24subnet:gpz-op26-secure.ghostlink.htb(172.16.20.10, an NTLM-authenticated file-share) andgpz-op26-toolkits.ghostlink.htb(172.16.20.20, a Gogs repository).
The HTB VPN doesn't route that subnet (ping 172.16.20.1 fails). However, the DC's own IIS acts as a Host-header-based reverse proxy (ARR), which reaches internal HTTP with no pivoting needed yet:
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: gpz-op26-toolkits.ghostlink.htb" http://10.129.37.254/ # 200
curl -s -i -H "Host: gpz-op26-secure.ghostlink.htb" http://10.129.37.254/ | head # 401 WWW-Authenticate: NTLM
echo "10.129.37.254 gpz-op26-secure.ghostlink.htb gpz-op26-toolkits.ghostlink.htb" | sudo tee -a /etc/hosts
gpz-op26-toolkits turns out to be Gogs — the version hash in the HTML's gogs.min.css?v=5084b4a9... matches the commit pinning Gogs 0.13.3 (vulnerable to CVE-2025-8110, but it requires authentication → not yet exploitable at this point). gpz-op26-secure responds with 401 NTLM — a real domain identity is needed to access it.
Resumen ejecutivo — Ghostlink (HTB Business CTF 2026, tema «Ghost Protocol Zero») es un controlador de dominio con una superficie de ataque muy poco habitual: un broker MQTT anónimo que orquesta los health checks de una infraestructura interna. La cadena completa encadena media docena de técnicas de naturaleza muy distinta entre sí: envenenar un mensaje MQTT retenido para coaccionar autenticación NTLM (SSRF → NTLM), relayar ese NTLM hacia un file-share interno mediante un proxy HTTP autenticado a medida, un path traversal con doble URL-encoding que filtra una base KeePass, CVE-2025-8110 en Gogs para RCE autenticada, un crackeo de PBKDF2 acotado por la propia política de contraseñas de la organización, y finalmente ESC11 de AD CS combinado con coerción de la cuenta de máquina del propio DC para acabar en DCSync.
| Plataforma | Hack The Box |
| Sistema operativo | Windows Server 2019 |
| Dificultad | Hard |
| Estado | Retired |
| IP objetivo (edge) | 10.129.37.254 |
Mapa del ataque
[1883] Broker MQTT (EMQX) — suscripción/publicación anónima
│ envenenar mensaje retenido de healthcheck → coerción NTLM de SVC_CANARY
▼
[RELAY] ntlmrelayx HTTP (proxy autenticado persistente) → file-share interno
│ path traversal con DOBLE URL-encoding → db.zip (KeePass) → creds de vroth
▼
[Gogs] CVE-2025-8110 (RCE autenticada, symlink+hook) → shell 'git'
│ gogs.db → PBKDF2 crackeado (rockyou recortado por política ≥20) → nvirelli (user.txt)
▼
[PIVOTE] chisel (única vía hacia 172.16.20.0/24) → AD CS ESC11 (ICPR/RPC sin cifrado)
│ DFSCoerce (DC01$) + certipy relay → certificado de DC01$
▼
[ROOT] PKINIT → NT hash de DC01$ → DCSync → Administrator → root.txt
1. Reconocimiento
nmap -sT -p- --min-rate 3000 -T4 -Pn -oN allports.txt 10.129.37.254
nmap -sT -sCV -p80,88,135,139,389,445,1883,2179,5985 -Pn \
--script "smb-os-discovery,ldap-rootdse" 10.129.37.254
53,88,135,139,389,445,464,593,636,3268,3269,9389 → Controlador de Dominio
80/tcp Microsoft IIS 10.0 → "Ghost Protocol Zero" (landing)
1883/tcp mqtt → broker MQTT (anónimo)
2179/tcp vmrdp → Hyper-V VMRDP
5985/tcp Microsoft HTTPAPI 2.0 → WinRM
dnsHostName: dc01.ghostlink.htb
defaultNamingContext: DC=ghostlink,DC=htb
echo "10.129.37.254 dc01.ghostlink.htb ghostlink.htb DC01" | sudo tee -a /etc/hosts
La web del puerto 80 es solo una landing estática de un grupo APT ficticio ambientado en «Ghost Protocol Zero», sin ninguna funcionalidad real. El vector genuino de esta máquina es el MQTT — un protocolo de mensajería publish/subscribe pensado para IoT/telemetría, poco habitual como superficie de ataque en un CTF de Windows/AD.
nmap ya adelanta que el broker admite suscripción anónima (mqtt-subscribe) y expone topics de estado de cliente ($SYS/brokers/client_status/..., cliente mqttui), típico de EMQX. Sin cliente MQTT instalado localmente, un pequeño script con paho-mqtt basta para volcar todos los topics:
import paho.mqtt.client as mqtt, sys, time
seen=set()
def on_connect(c,u,f,rc,*a): c.subscribe("#"); c.subscribe("$SYS/#")
def on_message(c,u,m):
p=m.payload.decode('utf-8','replace'); k=(m.topic,p[:80])
if k in seen: return
seen.add(k); print(f"[{m.topic}] {p[:400]}")
cl=mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="sub-%d"%int(time.time()))
cl.on_connect=on_connect; cl.on_message=on_message
cl.connect("10.129.37.254",1883,60); cl.loop_start(); time.sleep(20); cl.loop_stop()
python3 mqttsub.py | grep -v client_status
GhostProtocolZero/systems/node/domain/healthcheck url: dc01.ghostlink.htb/healthcheck ip 10.129.37.254
GhostProtocolZero/systems/node/repository/healthcheck url: gpz-op26-toolkits.ghostlink.htb ip 172.16.20.20
GhostProtocolZero/systems/node/secureshare/healthcheck url: gpz-op26-secure.ghostlink.htb ip 172.16.20.10
Los topics revelan telemetría de "nodos" de infraestructura crítica y, con ella, dos hosts internos en la subred
172.16.20.0/24:gpz-op26-secure.ghostlink.htb(172.16.20.10, un file-share autenticado por NTLM) ygpz-op26-toolkits.ghostlink.htb(172.16.20.20, un repositorio Gogs).
La VPN de HTB no enruta esa subred (ping 172.16.20.1 falla). Sin embargo, el IIS del propio DC actúa de reverse-proxy (ARR) basado en la cabecera Host, lo que permite alcanzar HTTP interno sin necesidad de pivotar todavía:
curl -s -o /dev/null -w "%{http_code}\n" -H "Host: gpz-op26-toolkits.ghostlink.htb" http://10.129.37.254/ # 200
curl -s -i -H "Host: gpz-op26-secure.ghostlink.htb" http://10.129.37.254/ | head # 401 WWW-Authenticate: NTLM
echo "10.129.37.254 gpz-op26-secure.ghostlink.htb gpz-op26-toolkits.ghostlink.htb" | sudo tee -a /etc/hosts
gpz-op26-toolkits resulta ser Gogs — el hash de versión en gogs.min.css?v=5084b4a9... del HTML corresponde al commit que fija Gogs 0.13.3 (vulnerable a CVE-2025-8110, pero requiere autenticación → aún no explotable en este punto). gpz-op26-secure responde 401 NTLM — hace falta una identidad de dominio real para acceder.
This content is Root Access only. Everything else on the site — the free tier, the whole public library — stays open.
See Root Access plansNo account? Create one free then upgrade from your console.
Este contenido es solo para Root Access. Todo lo demás del sitio — el nivel gratuito, toda la biblioteca pública — sigue abierto.
Ver planes de Root Access¿Sin cuenta? Crea una gratis y luego mejora desde tu consola.