
إثبات مفهوم تفاضلي لـ CVE-2026-40176، يوضح حقن أوامر نظام التشغيل في برنامج تشغيل Perforce الخاص بـ Composer عبر عنوان URL ضار لمستودع، مع اختبار A/B آلي ضد الإصدارات المتأثرة والثابتة.
مثال عملي ذاتي الاحتواء وبأسلوب البرمجة الكائنية (OOP) بلغة PHP يوضح ويتحقق تفاضليًا من ثغرة حقن الأوامر في برنامج تشغيل مستودع Perforce الخاص بـ Composer.
يقوم إثبات المفهوم (PoC) بتشغيل نفس composer.json الخبيث ضد إصدارين من Composer — إصدار متأثر (2.9.5) وإصدار مُصحح (2.9.6) — ويثبت وجود الثغرة من خلال ملاحظة أثر جانبي (ملف علامة مكتوب بواسطة أمر شل محقون) يحدث في الإصدار المتأثر ولكن ليس في الإصدار المُصحح.
⚠️ لأغراض البحث الأمني المصرح به والاختبار الدفاعي فقط. راجع الاستخدام المسؤول.
يمكن لـ Composer حل الحزم من عدة أنظمة للتحكم في الإصدارات. بالنسبة لـ Perforce، يتم تحديد المستودع بواسطة رابط p4:// يشفر المضيف والمنفذ والمستخدم/التيار. عندما يبني برنامج تشغيل Perforce الخاص بـ Composer سطر أوامر p4 الأساسي، لا يتم تنظيف الحقول المأخوذة من عنوان URL الذي يتحكم به المهاجم بشكل كافٍ قبل تمريرها إلى شل.
نظرًا لأن مؤلف المانيفست يتحكم بشكل كامل في عنوان URL للمستودع، فإن المهاجم الذي يمكنه جعل الضحية تشغيل composer update/composer install ضد ملف composer.json خبيث (على سبيل المثال، تبعية مسمومة، مستودع معادٍ، أو وظيفة CI تعالج ملفات مشروع غير موثوقة) يمكنه الخروج من استدعاء p4 المقصود وتنفيذ أوامر نظام تشغيل عشوائية بصلاحيات عملية Composer.
ينتمي هذا إلى نفس عائلة مشكلات حقن الوسائط في برامج تشغيل VCS التاريخية لـ Composer، حيث تتدفق قيم URL/الفرع/التيار إلى أوامر الشل دون هروب. يعمل Composer 2.9.6 على تقوية برنامج تشغيل Perforce بحيث لا يتم تنفيذ الحمولة المحقونة بعد الآن.
الوصف الموثوق للسلوك الموضح هنا هو مصدر إثبات المفهوم نفسه (
CVE202640176Test.php)؛ راجع الإعلان الرسمي وسجل تغييرات Composer للحصول على تفاصيل الإصلاح من المنبع.
إثبات المفهوم هو فئة واحدة، CVE202640176Test، تقوم بتجربة A/B (تفاضلية) محكومة:
--version على كل من إصداري Composer المتأثر (2.9.5) والمُصحح (2.9.6) ويتوقف مبكرًا إذا تعذر استدعاء أي منهما.composer.json الذي يحتوي قسم repositories على إدخال perforce برابط p4:// خبيث يحمل حمولة شل محقونة.composer update في ذلك الدليل.finally تستعيد دائمًا ملف composer.json الأصلي في دليل المشروع.PASS فقط عندما يُظهر التشغيل المتأثر الأثر الجانبي ولا يظهره التشغيل المُصحح.لكل تشغيل، تتحقق validateRun() من ثلاثة أشياء:
| الفحص | ما يثبته |
|---|
يكون التشغيل "موافقًا" فقط عندما تنجح جميع الشروط الثلاثة. ينجح الاختبار الشامل عندما يكون التشغيل المتأثر موافقًا والتشغيل المُصحح غير موافق — وهي البصمة الدقيقة لثغرة حقيقية تم إصلاحها لاحقًا.
يتم بناء رابط المستودع الخبيث في writeComposerJson():
p4://127.0.0.1:1666:attacker_user;touch <marker> && echo '<runId>' > <marker>:client_test
تقسيمها:
p4://127.0.0.1:1666:attacker_user — رابط Perforce يبدو جيدًا (مضيف، منفذ 1666، مستخدم).;touch <marker> && echo '<runId>' > <marker> — أوامر الشل المحقونة. الـ ; في البداية تُنهي أمر p4 المقصود؛ touch تنشئ ملف العلامة، و echo '<runId>' > <marker> يكتب معرف التشغيل الفريد فيه حتى يتمكن إثبات المفهوم من تأكيد أن الحمولة (وليس عملية غير ذات صلة) أنتجت الملف.:client_test — نص تذييل للحفاظ على معقولية باقي تحليل الرابط.في برنامج التشغيل المتأثر، يتم احترام أحرف الشل الخاصة ويتم إنشاء ملف العلامة. في برنامج التشغيل المُصحح، يتم هروب القيمة/اقتباسها بشكل صحيح، لذلك يتم التعامل مع نفس السلسلة كبيانات خاملة ولا يظهر أي علامة.
ملاحظة: يستخدم إثبات المفهوم معرف تشغيل فريدًا ومختومًا بزمن ويكتب علامته داخل دليل مؤقت معزول، لذا فإن الحمولة غير ضارة وتنظف نفسها بنفسها بدلاً من أن تكون مدمرة.
2.9.5 (متأثر)2.9.6 (مُصحح)exec() بتشغيل cd … && php …). مصمم لـ Linux/macOS.composer.json أساسي في دليل المشروع (يُقرأ عند بدء التشغيل، ويُنسخ إلى كل تشغيل مؤقت، ويُستعاد بعد ذلك).لا تحتاج عمومًا إلى خادم Perforce مباشر: الثغرة تكمن في كيفية بناء Composer لسطر أوامر
p4، وتعمل الحمولة المحقونة قبل/حول أي اتصالp4حقيقي. قد يسجل Composer خطأ اتصال Perforce — هذا متوقع ولا يؤثر على إثبات ملف العلامة.
استنساخ / وضع إثبات المفهوم في دليل عمل.
توفير composer.json في نفس الدليل الذي يوجد به إثبات المفهوم. ملف صغير يكفي:
{
"name": "research/cve-2026-40176-poc",
"description": "Base manifest for the CVE-2026-40176 differential PoC",
"require": {}
}
الحصول على ثنائيي Composer ووضعهما حيث يتوقعهما إثبات المفهوم (القيم الافتراضية موضحة):
/usr/local/bin/composer-2.9.5.phar # متأثر
/usr/local/bin/composer-2.9.6.phar # مُصحح
يمكنك تنزيل إصدارات Composer محددة من الأرشيف الرسمي، على سبيل المثال:
curl -Lo /usr/local/bin/composer-2.9.5.phar https://getcomposer.org/download/2.9.5/composer.phar
curl -Lo /usr/local/bin/composer-2.9.6.phar https://getcomposer.org/download/2.9.6/composer.phar
إذا كانت مساراتك مختلفة، قم بتحرير وسيطتي المُنشئ في أسفل
CVE202640176Test.php.
php CVE202640176Test.php
تقوم الأداة المساعدة بتشغيل كلا إصداري Composer بدوره وتطبع الحكم النهائي. يتم استعادة composer.json الأصلي تلقائيًا حتى لو فشل التشغيل (يتم العمل في أدلة مؤقتة مؤقتة).
يحتوي المستودع على مختبر محوسب ينسخ البيئة تمامًا: بيئة تشغيل PHP CLI بالإضافة إلى إصداري Composer المثبتين في المسارات التي يتوقعها إثبات المفهوم، مع عزل شبكي كامل في وقت التشغيل.
docker compose run --rm poc
يبني هذا cve-2026-40176-lab:latest (تنزيل Composer 2.9.5 و 2.9.6 والتحقق من كل --version أثناء البناء) ويقوم بتشغيل الاختبار التفاضلي داخل حاوية غير مميزة وخالية من الاتصال الصادر.
ما يضمنه المختبر:
poc على شبكة جسر internal (بدون اتصال صادر بالمضيف/الإنترنت)، مع cap_drop: ALL و no-new-privileges. تبقى حمولة الحقن محصورة.يمكن إعادة تثبيت الإصدارات (يجب أن تظل متزامنة مع مساري المُنشئ في إثبات المفهوم) عبر وسائط البناء:
docker compose build --build-arg COMPOSER_AFFECTED_VERSION=2.9.5 --build-arg COMPOSER_FIXED_VERSION=2.9.6
اختياري — خادم Perforce مباشر. تتوفر خدمة p4d تحت ملف full-lab (docker compose --profile full-lab up). إثبات العلامة لا يحتاج إليها؛ إنها موجودة للباحثين الذين يريدون نقطة نهاية p4:// مباشرة. لاحظ أن حمولة إثبات المفهوم تستهدف 127.0.0.1:1666، لذا فإن التوجيه عبر حاوية p4d منفصلة يتطلب توجيه رابط PoC إلى المضيف p4d.
نتيجة صادقة: ضد إصدارات Composer الحقيقية والمنشورة
2.9.5و2.9.6، لا يعمل إثبات المفهوم حاليًا، ويبلغ المختبرINCONCLUSIVE / FAIL.
عند تشغيل Composer المتأثر (2.9.5) ضد المانيفست الخبيث لإثبات المفهوم، يتم طرح خطأ داخل Composer قبل بناء أي أمر p4/شل:
In PerforceDriver.php line 40:
[ErrorException]
Undefined array key "depot"
`
يقرأ `PerforceDriver::initialize()` أول شيء `$this->repoConfig['depot']`، لكن إدخال المستودع في إثبات المفهوم يوفر فقط `type` و `url` (بدون مفتاح `depot`). يتوقف برنامج التشغيل عند هذه النقطة، لذلك لا يتم الوصول إلى حمولة `;touch <marker>` المحقونة في الرابط ولا يتم إنشاء أي علامة. العزل الشبكي **ليس** السبب — يحدث نفس الخطأ مع اتصال صادر كامل.
**ما يعنيه هذا:**
- المختبر Docker نفسه صحيح ويقوم بتشغيل الأداة المساعدة التفاضلية بأمانة ضد الثنائيات الحقيقية المتأثرة/المصححة. نتيجة `INCONCLUSIVE` هي سمة من سمات **حمولة إثبات المفهوم**، وليس البيئة.
- لممارسة مسار بناء أوامر Perforce الفعلي، ستحتاج تكوين مستودع إثبات المفهوم على الأقل إلى مفتاح `depot` (وبشكل واقعي نقطة نهاية `p4d` مباشرة تحت ملف `full-lab`). تحسين الحمولة إلى تلك النقطة هو تطوير استغلال يتجاوز "إعداد المختبر" وتم تركه عمدًا خارج النطاق هنا.
"المخرجات المتوقعة" أدناه هي النتيجة **المقصودة/المثالية** لإثبات المفهوم، محتفظ بها كمرجع؛ إنها ليست ما تنتجه الحمولة الحالية ضد برنامج التشغيل الحقيقي.
---
## المخرجات المتوقعة
يبدو العرض التوضيحي الناجح تقريبًا هكذا (ستختلف المسارات والمعرفات):
```text
=== CVE-2026-40176 PoC started ===
- Composer 2.9.5 version: 2.9.5
- Composer 2.9.6 version: 2.9.6
Prepared temp dir: /tmp/cve20264176_5_20260610_142233
Written malicious composer.json to /tmp/cve20264176_5_20260610_142233
Running Composer in /tmp/cve20264176_5_20260610_142233…
- Parsed Composer version: 2.9.5
- Marker /tmp/cve20264176_5_.../poc_marker_5.txt created with expected ID.
- Output shows Perforce driver activity.
- Affected run exit code: 1
Prepared temp dir: /tmp/cve20264176_6_20260610_142233
Written malicious composer.json to /tmp/cve20264176_6_20260610_142233
Running Composer in /tmp/cve20264176_6_20260610_142233…
- Parsed Composer version: 2.9.6
✘ Marker file /tmp/cve20264176_6_.../poc_marker_6.txt not found.
- Output shows Perforce driver activity.
- Fixed run exit code: 1
=== CVE-2026-40176 PoC finished ===
=== TEST RESULT: PASS (affected succeeded, fixed failed) ===
رمز الخروج غير الصفري لـ Composer طبيعي — يفشل composer update في النهاية في جلب الحزمة (الوهمية). الإثبات هو ملف العلامة، وليس حالة خروج Composer.
| النتيجة | المعنى |
|---|---|
| PASS (متأثر نجح، مُصحح فشل) | تم التأكيد: 2.9.5 نفذ الأمر المحقون، 2.9.6 لم ينفذه. تم استنساخ الثغرة وإصلاحها كلاهما. |
| INCONCLUSIVE / FAIL | فشل واحد أو أكثر من الفحوصات في التوافق. افحص سطور ✓/✗ لكل تشغيل: مسار ثنائي خاطئ، عدم تطابق الإصدار، فقدان العلامة المتأثرة (اختلاف في البيئة/الهروب)، أو إنشاء العلامة بشكل غير متوقع في التشغيل المُصحح. |
الأسباب الشائعة لنتيجة غير حاسمة:
PerforceDriver.php:40 مع Undefined array key "depot" — يفتقر تكوين المستودع إلى مفتاح depot، لذلك لا يصل Composer أبدًا إلى مسار بناء أوامر p4. هذا ما يحدث مع الحمولة الحالية لإثبات المفهوم ضد 2.9.5/2.9.6 الحقيقيين (راجع حالة الاستنساخ).2.9.5 / 2.9.6.exec() في PHP محصور/معطل.p4 (لم يتم الوصول إلى مسار برنامج التشغيل).CVE2026-40176/
├── CVE202640176Test.php # إثبات المفهوم: فئة CVE202640176Test + نقطة الدخول
├── composer.json # المانيفست الأساسي الذي يقرأه/يستعيده إثبات المفهوم في وقت التشغيل
├── Dockerfile # صورة المختبر: PHP CLI + Composer 2.9.5 & 2.9.6 مثبتان
├── docker-compose.yml # مشغل `poc` (+ `p4d` اختياري تحت ملف full-lab)
├── .dockerignore # يقلص سياق البناء
├── README.md # هذا الملف
└── .gitignore # يستثني حالة الوكيل/الأدوات المحلية
إثبات المفهوم بأكمله في ملف واحد:
__construct() — يخزن مساري الثنائيين، يصنع معرف تشغيل مختومًا بزمن، يلتقط لقطة من composer.json الأصلي.run() — يدير الفحص الأولي، التشغيل المتأثر، التشغيل المُصحح، الاستعادة، والحكم.prepareTempDir() — ينشئ دليل عمل معزولًا لكل تشغيل.writeComposerJson() — يبني المانيفست الخبيث برابط p4:// المحقون.runComposer() — ينفذ composer update (الوسائط مهروبة بـ escapeshellarg()) ويلتقط المخرجات + رمز الخروج.validateRun() — يتحقق من الإصدار، ملف العلامة، ونشاط برنامج تشغيل Perforce.preflightVersion() — يقرأ --version من ثنائي معين.composer.json الأصلي في كتلة finally بغض النظر عن النتيجة.escapeshellarg() (حتى لا يحقن إثبات المفهوم عن طريق الخطأ في استدعاءات exec() الخاصة به). الثغرة تعيش طبقة أعمق — في كيفية بناء Composer لأمر p4 — وهو ما تستهدفه الحمولة بالضبط.touch/echo في علامة مؤقتة فريدة، مما يجعل إثبات المفهوم آمنًا للتشغيل المتكرر دون آثار جانبية على المضيف.exec("cd … && php …") وحمولة ;/&& شلًا شبيهًا بـ Unix؛ Windows غير مدعوم كما هو.mkdir / صلاحيات. يتم إنشاء الدلائل المؤقتة بالوضع 0777؛ قم بتضييقه إذا كنت تعمل في بيئة مشتركة.Composer X.Y.Z من المخرجات؛ قد تؤدي لافتات Composer غير العادية إلى إفشال المطابقة.يوجد هذا المستودع لـ فهم CVE-2026-40176 والدفاع ضده.
composer install/update على ملفات composer.json غير موثوقة (على سبيل المثال، في خطوط CI التي تعالج مصادر مشاريع طرف ثالث) دون عزل.2.9.5 / 2.9.6)2.9.6 (راجع متتبع الأمان لتوزيعة الخاص / قاعدة بيانات استشارات GitHub للحصول على تفاصيل الإصلاح الموثوقة)المؤلف: Ikarolaborda · إثبات المفهوم بتاريخ 2026-06-10.
| CVE | CVE-2026-40176 |
| المكون | Composer — برنامج تشغيل مستودع/VCS لـ Perforce (perforce) |
| الفئة | حقن أوامر نظام التشغيل عبر عنوان URL لمستودع يتحكم به المهاجم |
| سطح الهجوم | ملف composer.json يحتوي على إدخال repositories مُعدّل من النوع type: perforce |
| المتأثر | Composer 2.9.5 |
| المُصحح | Composer 2.9.6 |
| التفعيل | حل/تحديث التبعيات (composer update) ضد المانيفست الخبيث |
| التأثير | تنفيذ أوامر عشوائية على الجهاز الذي يعمل عليه Composer |
| لغة إثبات المفهوم | PHP (ملف واحد، بدون تبعيات خارجية) |
| وجود ملف العلامة واحتوائه على معرف التشغيل | أن حمولة touch/echo المحقونة قد نُفذت بالفعل — أي نجاح حقن الأمر. |
ذكر مخرجات Composer لـ p4 | أن مسار كود برنامج تشغيل Perforce قد تم الوصول إليه (تمت معالجة الحمولة بواسطة المكون الصحيح، وليس خطوة غير ذات صلة). |
| إصدار Composer المُحلل == المتوقع | أن الثنائي الصحيح (2.9.5 مقابل 2.9.6) هو الذي تم تشغيله. |