JSON Toolkit

JSON प्रोसेसिंग के लिए सुरक्षा जोखिम चेकलिस्ट

मुख्य बात: JSON.parse स्वयं सुरक्षित है; ख़तरा उसके बाद होता है: डीप मर्जिंग, पथ द्वारा असाइनमेंट, HTML में इनलाइन करना, असीमित गहराई और आकार।

मध्यम~12 मिनट पढ़ें2026-10-30 को प्रकाशित2026-10-30 को अपडेट किया गया
JSON प्रोसेसिंग के लिए सुरक्षा जोखिम चेकलिस्ट का कवर

मुख्य निष्कर्ष: पहले दो प्रवेश बिंदु बंद करें — हर डीप मर्ज और पथ असाइनमेंट को proto, prototype, और constructor को अस्वीकार करना चाहिए, और अनुरोध-बॉडी का आकार तथा नेस्टिंग गहराई के लिए कठोर सीमाएँ चाहिए जो पार्सिंग से पहले लागू हों। केवल JSON.parse को देखने से ख़तरनाक दरवाज़े पूरे खुले रह जाते हैं।

हमलावरों को नई भेद्यता ईजाद करने की ज़रूरत नहीं; वे बस JSON में एक और कुंजी जोड़ देते हैं।

1. प्रोटोटाइप प्रदूषण: सबसे आम और सबसे अनदेखा

1.1 JSON.parse स्वयं सुरक्षित है

const data = JSON.parse('{"__proto__": {"polluted": true}}');
console.log(data.polluted);        // undefined
console.log({}.polluted);          // undefined

JSON.parse __proto__ को एक सामान्य own प्रॉपर्टी के रूप में बनाता है और प्रोटोटाइप चेन को नहीं बदलता। इसलिए "JSON.parse प्रोटोटाइप प्रदूषण पैदा करता है" का दावा ग़लत है।

1.2 उसके बाद होने वाला मर्ज ख़तरनाक है

/**
 * असुरक्षित डीप मर्ज: स्रोत ऑब्जेक्ट की कुंजियों को सीधे लक्ष्य ऑब्जेक्ट में लिखता है।
 * @param target लक्ष्य ऑब्जेक्ट
 * @param source स्रोत ऑब्जेक्ट (उपयोगकर्ता JSON से आ सकता है)
 * @returns मर्ज किया गया ऑब्जेक्ट
 */
function unsafeMerge(target, source) {
  for (const key of Object.keys(source)) {
    if (typeof source[key] === 'object' && source[key] !== null) {
      target[key] = target[key] ?? {};
      unsafeMerge(target[key], source[key]);
    } else {
      target[key] = source[key];
    }
  }
  return target;
}

unsafeMerge({}, JSON.parse('{"__proto__": {"isAdmin": true}}'));
console.log({}.isAdmin);    // true — पूरी साइट प्रदूषित है

constructor.prototype उसी तरह का दूसरा प्रवेश बिंदु है:

{ "constructor": { "prototype": { "isAdmin": true } } }

1.3 इसे कैसे ठीक करें

/** वे कुंजी नाम जिन्हें अस्वीकार करना है */
const FORBIDDEN_KEYS = new Set(['__proto__', 'prototype', 'constructor']);

/**
 * सुरक्षित डीप मर्ज: प्रोटोटाइप-संबंधित कुंजियाँ अस्वीकार करता है और केवल own प्रॉपर्टीज़ संभालता है।
 * @param target लक्ष्य ऑब्जेक्ट
 * @param source स्रोत ऑब्जेक्ट
 * @returns नया मर्ज किया गया ऑब्जेक्ट
 */
function safeMerge(target, source) {
  const result = { ...target };

  for (const key of Object.keys(source)) {
    if (FORBIDDEN_KEYS.has(key)) {
      continue;
    }
    const sourceValue = source[key];
    const isPlainObject =
      typeof sourceValue === 'object' && sourceValue !== null && !Array.isArray(sourceValue);

    if (isPlainObject) {
      result[key] = safeMerge(target[key] ?? {}, sourceValue);
      continue;
    }

    // defineProperty का उपयोग करें ताकि कोई setter प्रोटोटाइप परिवर्तन ट्रिगर न कर सके
    Object.defineProperty(result, key, {
      value: sourceValue,
      enumerable: true,
      writable: true,
      configurable: true,
    });
  }

  return result;
}

