
تنفيذ تعليمات برمجية عن بُعد في 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 كصندوق حماية (sandbox)، لكن process.binding("spawn_sync") يظل قابلاً للوصول:
process.binding("spawn_sync") لا يزال قابلاً للوصول ويُشغّل عملية نظام تشغيل حقيقية.
كنت أُدقّق النظام الفرعي للمشغّلات (runner) في 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 المحقونة الأمر داخل عملية runner المتفرعة.
تنفيذ أوامر عن بُعد (RCE) بعد المصادقة على مضيف واجهة DbGate API. غالبًا ما يُنشر DbGate، وهو واجهة رسومية لإدارة قواعد البيانات، بوصول شبكي واسع إلى قواعد البيانات الداخلية — لذا يُعدّ تنفيذ التعليمات البرمجية عليه نقطة انطلاق قوية نحو طبقة البيانات.
أُفصح عنه بمسؤولية. نُشر PoC بعد إصدار الإصلاح، للمدافعين وهندسة الكشف.