السياق
في الآونة الأخيرة، اكتشف نظام المراقبة الأمنية MistEye معلومات تشير إلى ظهور إصدارات غير طبيعية في عدة حزم npm تابعة لمنظمة Red Hat Cloud Services. شملت هذه الحادثة إجمالي 32 حزمة npm و96 إصدارًا تابعة لهذه المنظمة. اختار هذا المقال ثلاثة عينات محلية لتحليل معمق خارج الخط: هذه العينات ليست حزمًا مزيفة أو مبنية على أخطاء إملائية، بل هي إصدارات حقيقية تستخدم نطاق @redhat-cloud-services؛ ومن خلال تحليل شفرة المصدر، تأكد أن ملفات tarball الخاصة بها تحتوي على مُحمّل ضار متعدد الطبقات تم زراعته ليُفعّل تلقائيًا أثناء مرحلة التثبيت.
بعد الاستعادة الكاملة، يمكن التأكيد أن حمولة العينات الثلاث الأساسية تمتلك قدرات في قراءة ذاكرة GitHub Actions Runner، وجمع بيانات الاعتماد من سحابات متعددة ومحلية، ونقل البيانات عبر GitHub API وآلية dead-drop، وحقن سير عمل GitHub، ونشر ذاتي عبر npm، والبقاء المستمر عبر Claude Code / VS Code / systemd / LaunchAgent، ومكافحة Harden-Runner / StepSecurity، واكتشاف EDR ومنتجات الأمان. من حيث نطاق القدرات، فإن الكيانات المتأثرة المحتملة تشمل أجهزة المطورين ومشغلي CI/CD وحاويات البناء ومستودعات GitHub وسير عمل GitHub وسلسلة نشر npm وبيانات الاعتماد في البيئات السحابية؛ ويجب تأكيد النطاق الفعلي للتأثير من خلال سجلات التثبيت ومراجعة المستودعات وبيانات التتبع من المنصة.
من حيث بنية الكود ومسارات الانتشار ومجموعات القدرات، فإن هذا البرنامج الضار هو نسخة من برنامج Shai-Hulud الضار.
استجابة MistEye
MistEye هو نظام متكامل للتهديدات الاستخباراتية والمراقبة الأمنية الديناميكية للويب3، تم تطويره ذاتيًا من قبل SlowMist، ويدمج قدرات المراقبة الأمنية وتجميع الاستخبارات لتقديم تحذيرات مخصصة في الوقت الفعلي وحماية الأصول للمستخدمين.
بعد اكتشاف حادثة تسميم سلسلة التوريد لحزمة npm الخاصة بخدمات Red Hat Cloud وعينات الخبيثة المرتبطة بها، أطلق نظام MistEye إنذارًا عالي الخطورة، وقام بتحليل منهجي لهيكل التمويه، وفك تشفير الحمل، واستعادة القدرات، ومؤشرات التهديد.
(https://enterprise.misteye.io/threat-intelligence/SM-2026-378450)
نظرة عامة على سلسلة الهجوم
يُستند قسم التحليل الفني في هذا المستند إلى تحليل محلي غير متصل بالإنترنت وثابت، ويتضمن فك حزم وفك تشفير والتحقق من تضليل 3 عينات من npm tgz.
العينات المشمولة في هذه المصادقة هي كالتالي:
@redhat-cloud-services/frontend-components-config
الإصدار: 6.11.3
tgz SHA-256: 0c9c67ec40d5f23efa1ec3470d0ac88b4993ccc0e92be913fc29a337dfc4f060
@redhat-cloud-services/types
الإصدار: 3.6.1
tgz SHA-256: d543bb3cdf1569c2b3d38c8a4081ed746cfe78bf3236c2302704d79ab7fa9558
@redhat-cloud-services/rule-components
الإصدار: 4.7.2
tgz SHA-256: aaf00d06baa3c679b82452c50014e9824b8874e9ca2d150f19095f8de19ba90f
مواقع الهجوم على العينات الثلاثة متطابقة تمامًا: جميعها تحتوي على scripts.preinstall = "node index.js" في ملف package.json. هذا يعني أنه بمجرد تثبيت هذه الإصدارات على جهاز المطور أو بيئة CI/CD، سيتم تنفيذ ملف index.js في الجذر تلقائيًا أثناء مرحلة التثبيت، دون الحاجة إلى استيراد أو تشغيل كود العمل بشكل صريح من قبل المستخدم.
تشارك العينات الثلاثة نفس حمل الخبيث الأساسي، لكنها تختلف في معلمات التغليف الخارجي. أي أن العينات الثلاثة تستخدم قيم تحويل ROT/Caesar مختلفة ومفاتيح/AES-GCM iv/tag مختلفة؛ مما يجعل السمات الثابتة القائمة على هاش التغليف الخارجي أو المفتاح الثابت أو قيمة التحويل الثابتة صعبة الاستخدام عبر العينات. لكن بيئة تشغيل Bun بعد فك التشفير وحمل الخبيث الأساسي متطابقان تمامًا في الهاش: أحدهما وحدة مساعدة للبدء تُعد بيئة تشغيل Bun، والآخر هو الحمل الأساسي الذي يحتوي على الوظائف الخبيثة الحقيقية. يُطلق على هذين العنصرين في هذه المقالة على التوالي "مُوجّه بيئة تشغيل Bun (اسم المتغير في الكود المصدر _b)" و"الحمل الخبيث الأساسي (اسم المتغير في الكود المصدر _p)".
سلسلة الهجوم المُستعادة كالتالي:

التحليل الفني
1. المدخل: اختراق سكريبتات دورة حياة npm
تم تشغيل سلسلة الهجوم عبر مُفعّل دورة الحياة preinstall في ملف package.json في جميع العينات الثلاث:
"scripts": {
"preinstall": "node index.js"
}
يُنفذ preinstall تلقائيًا أثناء مرحلة تثبيت npm. بالنسبة للعناصر الأمامية العادية أو تعريفات النوع أو حزم المكونات القواعدية، عادةً لا يلزم تشغيل ملف JavaScript كبير في الدليل الجذر أثناء مرحلة التثبيت؛ وهذه هي إشارة الاستثناء الأكثر وضوحًا.
الأدلة على مستوى العينة كما يلي:
@redhat-cloud-services/frontend-components-config@6.11.3
- رئيسي: lib/index.js
- حقل الملفات: ["/lib", "/bin"]
- جذر الملف index.js SHA-256: 545a1838c66e1771f58d84a17b3e1841e5eeab91a73f4ccc59c9492450a6d9c0
@redhat-cloud-services/types@3.6.1
- رئيسي: index.d.ts
- حقل files: غير مُعيَّن
- SHA-256 لملف index.js في الدليل الجذر: b86c5ae9e95bd841a595440faa3eb6317441e746f241ae8fd641ab59ed1d1966
@redhat-cloud-services/rule-components@4.7.2
- رئيسي: index.js
- حقل files: غير مُعيَّن
- جذر المجلد index.js SHA-256: 1a30a9abe20bab121aaa75ed040565af14e6cdfb745609ee0e7b94a2d814fb9c
في هذا السياق، يُعلن حقل files في frontend-components-config@6.11.3 عن "/lib" و"/bin" فقط، لكن ملف index.js إضافي موجود في الجذر الخاص بـ tarball ويتم استدعاؤه عبر preinstall. هذه الشذوذ ينطبق فقط على هذا العينة؛ حيث لم يتم تعيين حقل files في types@3.6.1 وrule-components@4.7.2، وبالتالي لا يمكن تعميم هذا الشذوذ على جميع العينات.
2. الطبقة الأولى: مصفوفة أرقام + استبدال حروف ROT/Caesar
جميع ملفات index.js في الجذر لثلاثة عينات هي ملفات JavaScript أحادية السطر ضخمة، وهي متشابهة في الهيكل، وتحتوي على جسم رئيسي مُحاط بـ try { eval(...) } catch (...)
يقوم هذا الـ wrapper أولاً بتحويل مصفوفة أرقام ضخمة إلى سلسلة باستخدام String.fromCharCode، ثم يطبق تحولاً ROT/Caesar على الحروف الإنجليزية في السلسلة، وأخيراً يمرر النتيجة المحللة إلى eval للتنفيذ. معالجة الاستثناءات تقتصر على إظهار البادئة "wrapper:" لتقليل كشف الاستثناءات البارزة عند فشل التثبيت.
قيم الإزاحة الخارجية و Stage 2 hash لثلاثة عينات كالتالي:
frontend-components-config@6.11.3
حجم ملف index.js: 4,294,798 بايت
ROT/Caesar shift: 10
المرحلة 2 SHA-256: b19c2fd48535c8c40aeb3e627ce92775f33ef9292611767bb1236c238e6f90cc
types@3.6.1
حجم ملف index.js: 4,135,588 بايت
ROT/Caesar shift: 4
المرحلة 2 SHA-256: 9c0425aa6e6d7792ac38d24f3e7245f42fcaa553ddfeb6bd97677017f10c3b75
rule-components@4.7.2
حجم ملف index.js: 4,294,336 بايت
ROT/Caesar shift: 11
المرحلة 2 SHA-256: d590bd375d95e4ac072b7ebc1fc4489bcaf5f20a939e92486267aa398bcf1e5d
3. الطبقة الثانية: فك تشفير Bun Bootloader والحمولة الأساسية باستخدام AES-128-GCM
يتم تنفيذ فك تشفير AES-128-GCM (المعيار المتقدم للتشفير - وضع غاليوس/المعدّل) باستخدام Node.js crypto.createDecipheriv مع إعداد علامة المصادقة عبر setAuthTag، بعد فك تشفير Stage 2 باستخدام ROT/Caesar. الهدف من الفك هو مكونان لاحقان: مُشغّل بيئة Bun وحمولة الخبيثة الأساسية.
فيما يخص، يُشرف مُشغّل بيئة Bun على اكتشاف أو تحديد أو إعداد بيئة Bun، مما يمكّن الكود اللاحق من التنفيذ عبر Bun؛ بينما يُعد الحمل الضار الأساسي هو العنصر الرئيسي في سلسلة الهجوم، ويتضمن القدرات الضارة الرئيسية التالية: جمع بيانات الاعتماد، نشر عبر GitHub/npm، الاستمرارية، مواجهة الحماية، وفك تشفير الموارد المضمنة.
استُخدمت مفاتيح AES-128-GCM ومقادير بدء وعلامات مصادقة مختلفة لكل عينة من العينات الثلاث لتجزئة هذين المكونين، لكن SHA-256 لمشغل بيئة Bun بعد فك التشفير كان متطابقًا في جميع العينات الثلاث: ac2a2208e1726e008be6c73dc0872d9bba163319259dff1b62055ac933ca46b6، كما كان SHA-256 للحمولة الضارة الأساسية متطابقًا في جميع العينات الثلاث: 0dc06ecdaa63fe24859cfd955053c23245c536e4733480239d14bebf12688e35. وهذا يشير إلى أن المهاجمين قاموا بتغيير معلمات التغليف الخارجي في حزم npm المختلفة، لكنهم أعادوا استخدام نفس مجموعة المكونات الضارة الأساسية.
بعد فك التشفير، ستكتب المرحلة 2 الحمل الضار الأساسي إلى /tmp، ثم تنفذه عبر Bun، وأخيرًا تحذف الملفات المؤقتة. المنطق الفعلي المسترجع من العينات الثلاث هو كالتالي:
لذلك، نمط الملفات المؤقتة القابلة للتحقق في العينات الثلاث هو:
/tmp/p.js
حجم مُوجّه بيئة Bun هو 898 بايت، وجميع عينات الـ hash متطابقة تمامًا:
ac2a2208e1726e008be6c73dc0872d9bba163319259dff1b62055ac933ca46b6
يحتوي مُحمّل بيئة Bun على منطق مثل child_process.execSync و fs.existsSync و fs.mkdtempSync و fs.chmodSync و os.platform() و os.arch() و getBunPath() لتحديد أو إعداد بيئة Bun. أي أن المرحلة 2 لا تفترض ببساطة أن Bun مثبت عالميًا على النظام؛ ففي مسارات وقت التشغيل غير Bun، تقوم أولاً بتحميل مُحمّل بيئة Bun، ثم تستدعي Bun عبر getBunPath() لتنفيذ الحمل الضار الأساسي.
4. الطبقة الثالثة: جدول سلاسل obfuscator.io
الحمولة الضارة الأساسية التي تم فك تشفيرها ليست نصًا واضحًا قابلًا للقراءة مباشرةً، بل تخضع لتشويش على نمط obfuscator.io باستخدام جداول سلاسل. تقوم هذه الطبقة بتركيز عدد كبير من السلاسل داخل مصفوفة، ثم تعيد بناءها في وقت التشغيل باستخدام فهارس ومنطق دوران، مما يجعل من المستحيل على محللي الأمان استرجاع واجهات برمجة التطبيقات أو المسارات أو أسماء التكوين الحرجة التالية دون إكمال إعادة بناء جدول السلاسل.
5. الطبقة الرابعة: B5 تشفير سلسلة مخصصة
بعد استعادة جدول السلاسل الخاص بـ obfuscator.io، لا يزال هناك طبقة إضافية من تشفير السلاسل المخصصة B5 داخل الحمل الضار الأساسي. تم التأكد من المعلمات التالية:
- KDF: PBKDF2 (Password-Based Key Derivation Function 2)
- دالة التجزئة: SHA-256
- عدد التكرارات: 200000
- طول المفتاح: 32 بايت
- عدد جولات التشفير: 3
- كلمة المرور: ba2c6ddb3672bdd6a611e6850b4f700b52aed3dab2f1b3d5f8c839d4a157a709
- salt: 5b26508dc0f1075a7c0b4d8aa464487e
النتيجة بعد فك التشفير هي كالتالي:
فيه، يمكن رؤية أنواع متعددة من السلاسل النصية الواضحة المفتاحية بعد فك تشفير B5. يجب ملاحظة أن بعض السلاسل كانت في الأصل موجودة في جدول سلاسل obfuscator.io، ولا يمكن البحث عنها مباشرة بالـ grep إلا بعد إكمال الخطوتين: "استبدال جدول السلاسل + فك تشفير B5":
6. طبقة تضمين إضافية: AES-256-GCM + gzip
يحتوي الحمل الضار الأساسي أيضًا على موارد مضمنة مشفرة بـ AES-256-GCM ومضغوطة بـ gzip. يتم استخدام منطق الاستعادة:
تم تقسيم الموارد المضمنة التي تم فك تشفيرها إلى ثلاث فئات حسب الوظيفة:
قراءة الذاكرة عبر المنصات:
تُستخدم هذه الموارد لقراءة ذاكرة العمليات قيد التشغيل. يمكن اكتشاف المفاتيح والرموز في الملفات العادية من خلال فحص الملفات، لكن مُنفذي CI/CD أو أدوات التطوير تخزن قيمًا حساسة مؤقتًا في الذاكرة أثناء التشغيل. يقوم المهاجمون بإدراج نصوص قراءة الذاكرة لثلاثة أنظمة: Linux وWindows وmacOS، بهدف التقاط هذه الأسرار المؤقتة من ذاكرة العملية بأكبر قدر ممكن من التغطية عبر الأنظمة المختلفة.

التكوين المستمر:
تُستخدم هذه الموارد لـ"ترك نقاط تحفيز خلفية". بمعنى آخر، حتى بعد اكتمال عملية تثبيت npm الأصلية، يرغب المهاجمون في أن يعمل الكود الضار مرة أخرى عند قيام المطور بفتح المشروع لاحقًا، أو تشغيل محرر، أو الدخول إلى جلسة Claude Code، أو إعادة تسجيل الدخول إلى النظام. لذلك تستهدف هذه الموارد آليات التهيئة على مستوى المشروع وآليات التشغيل التلقائي على مستوى المستخدم: تهيئة المشروع أكثر خفية، بينما الخدمات على مستوى النظام أكثر ملاءمة للتشغيل طويل الأمد.

C2 والنشر:
تُستخدم هذه الموارد المضمنة للتحكم اللاحق في سلاسل وسرية المستودعات. بعد فك تشفير YZ، يصبح مراقب بحث التزامات GitHub، والذي يبحث دوريًا عن رسائل التزامات بالصيغة firedalazer .، ويتحقق من التوقيع ثم يُحمّل وينفّذ المحتوى البعيد المكتوب بلغة Python؛ أما zZ بعد فك تشفيره فهو قالب سير عمل GitHub Actions يُسمى Run Copilot، والذي يكتب ${{ toJSON(secrets) }} في ملف format-results.txt ويرفعه كأثر. يقدم الأول سلسلة للحصول على/تنفيذ المهام اللاحقة، بينما يقدم الثاني قالبًا لنقل أسرار المستودع عبر أثر GitHub Actions؛ ويتم تنفيذ حقن سير العمل والانتظار لتشغيله وتنزيل الأثر بواسطة منطق API لـ GitHub المرفق في الحمل الرئيسي.

تحليل قدرات الحمل الضار الأساسي
بعد إكمال عملية تفكيك التضليل متعددة الطبقات، يتم إطلاق وتنفيذ حملة الخبث الأساسية. جميع التحليلات التالية تركز على وحدة حملة الخبث الأساسية، وتم تفكيكها وفقًا للخطوات الخمس: جمع البيانات، نقلها، الانتشار، الاستمرارية، والمقاومة.
قراءة الذاكرة من GitHub Actions Runner:
يحتوي العينة على نصوص قراءة الذاكرة لثلاثة أنظمة: Linux و Windows و macOS، وتتمتع بقدرة على استخراج الذاكرة عبر المنصات. يتم تنفيذ نسخة Linux من خلال قراءة /proc//maps و /proc//mem للعملية المستهدفة:
منطق التشغيل النشط للحمولة الضارة الأساسية يستهدف تشغيل Linux الخاص بـ GitHub Actions: عندما يكون GITHUB_ACTIONS === "true" و RUNNER_OS === "Linux"، يبحث الكود في عملية Runner.Worker ويقوم بتصدير الذاكرة، ثم يستخرج الأسرار المخفية بالصيغة "":{"value":"","isSecret":true}" من التصدير. تم التأكد من أن مسارات الجمع النشطة في العينات الثلاث تركز على تشغيل Linux، لكن النصوص البرمجية لجميع المنصات الثلاث مُضمنة بالفعل.
2. جمع بيانات الاعتماد المحلية للمطورين مع السحابة المتعددة:
يحتوي الحمل الضار الأساسي على وحدة منهجية لجمع بيانات الاعتماد، تغطي مزودي السحابة، بيئة CI، التكوينات المحلية للمطورين، GitHub CLI، مديرات كلمات المرور، وملفات المحافظ.
أهداف جمع بيانات مزود السحابة:

يحتوي مكون AWS أيضًا على منطق متعلق بـ ECS / IMDS (خدمة بيانات المثيل) / STS WebIdentity.
يغطي جمع بيانات الاعتماد المحلية للمطورين الأهداف التالية:

يتم تنفيذ منطق مدير كلمات المرور في الشفرة المصدرية على شكل مصفوفة runCommand(command, args)، وليس عن طريق تجميع سلاسل shell.
تغطي وحدة جمع الاعتمادات أهدافًا واسعة تشمل من المنصات السحابية إلى جهاز المطور المحلي؛ وتُنقل البيانات الحساسة التي تم جمعها عبر الآليات التالية.
3. واجهة برمجة تطبيقات GitHub الخارجية و dead-drop:
الحمولة الضارة الأساسية تستخدم واجهة برمجة تطبيقات GitHub كقناة رئيسية لنقل البيانات. يتم تزييف رأس User-Agent ليكون python-requests/2.31.0، وبعد التحقق من رمز المصادقة عبر x-oauth-scopes (الذي يجب أن يحتوي على repo أو public_repo أو workflow)، يتم تنفيذ عملية نقل البيانات: إنشاء مستودع (يتم تعيين الوصف ثابتًا كـ Miasma: The Spreading Blight)، ثم كتابة البيانات المسروقة، بعد ترميزها بـ base64، عبر PUT /repos///contents/results/.
بالإضافة إلى ذلك، نفذ الحمل آلية dead-drop المستندة إلى بحث GitHub عن commits، من خلال البحث عن العلامة "thebeautifulmarchoftime" (أو "thebeautifulsnadsoftime") للحصول على أوامر C2:
let result = await X9("thebeautifulmarchoftime ", xZ);
يُعد مورد التضمين YZ.bin مراقبًا مستقلًا لـ GitHub commit، يُجري مسحًا دوريًا كل 3600 ثانية للبحث عن رسائل commit بتنسيق firedalazer، وبعد التحقق من التوقيع، يُحمّل وينفّذ المحتوى البعيد المكتوب بلغة Python. هذا يشكل قناة تحكم لاحقة مستقلة عن القناة الرئيسية للتسريب.
يتضمن الكود المصدر أيضًا كيان إرسال HTTP POST مُهيأ لاستهداف نقاط النهاية api.anthropic.com و v1/api. تم تعيين حقل noop على قيمة true، مما يعني أنه في حالة عدم تعديله من قبل طرف خارجي، لن يُطلق هذا الإرسال أي طلب شبكة فعلي. وبالتالي، فإن التحليل الثابت يمكنه فقط تأكيد وجود هيكل الكود، لكنه لا يثبت وجود سلوك نشط لتصدير بيانات إلى واجهة برمجة تطبيقات Anthropic.
4. تلوث مستودع GitHub وسير العمل:
يمكن للحمولة الضارة الأساسية تنفيذ عمليات على كائنات git للمستودع عبر واجهة برمجة تطبيقات GitHub REST / GraphQL:

يُنشئ حمل مُعدّ لعملية تدفق عمل ضارة تحت مسمى "release"، ويطالب بصلاحية id-token: write، ثم يستخدم مسار OIDC (OpenID Connect) للحصول على رمز نشر npm. متغيرات البيئة وواجهة برمجة التطبيقات الخاصة بمسار OIDC هما ACTIONS_ID_TOKEN_REQUEST_TOKEN وACTIONS_ID_TOKEN_REQUEST_URL، مع ضبط الجمهور على npm:registry.npmjs.org. يمكن التحقق من المصدر أن الحمل يمتلك القدرة على حقن عملية تدفق عمل في مستودع GitHub، واستخدام OIDC / النشر الموثوق من npm للحصول على صلاحيات النشر.
بالإضافة إلى طريقة حقن سير العمل عبر git refs المذكورة أعلاه، يحتوي الحمل أيضًا على آلية خفية أكثر لنقل الأسرار: حيث يقوم سير العمل المُزَيَّف لـ Run Copilot بكتابة ${{ toJSON(secrets) }} إلى ملف format-results.txt ورفعه كأرشيف. مقارنةً بكتابة الأسرار مباشرةً في محتوى المستودع، فإن هذه الطريقة تركز على نقل البيانات من خلال نواتج تشغيل سير العمل، وقد لا تُظهر مراجعة التغييرات الكودية العادية المحتوى النهائي للأسرار المصدرة.
5. تشغيل سير عمل Copilot عبر artifact لنقل الأسرار:
يُخفي مورد zZ.bin كعملية تدفق GitHub Actions تُدّعي أنها Copilot، وتكتُب ${{ toJSON(secrets) }} في ملف format-results.txt ثم ترفعه كأثر.
تشمل سلوكيات GitHub API المرتبطة: إنشاء/تحديث فرع مؤقت → كتابة blob سير العمل → الانتظار حتى تشغيل سير العمل → تنزيل ملف zip الأثر → قراءة format-results.txt → حذف تشغيل سير العمل والفرع المؤقت. تشمل الكلمات المفتاحية ذات الصلة بالبحث: Run Copilot، VARIABLE_STORE، format-results، chore/add-codeql-static-analysis، .github/workflows/codeql.yml.
6. npm الترويج الذاتي:
يحتوي الحمل الضار الأساسي على طريقتين مستقلتين للنشر/النشر عبر npm.
مسار رمز npm: يتم تعداد رموز npm للمستخدم الحالي عبر GET /-/npm/v1/tokens، ويتم تصفية الرموز التي يكون فيها bypass_2fa === true وتمتلك صلاحيات الكتابة للحزمة، ثم استخدام هذه الرموز لنشر تلقائي. bypass_2fa هو حقل في بيانات الرمز، وليس معلمة في جسم النشر.
مسار GitHub Actions OIDC: يقرأ الحمولة متغيري البيئة ACTIONS_ID_TOKEN_REQUEST_TOKEN وACTIONS_ID_TOKEN_REQUEST_URL، ويطالب برمز OIDC بجمهور npm:registry.npmjs.org، ثم يستبدل رمز OIDC الخاص بـ npm للحصول على صلاحيات النشر.
إجراءات تلويث ملف tarball هي: فك الضغط → كتابة ملف index.js الضار → تعديل package.json:
يتم التمييز بين نوعين من نقاط الدخول: العينات المحلية المرفوعة الثلاث تستخدم preinstall: "node index.js" — في هذه الحالة، لا يتطلب تثبيت البيئة تثبيت Bun مسبقًا، حيث يكون استخدام Node.js كنقطة دخول أولية أكثر خفاءً؛ أما عند التكاثر الذاتي وتلويث الحزم الأخرى، فيتم كتابة preinstall: "bun run index.js" وإضافة تبعية bun — لأن المنطق التالي للحمولة الخبيثة الأساسية يعتمد على واجهات برمجة التطبيقات التي توفرها بيئة تشغيل Bun (مثل Bun.gunzipSync وBun.file)، ويجب ضمان توفر Bun في بيئة الهدف في سيناريوهات التكاثر الذاتي.
7. آلية التثبيت:
يحتوي الحمل الضار الأساسي على أربع طرق للبقاء، تغطي دليل المشروع والتشغيل التلقائي على مستوى النظام.
مُفعّل جلسة Claude Code: يتم كتابة مُفعّل SessionStart في .claude/settings.json عن طريق تضمين المورد S8.bin، مما يُنفّذ تلقائيًا node .claude/setup.mjs عند بدء كل جلسة Claude Code. يتم أيضًا كتابة الحمل الضار في كل من .claude/setup.mjs و.claude/index.js (المسار يُكوّن من ".claude/" + M0، حيث M0 = "index.js").
مهمة folderOpen في VS Code: يُكتب مهمة folderOpen في ملف .vscode/tasks.json مع تضمين المورد EZ.bin، بحيث يتم تنفيذ النص البرمجي نفسه تلقائيًا عند فتح مجلد المشروع المصاب. كما يُكتب ملف .vscode/setup.mjs.
kitty-monitor (systemd / LaunchAgent): يُثبّت خدمة دائمة عن طريق تضمين المورد RZ.bin، حيث تُوزّع الملفات في /.local/share/kitty/cat.py و/.config/systemd/user/kitty-monitor.service (لينكس) و~/Library/LaunchAgents/com.user.kitty-monitor.plist (macOS). يُنشئ الحمل الخبيث دليل /tmp/kitty-، ويكتب YZ.bin (مراقبة GitHub commit) إلى cat.py ثم يُثبّته عبر RZ.bin.
gh-token-monitor (systemd / LaunchAgent): يُثبّت خدمة مراقبة رمز GitHub باستخدام المورد المضمن jZ.bin، مع توزيع الملفات في المجلدات /.local/bin/gh-token-monitor.sh و/.config/gh-token-monitor/ والمسارات المقابلة لـ systemd / LaunchAgent. يقوم هذا المراقب بفحص ما إذا كان رمز GitHub يُعيد رمز حالة 40x، وما إذا كانت بيانات تعريف حزمة npm @cap-js/openapi@1.4.1 غير طبيعية، ثم يُشغّل المعالج عند التفعيل:
هذا يعني أنه عندما يلاحظ المراقب عودة واجهة برمجة تطبيقات GitHub برمز حالة 40x، مثل إلغاء أو فساد أو حالة صلاحيات غير طبيعية للرمز، سيُنفّذ النص الضار المعالج المُدخل. المعالج الذي تم استعادته من العينات الثلاث في هذا المقال هو rm -rf ~/; rm -rf ~/Documents. عند المعالجة، لا يجب إلغاء الرمز مباشرة على المضيف الذي قد لا يزال يُنفّذ الحمولة—بل يجب أولاً عزل المضيف، وتنظيف عناصر التثبيت والعمليات الجارية، ثم تغيير بيانات الاعتماد من بيئة نظيفة.
8. هاردن-راننر / ستيبسيكيوريتي:
يحتوي الحمل الضار الأساسي على منطق للكشف عن وتعطيل StepSecurity Harden-Runner، ويتجاوز حمايات CI/CD عبر الخطوات التالية:
- التحقق من توفر مقبس Docker /var/run/docker.sock؛
- سرد حاويات Docker التي تطابق الكلمات المفتاحية harden-runner / stepsecurity في الاسم أو الصورة؛
- أوقف الحاوية المطابقة عبر POST /containers//kill؛
- أنشئ حاوية Alpine مُميزة، واعمل على إعادة كتابة ملف sudoers ليصبح: runner ALL=(ALL) NOPASSWD:ALL؛
- اكتب تسجيلات غير صالحة لاسماء نطاقات StepSecurity في /etc/hosts؛
- تجاوز /etc/resolv.conf إلى 8.8.8.8 و 1.1.1.1.
يُظهر منطق المواجهة أن العينة تم تكييفها خصيصًا لبيئات CI/CD المحمية بواسطة StepSecurity.
9. EDR / كشف المنتجات الأمنية، وتحديد البيئة، وتجنبها:
يكتشف الحمل الضار الأساسي أسماء العمليات ومسارات التثبيت لعدة منتجات EDR (كشف واستجابة نقطة النهاية) / أمنية:

يحتوي الحمل أيضًا على منطق تجنب المنطقة: يتم الكشف عن متغيرات البيئة LC_ALL و LC_MESSAGES و LANGUAGE و LANG، وإذا كانت بعد تحويلها إلى أحرف صغيرة تبدأ بـ "ru"، فسيتم تخطي التنفيذ. يدعم التعرف على بيئة CI: GitHub Actions و GitLab CI و Travis CI و CircleCI و Jenkins و AWS CodeBuild و Buildkite و AppVeyor و Bitbucket و Drone و TeamCity و Cirrus CI وغيرها. تشمل علامات الحالة المستخدمة أثناء التشغيل: tmp.0987654321.lock و __IS_DAEMON (لتمييز عملية الـ daemon المنفصلة) و SKIP_DOMAIN (لتخطي مسار مرسل المجال) و /tmp/kitty-* و cat.py و /var/tmp/.gh_update_state.
Impact Analysis
من حيث القدرة على الكود المصدر، لا تقتصر تأثيرات هذه العينات الثلاث على التنفيذ الواحد في مرحلة تثبيت npm. يمكن تقسيم المخاطر الفعلية إلى أربع طبقات:
على مستوى مضيف المطور. سيتم جمع متغيرات البيئة وملف .npmrc وملف .pypirc ومفاتيح SSH وتكوين Docker وملف .env ورمز GitHub CLI وبيانات مدير كلمات المرور وملفات المحافظ، مع الحفاظ على القدرة على التفعيل المستمر عبر Claude Code أو VS Code أو خدمة systemd المستخدم أو LaunchAgent على macOS.
على مستوى CI/CD Runner. سيقوم العينة بتحديد مشغل Linux الخاص بـ GitHub Actions، وقراءة ذاكرة عملية Runner.Worker واستخراج الأسرار المخفية؛ كما يمتلك منطقًا مضادًا لـ StepSecurity Harden-Runner، ويسعى إلى تعطيل أو تجاوز مكونات الحماية الخاصة بـ CI/CD.
على مستوى منظمة GitHub ومستوى المستودع. يمكن للعينة استخدام واجهة برمجة تطبيقات GitHub لإنشاء مستودعات، وكتابة المحتويات/النتائج/، وتنفيذ عمليات على مراجع/كائنات/أشجار/التزامات git، وحقن سير عمل ضار، ونقل الأسرار عبر تشغيل سير عمل Copilot + أرشيف.
على مستوى انتشار بيئة npm، يمكن اختيار عينات تحتوي على رموز npm تمتلك صلاحيات الكتابة لحزمة وbypass_2fa === true، أو يمكن استخدام مسار GitHub Actions OIDC / النشر الموثوق من npm للحصول على صلاحيات النشر؛ ثم تنزيل ملف tarball المستهدف، وإدخال مُحمّل خبيث، وتعديل preinstall، وإضافة تبعية Bun، ورفع الإصدار التصحيحي، ونشره، مما يشكّل سلسلة انتشار ذاتية داخل npm.
Summary
تُظهر أدلة مصدر العينات الثلاثة أن الحزمة الضارة هذه ليست مجرد نص سرقة أثناء التثبيت، بل هي مزيج من مُحمّل متعدد المراحل وحُمولة ضارة كاملة: الطبقة الخارجية مُتغيرة عشوائيًا حسب الحزمة، بينما يظل مُوجّه بيئة تشغيل Bun متسقًا مع الحمولة الضارة الأساسية؛ وتغطي الحمولة الأساسية مراحل متعددة تشمل جمع بيانات الاعتماد، استخراج أسرار CI، نشر عبر GitHub/npm، التثبيت الدائم، ومكافحة التدابير الوقائية.
تصميم مدخل عالي الخفاء. لا يُدمج المهاجمون منطقًا ضارًا في كود العمل، بل يُثبّتون مُحمّلًا مشوّشًا في سكريبتات دورة حياة npm. يُعَدّ هذا المُحمّل مسؤولًا بشكل رئيسي عن فك تشفير وتنفيذ الحمل المضمن، مما يجعل اكتشاف القدرات الحقيقية من خلال مراجعة كود المصدر وحدها صعبًا جدًا.
سلسلة تضليل متعددة الطبقات. يمر الحمل الضار بخمس طبقات من التغليف: مصفوفة أرقام + استبدال حروف ROT، تشفير AES-128-GCM، تضليل عبر obfuscator.io، تشفير سلسلة مخصص من B5، ثم طبقة تضمين بـ AES-256-GCM + gzip. يمكن أن تتغير المفاتيح أو المعلمات لكل طبقة بشكل مستقل بين الحزم، مما يجعل الكشف الجماعي القائم على السمات الثابتة أكثر صعوبة.
قدرة هجومية شاملة على مستوى المنظمة. يمتلك هذا البرنامج الضار قدرات على قراءة ذاكرة GitHub Actions Runner، وجمع بيانات الاعتماد من سحابات متعددة ومحلية، ونقل البيانات عبر واجهة برمجة تطبيقات GitHub وتقديمها عبر نقاط تفريغ ميتة، وتعطيل مستودعات GitHub وسير عملها، والانتشار الذاتي عبر npm، والبقاء المستمر، ومكافحة التدابير الوقائية. من هيكل الشيفرة المصدرية، يبدو أنه يمتلك مسارات كودية تسمح ببدء التثبيت من نقطة واحدة ثم الانتشار إلى المستودعات وسير عمل CI/CD وسلسلة نشر npm؛ لا يزال نطاق الانتشار الفعلي بحاجة إلى التأكيد من خلال سجلات التثبيت ومراجعة المستودعات وبيانات الاستشعار من المنصة.
ما يمكن تأكيده من أدلة المصدر هو أن العينات الثلاثة جميعها تُفعّل تلقائيًا عبر preinstall وتُفك تشفيرها وتنفذ نفس العينة الرئيسية. أما بالنسبة لكيفية اكتساب الصلاحيات الأولية في الحدث الحقيقي، فلا يمكن إثباتها بناءً على هذه العينات الثلاثة من tgz فقط.
توصية التخلص
- التحقق من إزالة الإصدارات الضارة من الاعتمادات المشروع، ملف القفل، ذاكرة التخزين المؤقت للسجلات الخاصة، وذاكرة التخزين المؤقت للبناء.
- تحقق من سجلات التثبيت لمعرفة ما إذا كانت تظهر preinstall أو node index.js أو bun run أو /tmp/p*.js أو tmp.0987654321.lock.
- لا تُلغِي التوكن مباشرة على المضيف الضحية الذي لا يزال من المحتمل أنه يُشغّل الـ payload. يُوصى أولاً بعزل المضيف الضحية، وتنظيف العمليات الجارية وعناصر التثبيت الدائم، ثم تغيير التوكنات المتعلقة بـ GitHub وnpm وبيانات اعتماد السحابة وKubernetes وVault وSSH ومستودع Docker وإدارة كلمات المرور من بيئة نظيفة.
- تحقق من مستودع GitHub الأخير للفرع، والالتزام، وسير العمل، والقطع، والمستودعات الجديدة، مع التركيز على الكلمات المفتاحية مثل Run Copilot و format-results و chore/add-codeql-static-analysis و .github/workflows/codeql.yml و OIDC_PACKAGES.
- تحقق مما إذا تم إضافة أو تعديل مجلد المشروع: .claude/settings.json، .claude/setup.mjs، .vscode/tasks.json، .vscode/setup.mjs.
- تحقق من التثبيت المستمر على مستوى المستخدم: /.local/share/kitty/cat.py، /.config/systemd/user/kitty-monitor.service، ~/Library/LaunchAgents/com.user.kitty-monitor.plist، ملفات gh-token-monitor.
- تحقق من سجل نشر npm للتأكد من وجود إصدارات تصحيح غير مصرح بها؛ كما قم بمراجعة بيانات تعريف رمز npm، مع التركيز على الرموز التي يمكنها تجاوز المصادقة الثنائية (Two-Factor Authentication) وتمتلك صلاحيات الكتابة للحزمة.
- إجراء مراجعة للسلامة للمنتجات الناتجة التي تم بناؤها في بيئات ملوثة.
IOC
ملف ضار
اسم الملف: redhat-cloud-services-frontend-components-config-6.11.3.tgz MD5: 633ad8849a59e2bfb7a0fe589e816a07 SHA1: 675294612f455fe6a9acb195f0cbe3687d8e2e34 SHA256: 0c9c67ec40d5f23efa1ec3470d0ac88b4993ccc0e92be913fc29a337dfc4f060
اسم الملف: redhat-cloud-services-types-3.6.1.tgz MD5: 9e6c5af01438b52c9a411686c1f1b8ff SHA1: 88d098c8d96e9ae17550e9798c3b62c420464b8c SHA256: d543bb3cdf1569c2b3d38c8a4081ed746cfe78bf3236c2302704d79ab7fa9558
اسم الملف: redhat-cloud-services-rule-components-4.7.2.tgz MD5: f1ffdbf5e639899f26a6ebab2eec408d SHA1: f3c5c21274045ae02fef11e931de6dcf8462a067 SHA256: aaf00d06baa3c679b82452c50014e9824b8874e9ca2d150f19095f8de19ba90f
SHA256
ac2a2208e1726e008be6c73dc0872d9bba163319259dff1b62055ac933ca46b6
0dc06ecdaa63fe24859cfd955053c23245c536e4733480239d14bebf12688e35
Malicious dependency
npm:@redhat-cloud-services/topological-inventory-client@3.0.10
npm:@redhat-cloud-services/topological-inventory-client@3.0.11
npm:@redhat-cloud-services/topological-inventory-client@3.0.13
npm:@redhat-cloud-services/compliance-client@4.0.3
npm:@redhat-cloud-services/compliance-client@4.0.4
npm:@redhat-cloud-services/compliance-client@4.0.6
npm:@redhat-cloud-services/rbac-client@9.0.3
npm:@redhat-cloud-services/rbac-client@9.0.4
npm:@redhat-cloud-services/rbac-client@9.0.6
npm:@redhat-cloud-services/insights-client@4.0.4
npm:@redhat-cloud-services/insights-client@4.0.5
npm:@redhat-cloud-services/insights-client@4.0.7
npm:@redhat-cloud-services/frontend-components@7.7.2
npm:@redhat-cloud-services/frontend-components@7.7.3
npm:@redhat-cloud-services/frontend-components@7.7.5
npm:@redhat-cloud-services/frontend-components-utilities@7.4.1
npm:@redhat-cloud-services/frontend-components-utilities@7.4.2
npm:@redhat-cloud-services/frontend-components-utilities@7.4.4
npm:@redhat-cloud-services/remediations-client@4.0.4
npm:@redhat-cloud-services/remediations-client@4.0.5
npm:@redhat-cloud-services/remediations-client@4.0.7
npm:@redhat-cloud-services/frontend-components-notifications@6.9.2
npm:@redhat-cloud-services/frontend-components-notifications@6.9.3
npm:@redhat-cloud-services/frontend-components-notifications@6.9.5
npm:@redhat-cloud-services/patch-client@4.0.4
npm:@redhat-cloud-services/patch-client@4.0.5
npm:@redhat-cloud-services/patch-client@4.0.7
npm:@redhat-cloud-services/host-inventory-client@5.0.3
npm:@redhat-cloud-services/host-inventory-client@5.0.4
npm:@redhat-cloud-services/host-inventory-client@5.0.6
npm:@redhat-cloud-services/rule-components@4.7.2
npm:@redhat-cloud-services/rule-components@4.7.3
npm:@redhat-cloud-services/rule-components@4.7.5
npm:@redhat-cloud-services/frontend-components-advisor-components@3.8.2
npm:@redhat-cloud-services/frontend-components-advisor-components@3.8.4
npm:@redhat-cloud-services/frontend-components-advisor-components@3.8.6
npm:@redhat-cloud-services/notifications-client@6.1.4
npm:@redhat-cloud-services/notifications-client@6.1.5
npm:@redhat-cloud-services/notifications-client@6.1.7
npm:@redhat-cloud-services/sources-client@3.0.10
npm:@redhat-cloud-services/sources-client@3.0.11
npm:@redhat-cloud-services/sources-client@3.0.13
npm:@redhat-cloud-services/integrations-client@6.0.4
npm:@redhat-cloud-services/integrations-client@6.0.5
npm:@redhat-cloud-services/integrations-client@6.0.7
npm:@redhat-cloud-services/frontend-components-config@6.11.3
npm:@redhat-cloud-services/frontend-components-config@6.11.4
npm:@redhat-cloud-services/frontend-components-config@6.11.6
npm:@redhat-cloud-services/frontend-components-config-utilities@4.11.2
npm:@redhat-cloud-services/frontend-components-config-utilities@4.11.3
npm:@redhat-cloud-services/frontend-components-config-utilities@4.11.5
npm:@redhat-cloud-services/hcc-pf-mcp@0.6.1
npm:@redhat-cloud-services/hcc-pf-mcp@0.6.2
npm:@redhat-cloud-services/hcc-pf-mcp@0.6.4
npm:@redhat-cloud-services/frontend-components-remediations@4.9.2
npm:@redhat-cloud-services/frontend-components-remediations@4.9.3
npm:@redhat-cloud-services/frontend-components-remediations@4.9.5
npm:@redhat-cloud-services/eslint-config-redhat-cloud-services@3.2.1
npm:@redhat-cloud-services/eslint-config-redhat-cloud-services@3.2.2
npm:@redhat-cloud-services/eslint-config-redhat-cloud-services@3.2.4
npm:@redhat-cloud-services/javascript-clients-shared@2.0.8
npm:@redhat-cloud-services/javascript-clients-shared@2.0.9
npm:@redhat-cloud-services/javascript-clients-shared@2.0.11
npm:@redhat-cloud-services/quickstarts-client@4.0.11
npm:@redhat-cloud-services/quickstarts-client@4.0.12
npm:@redhat-cloud-services/quickstarts-client@4.0.14
npm:@redhat-cloud-services/config-manager-client@5.0.4
npm:@redhat-cloud-services/config-manager-client@5.0.5
npm:@redhat-cloud-services/config-manager-client@5.0.7
npm:@redhat-cloud-services/hcc-feo-mcp@0.3.1
npm:@redhat-cloud-services/hcc-feo-mcp@0.3.2
npm:@redhat-cloud-services/hcc-feo-mcp@0.3.4
npm:@redhat-cloud-services/entitlements-client@4.0.11
npm:@redhat-cloud-services/entitlements-client@4.0.12
npm:@redhat-cloud-services/entitlements-client@4.0.14
npm:@redhat-cloud-services/tsc-transform-imports@1.2.2
npm:@redhat-cloud-services/tsc-transform-imports@1.2.4
npm:@redhat-cloud-services/tsc-transform-imports@1.2.6
npm:@redhat-cloud-services/hcc-kessel-mcp@0.3.1
npm:@redhat-cloud-services/hcc-kessel-mcp@0.3.2
npm:@redhat-cloud-services/hcc-kessel-mcp@0.3.4
npm:@redhat-cloud-services/frontend-components-testing@1.2.1
npm:@redhat-cloud-services/frontend-components-testing@1.2.2
npm:@redhat-cloud-services/frontend-components-testing@1.2.4
npm:@redhat-cloud-services/types@3.6.1
npm:@redhat-cloud-services/types@3.6.2
npm:@redhat-cloud-services/types@3.6.4
npm:@redhat-cloud-services/chrome@2.3.1
npm:@redhat-cloud-services/chrome@2.3.2
npm:@redhat-cloud-services/chrome@2.3.4
npm:@redhat-cloud-services/frontend-components-translations@4.4.1
npm:@redhat-cloud-services/frontend-components-translations@4.4.2
npm:@redhat-cloud-services/frontend-components-translations@4.4.4
npm:@redhat-cloud-services/vulnerabilities-client@2.1.8
npm:@redhat-cloud-services/vulnerabilities-client@2.1.9
npm:@redhat-cloud-services/vulnerabilities-client@2.1.11
