
إثبات مفهوم لاستغلال CVE-2026-44590، وهي حقن أوامر في سير عمل GitHub Actions الخاص بـ Sherlock مما يتيح تنفيذ الأوامر عن بُعد (RCE) وسرقة GITHUB_TOKEN عبر pull_request_target.
تم الاكتشاف والإبلاغ بواسطة: Astaruf
الشرح الكامل: https://nstsec.com/en/posts/sherlock-rce-pull-request-target-cve-2026-44590/
الإشعار الرسمي: إشعار GHSA الخاص بـ sherlock-project/sherlock
إدخال NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-44590
سجل CVE: https://www.cve.org/CVERecord?id=CVE-2026-44590
يحتوي هذا المستودع على إثبات المفهوم لـ CVE-2026-44590، وهو حقن أوامر في سير عمل GitHub Actions الخاص بـ validate_modified_targets.yml في sherlock-project/sherlock. يمكن لأي مستخدم GitHub فتح طلب سحب يؤدي إلى تنفيذ أوامر عشوائية في سياق CI المميز، وسرقة GITHUB_TOKEN الخاص بسير العمل، والموافقة التلقائية على طلب السحب الخبيث، كل ذلك دون أي تفاعل بشري.
للحصول على الشرح الفني الكامل (تحليل السبب الجذري، شرح خطوات الاستغلال، مناقشة التأثير، وفصل حول ما يمكن للمهاجم فعله في سيناريوهات العالم الحقيقي) راجع منشور المدونة:
nstsec.com/en/posts/sherlock-rce-pull-request-target-cve-2026-44590
يركز هذا README حصريًا على سكربت PoC: ما الذي يفعله، وكيفية تشغيله، وما يمكن توقعه.
poc.pypoc.py هو سكربت Python واحد مكتفٍ بذاته (مكتبة قياسية فقط) يقوم بأتمتة سلسلة الهجوم بالكامل من البداية إلى النهاية:
sherlock-project/sherlock إذا لزم الأمرmaster الخاص بالـ fork إلى الالتزام قبل الإصلاح بحيث يمكن إعادة إنتاج الثغرة حتى بعد الإصلاح الرسميinteractsh-client)GITHUB_TOKEN من استدعاء OAST (في --mode exfil) ويفك ترميزه كنص واضحVULNERABILITY CONFIRMED أو FIX VERIFIEDinteractsh-client)هناك خطوة يدوية واحدة بالضبط مطلوبة (النقر على لافتة "I understand my workflows" في GitHub في المرة الأولى لكل fork)، لأنه لا توجد واجهة برمجة تطبيقات عامة لإغلاقها. يكتشف السكربت هذه الحالة ويتوقف مؤقتًا مع مطالبة واضحة.
python3 poc.py --fork-owner <your-github-username>
يقوم بعمل fork للمستودع (إذا لزم الأمر)، ويزامن مع المستودع الرسمي (master مُصلَح)، ويفتح طلب سحب خبيث، ويشغّل سلسلة الهجوم، ويبلغ بـ FIX VERIFIED لأن سير العمل المُصلَح يحظر الحمولة قبل تنفيذ أي أمر shell.
python3 poc.py --fork-owner <your-github-username> --vulnerable
نفس ما سبق، لكن أولاً يعيد master الخاص بالـ fork إلى الالتزام قبل الإصلاح (271608fb). الحكم المتوقع: VULNERABILITY CONFIRMED.
python3 poc.py --fork-owner <your-github-username> --vulnerable --mode exfil
تقوم حمولة exfil بإرسال git config --list إلى OAST وتنام لمدة 180 ثانية لإبقاء سير العمل (وبالتالي GITHUB_TOKEN) نشطًا. بينما سير العمل نائم، يستخرج السكربت الرمز من سجل OAST، ويفك ترميزه، ويستدعي فورًا GitHub API للموافقة على طلب السحب. ينتهي طلب السحب بموافقة من github-actions[bot].
gh (GitHub CLI)، مُصادق عليه:
gh auth login
gitinteractsh-client (اختياري لكن موصى به). عند تثبيته، يقوم السكربت تلقائيًا بتشغيله والتحقق من الاستدعاء داخل السكربت:
go install github.com/projectdiscovery/interactsh/cmd/interactsh-client@latest
إذا كنت تفضل استخدام نقطة نهاية OAST الخاصة بك (Burp Collaborator، oast.fun عبر واجهة الويب، requestbin، إلخ)، مررها عبر --oast-url وسيتم تخطي خطوة التحقق التلقائي.لا يتطلب السكربت وجود fork مسبقًا، فهو ينشئ واحدًا تلقائيًا.
--mode harmless (الافتراضي)الحمولة هي طلب curl POST واحد بسلسلة تأكيد ثابتة. لا تتم قراءة أي أسرار، ولا يتم إجراء أي استدعاءات API، والتأثير الجانبي الوحيد هو استدعاء OAST. استخدم هذا لتأكيد وجود الثغرة دون كشف أي بيانات اعتماد.
--mode exfilتقوم الحمولة بإرسال git config --list (الذي يحتوي على GITHUB_TOKEN بترميز base64 تحت http.https://github.com/.extraheader) إلى OAST، ثم تنام لمدة 180 ثانية. يقوم السكربت بعد ذلك بما يلي:
x-access-token:ghs_XXXXXXXX....x-access-token: ويستخدم الرمز الخام لاستدعاء POST /repos/<fork>/pulls/<n>/reviews مع حمولة الموافقة القياسية ({"event":"APPROVE","body":"All checks passed. LGTM!"}).github-actions[bot]، لا يمكن تمييزه عن أتمتة CI المشروعة.بمجرد تسجيل الموافقة، يتخطى السكربت باقي فترة النوم البالغة 180 ثانية لسير العمل، لأن سلسلة الهجوم مكتملة والانتظار حتى انتهاء مهلة العامل لا يضيف شيئًا.
| العلم | الوصف |
|---|---|
--fork-owner <user> | مطلوب. اسم مستخدم GitHub الذي يملك (أو سيملك) الـ fork |
--fork-name <name> | اسم مستودع الـ fork (الافتراضي: sherlock) |
--oast-url <url> | نقطة نهاية OAST التي تستقبل الاستدعاء. إذا تم حذفها، يقوم السكربت تلقائيًا بتشغيل interactsh-client ويشغّل الحكم داخل السكربت |
--mode harmless|exfil | نوع الحمولة (الافتراضي: harmless) |
--vulnerable | إعادة تعيين master الخاص بالـ fork قسريًا إلى الالتزام قبل الإصلاح (271608fb) قبل التشغيل. يستلزم --no-sync |
--no-sync | تخطي مزامنة الـ fork مع المستودع الرسمي (مفيد عند اختبار التزام محدد) |
--base-branch <name> | فرع طلب السحب المستهدف في الـ fork (الافتراضي: master) |
--keep-branch | عدم حذف فرع PoC بعد الاكتمال |
--no-poll | تخطي استقصاء تشغيل سير العمل والخروج بعد إنشاء طلب السحب |
حسب التصميم، يفتح PoC طلب السحب من فرع في الـ fork إلى master لنفس الـ fork. لا يستهدف sherlock-project/sherlock مباشرة. هناك سببان لذلك.
طلب السحب على مستودع عام مرئي لأي شخص. يبقى الفرق مفهرسًا حتى بعد إغلاق طلب السحب، وسجلات GitHub Actions قابلة للوصول عبر واجهة الويب. فتح طلب سحب بحمولة حقن أوامر عاملة على المستودع الرسمي سينشر فعليًا استغلالًا وظيفيًا قبل أن تتاح للمشرفين فرصة إصدار إصلاح. يمكن لأي شخص يراقب المستودع نسخ الحمولة، واستبدال استدعاء OAST بنقطة نهاية خبيثة، واستخدامها لسرقة GITHUB_TOKEN الحقيقي.
يستخدم PoC طلب سحب من fork إلى fork لإبقاء الاستغلال بعيدًا عن الأنظار العامة مع إظهار الثغرة من البداية إلى النهاية.
عند عمل fork لمستودع sherlock-project/sherlock، يتم تضمين ملف سير العمل validate_modified_targets.yml في الـ fork. فتح طلب سحب يستهدف master الخاص بالـ fork يشغّل سير العمل في سياق الـ fork، مع GITHUB_TOKEN صادر للـ fork. الآليات مطابقة تمامًا للهجوم الأصلي:
pull_request_target تلقائيًاGITHUB_TOKEN الخاص بهactions/checkout الرمز في .git/config عبر إعداد http.https://github.com/.extraheader