एक अधिक गहन दृष्टिकोण यह है कि कंटेनर के रूप में Object.create(null) का उपयोग करें (प्रदूषित करने के लिए कोई प्रोटोटाइप चेन नहीं है), या बस एक Map। जब किसी तैयार लाइब्रेरी का उपयोग करें, तो पुष्टि करें कि वह पहले से इन तीन कुंजियों को संभालती है — lodash merge के कई पुराने संस्करण ठीक इसी के लिए रिपोर्ट किए गए थे।

1.4 पथ द्वारा असाइनमेंट बराबर ख़तरनाक है

// विरोधी पैटर्न: पथ उपयोगकर्ता इनपुट से आता है
for (const segment of pathSegments) {
  current = current[segment] ?? (current[segment] = {});
}

जब pathSegments में __proto__ आता है, तो यह ऊपर के प्रदूषण के बराबर है। JSON Pointer / JSON Patch के कार्यान्वयन को हर टोकन खंड का सत्यापन करना चाहिए।

2. संसाधन समाप्ति के हमले

हमला चाल शमन
गहरी नेस्टिंग [[[[...]]]] के एक लाख स्तर नेस्टिंग गहराई सीमित करें (आम तौर पर 32–64) और पार्सिंग से पहले प्री-स्कैन करें
अत्यधिक बड़े ऐरे करोड़ों एलिमेंट वाला ऐरे अनुरोध बॉडी आकार और ऐरे लंबाई सीमित करें
अत्यधिक लंबी स्ट्रिंग्स सैकड़ों MB की एक स्ट्रिंग फ़ील्ड लंबाई सीमित करें
दोहराई गई कुंजियों का विस्फोट बहुत अधिक दोहराई गई कुंजियाँ ऑब्जेक्ट कुंजियों की संख्या सीमित करें

सर्वर पर दो सबसे प्रभावी बचाव हैं अनुरोध बॉडी आकार सीमाएँ और गहराई सीमाएँ, और दोनों को किसी भी पार्सिंग से पहले प्रभावी होना चाहिए।

/**
 * पार्सिंग के दौरान स्टैक ओवरफ़्लो रोकने के लिए पार्सिंग से पहले सस्ते तरीके से नेस्टिंग गहराई जाँचता है।
 * @param text JSON टेक्स्ट
 * @param maxDepth अधिकतम अनुमत गहराई
 * @returns क्या गहराई अनुमत सीमा के भीतर है
 */
function isDepthAcceptable(text: string, maxDepth = 64): boolean {
  let currentDepth = 0;
  let isInsideString = false;
  let isEscaped = false;

  for (const character of text) {
    if (isInsideString) {
      if (isEscaped) {
        isEscaped = false;
      } else if (character === '\\') {
        isEscaped = true;
      } else if (character === '"') {
        isInsideString = false;
      }
      continue;
    }

    if (character === '"') {
      isInsideString = true;
    } else if (character === '{' || character === '[') {
      currentDepth += 1;
      if (currentDepth > maxDepth) {
        return false;
      }
    } else if (character === '}' || character === ']') {
      currentDepth -= 1;
    }
  }

  return true;
}

3. HTML में इनलाइन करते समय इंजेक्शन

JSON को सीधे पेज में लिखना एक आम अभ्यास है, लेकिन यह तीन प्रकार की समस्याएँ लाता है:

<script>
  window.__DATA__ = {"title": "</script><script>alert(1)</script>"};
</script>

स्ट्रिंग के अंदर का </script> स्क्रिप्ट ब्लॉक को जल्दी बंद कर देता है, और बाक़ी HTML के रूप में पार्स होता है — एक क्लासिक XSS।

दो और कैरेक्टर इसी तरह व्यवहार करते हैं: \u2028 (लाइन सेपरेटर) और \u2029 (पैराग्राफ़ सेपरेटर) JSON में वैध हैं, लेकिन उन्हें JS स्रोत में एम्बेड करने से सिंटैक्स त्रुटि होती है।

समाधान:

/**
 * इनलाइन स्क्रिप्ट के लिए JSON को सुरक्षित रूप से सीरियलाइज़ करता है।
 * @param value डेटा ऑब्जेक्ट
 * @returns वह स्ट्रिंग जिसे स्क्रिप्ट टैग के अंदर सुरक्षित रूप से रखा जा सके
 */
function serializeForInlineScript(value: unknown): string {
  return JSON.stringify(value)
    .replace(/</g, '\\u003c')
    .replace(/>/g, '\\u003e')
    .replace(/&/g, '\\u0026')
    .replace(/\u2028/g, '\\u2028')
    .replace(/\u2029/g, '\\u2029');
}

एक अधिक मज़बूत दृष्टिकोण यह है कि डेटा को <script type="application/json"> के अंदर रखें (जिसकी सामग्री निष्पादित नहीं होती), फिर उसे JSON.parse(document.getElementById('data').textContent) से पढ़ें।

4. JSON हाईजैकिंग और JSONP

शुरुआती ब्राउज़र क्रॉस-ओरिजिन <script src="https://api.example.com/user"> को लौटाए गए JavaScript को सीधे निष्पादित करने देते थे, और जिस JSON का शीर्ष स्तर एक ऐरे हो, वह संयोगवश एक वैध JS एक्सप्रेशन भी है, इसलिए हमलावर Array कॉन्स्ट्रक्टर को ओवरराइड करके डेटा पढ़ सकता था।

आधुनिक ब्राउज़रों में पहले से सुरक्षा है, लेकिन हम फिर भी सुझाव देते हैं:

  • API के शीर्ष स्तर पर ऐरे के बजाय ऑब्जेक्ट का उपयोग करें ({"data": [...]});
  • X-Content-Type-Options: nosniff भेजें और Content-Type: application/json सख़्ती से सेट करें;
  • JSONP का उपयोग बंद करें; जब क्रॉस-ओरिजिन पहुँच वास्तव में ज़रूरी हो तो CORS का उपयोग करें।

5. ऑब्जेक्ट्स में डीसेरियलाइज़ करते समय टाइप भ्रम

Jackson में डिफ़ॉल्ट टाइपिंग सक्षम करने के बाद (enableDefaultTyping), एक हमलावर @class फ़ील्ड निर्दिष्ट कर सकता है ताकि सर्वर कोई भी क्लास इंस्टैंशिएट करे, और इसे रिमोट कोड निष्पादन प्राप्त करने के लिए शोषण-योग्य गैजेट चेन के साथ जोड़ सकता है। इस तरह की भेद्यताएँ कई बार सामने आई हैं (Fastjson और Jackson दोनों चपेट में आए हैं)।

सिद्धांत: डीसेरियलाइज़ेशन का लक्ष्य प्रकार सर्वर कोड द्वारा स्थिर रूप से निर्दिष्ट होना चाहिए और पेलोड सामग्री से कभी निर्धारित नहीं होना चाहिए।

6. रेगुलर एक्सप्रेशन और स्कीमा में सेवा-अस्वीकृति

  • JSON Schema का pattern और JSONPath का match() दोनों रेगुलर एक्सप्रेशन स्वीकार करते हैं;
  • (a+)+$ जैसे पैटर्न लंबे इनपुट पर घातीय रूप से बैकट्रैक करते हैं;
  • यदि रेगेक्स उपयोगकर्ताओं से आता है (कस्टम सत्यापन नियम), तो आपको उसकी लंबाई सीमित करनी चाहिए, बैकरेफ़रेंस और नेस्टेड क्वांटिफ़ायर मना करने चाहिए, या RE2 जैसे रैखिक इंजन पर स्विच करना चाहिए।

7. लॉगिंग और मास्किंग

JSON लॉग संवेदनशील जानकारी लीक करने का सबसे आसान तरीका हैं:

/** वे फ़ील्ड नाम जिन्हें लॉग में मास्क करना है (लोअरकेस में मिलान) */
const SENSITIVE_KEYS = new Set(['password', 'token', 'secret', 'idcard', 'phone', 'authorization']);

/**
 * लॉग आउटपुट के लिए किसी ऑब्जेक्ट में संवेदनशील फ़ील्ड्स को पुनरावर्ती रूप से मास्क करता है।
 * @param value मूल मान
 * @returns मास्क की गई प्रतिलिपि
 */
function maskSensitive(value: unknown): unknown {
  if (Array.isArray(value)) {
    return value.map((item) => maskSensitive(item));
  }
  if (typeof value === 'object' && value !== null) {
    return Object.fromEntries(
      Object.entries(value).map(([entryKey, entryValue]) => {
        if (SENSITIVE_KEYS.has(entryKey.toLowerCase())) {
          return [entryKey, '***'];
        }
        return [entryKey, maskSensitive(entryValue)];
      }),
    );
  }
  return value;
}

ध्यान दें कि मास्किंग लॉग लिखे जाने से पहले होनी चाहिए, बाद में लॉगिंग प्लेटफ़ॉर्म से सफ़ाई करके नहीं।

8. लॉन्च-पूर्व चेकलिस्ट

  • [ ] सभी डीप-मर्ज फ़ंक्शन __proto__ / prototype / constructor को अस्वीकार करते हैं;
  • [ ] JSON Pointer / Patch पथ टोकन व्हाइटलिस्ट के विरुद्ध सत्यापित होते हैं;
  • [ ] अनुरोध बॉडी आकार और नेस्टिंग गहराई की कठोर सीमाएँ हैं, और वे सीमाएँ पार्सिंग से पहले लागू होती हैं;
  • [ ] HTML में इनलाइन की गई JSON <, >, &, U+2028, और U+2029 को एस्केप करती है;
  • [ ] API शीर्ष स्तर पर ऑब्जेक्ट का उपयोग करती हैं और प्रतिक्रियाएँ nosniff भेजती हैं;
  • [ ] डीसेरियलाइज़ेशन के लक्ष्य प्रकार कोड में स्थिर रूप से निर्दिष्ट हैं;
  • [ ] उपयोगकर्ता-नियंत्रित रेगुलर एक्सप्रेशन की जटिलता सीमाएँ हैं;
  • [ ] लॉग फ़ील्ड के मास्किंग नियम हैं और वे नियम परीक्षित हैं।

FAQ

मैं कैसे जाँचूँ कि मेरा डीप मर्ज प्रोटोटाइप प्रदूषण के प्रति भेद्य है?

JSON.parse('{"__proto__": {"isAdmin": true}}') को मर्ज फ़ंक्शन में डालें, फिर जाँचें कि {}.isAdmin true हो गया या नहीं; constructor.prototype पेलोड के साथ दोहराएँ। किसी तीसरे-पक्ष लाइब्रेरी के साथ, पुष्टि करें कि वह __proto__, prototype, और constructor को अस्वीकार करती है — lodash merge के कई पुराने संस्करण ठीक इसी के लिए रिपोर्ट किए गए थे। पथ-आधारित असाइनमेंट को अपनी जाँच चाहिए, क्योंकि पथ में __proto__ टोकन उसी तरह प्रदूषित करता है।

JSON.parse स्वयं प्रोटोटाइप प्रदूषण का स्रोत क्यों नहीं है?

यह __proto__ को एक सामान्य own प्रॉपर्टी के रूप में बनाता है और प्रोटोटाइप चेन को कभी नहीं बदलता, इसलिए data.polluted और {}.polluted दोनों undefined रहते हैं। प्रदूषण बाद में होता है: एक असुरक्षित डीप मर्ज स्रोत ऑब्जेक्ट की कुंजियों को सीधे लक्ष्य में लिखता है, और constructor.prototype उसी तरह का प्रवेश बिंदु है। JSON.parse को मूल कारण मानने का अर्थ है ग़लत जगह ठीक करना।

HTML में इनलाइन की गई JSON को हमेशा एस्केप करना चाहिए?

हाँ। स्ट्रिंग के अंदर </script> स्क्रिप्ट ब्लॉक को जल्दी बंद कर देता है और बाक़ी HTML के रूप में पार्स होता है — एक क्लासिक XSS; <, >, &, U+2028, और U+2029 को भी एस्केप करना चाहिए। एक सुरक्षित विकल्प यह है कि डेटा को <script type="application/json"> के अंदर रखें, जिसकी सामग्री निष्पादित नहीं होती, और textContent पर JSON.parse से पढ़ें।