Sanitizer एपीआई की मदद से, DOM में सुरक्षित बदलाव करना

नए Sanitizer एपीआई का मकसद, पेज में सुरक्षित तरीके से आर्बिट्रेरी स्ट्रिंग डालने के लिए, एक मज़बूत प्रोसेसर बनाना है.

Jack J
Jack J

ऐप्लिकेशन हर समय गैर-भरोसेमंद स्ट्रिंग से निपटते हैं, लेकिन उस कॉन्टेंट को एचटीएमएल दस्तावेज़ के हिस्से के रूप में सुरक्षित तरीके से रेंडर करना मुश्किल हो सकता है. ज़रूरत के मुताबिक सावधानी बरतने के बिना, गलती से क्रॉस-साइट स्क्रिप्टिंग (XSS) के अवसर पैदा हो सकते हैं. इनका इस्तेमाल नुकसान पहुंचाने वाले हमलावर कर सकते हैं.

इस जोखिम को कम करने के लिए, नए Sanitizer API प्रस्ताव का मकसद, पेज में आर्बिट्रेरी स्ट्रिंग को सुरक्षित तरीके से डालने के लिए एक मज़बूत प्रोसेसर बनाना है. इस लेख में, एपीआई के बारे में जानकारी दी गई है. साथ ही, इसके इस्तेमाल के बारे में भी बताया गया है.

// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerror=alert(0)>`, new Sanitizer())

उपयोगकर्ता का इनपुट एस्केप किया जा रहा है

डीओएम में उपयोगकर्ता का इनपुट, क्वेरी स्ट्रिंग, कुकी का कॉन्टेंट वगैरह शामिल करते समय, स्ट्रिंग सही तरीके से एस्केप होनी चाहिए. .innerHTML के ज़रिए डीओएम हेर-फेर पर खास ध्यान देना चाहिए, जहां अनएस्केप्ड स्ट्रिंग आम तौर पर XSS का सोर्स होती हैं.

const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.innerHTML = user_input

अगर ऊपर दी गई इनपुट स्ट्रिंग में, एचटीएमएल के विशेष वर्णों को एस्केप किया जाता है या .textContent का इस्तेमाल करके बड़ा किया जाता है, तो alert(0) लागू नहीं होगा. हालांकि, चूंकि उपयोगकर्ता के जोड़े गए <em> को स्ट्रिंग के तौर पर भी उसी तरह बड़ा किया जाता है, इसलिए एचटीएमएल में टेक्स्ट की सजावट को बनाए रखने के लिए, इस तरीके का इस्तेमाल नहीं किया जा सकता.

यहां सबसे अच्छा काम एस्केप करना नहीं, बल्कि साफ़ करना है.

उपयोगकर्ता के इनपुट से अनचाही चीज़ें हटाई जा रही हैं

एस्केपिंग और सैनिटाइज़िंग में अंतर

एस्केपिंग का मतलब है, खास एचटीएमएल वर्णों को एचटीएमएल इकाइयों से बदलना.

'साफ़ करना' का मतलब है, एचटीएमएल स्ट्रिंग से वाक्य के रूप में नुकसान पहुंचाने वाले हिस्सों (जैसे कि स्क्रिप्ट चलाना) को हटाना.

उदाहरण

पिछले उदाहरण में, <img onerror> की वजह से गड़बड़ी हैंडलर ट्रिगर होता है, लेकिन अगर onerror हैंडलर को हटा दिया गया है, तो <em> को बनाए रखते हुए इसे डीओएम में सुरक्षित तरीके से बड़ा किया जा सकता है.

// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerror=alert(0)>`
// Sanitized ⛑
$div.innerHTML = `<em>hello world</em><img src="">`

सही तरीके से सैनिटाइज़ करने के लिए, इनपुट स्ट्रिंग को एचटीएमएल के रूप में पार्स करना, नुकसान पहुंचाने वाले टैग और एट्रिब्यूट को हटाना और नुकसान न पहुंचाने वाले एट्रिब्यूट को रखना ज़रूरी है.

सुझाई गई सैनिटाइज़र एपीआई की खास बातों का मकसद, ब्राउज़र के लिए इस तरह की प्रोसेसिंग को स्टैंडर्ड एपीआई के तौर पर उपलब्ध कराना है.

सैनिटाइज़र एपीआई

Sanitizer API का इस्तेमाल इस तरह से किया जाता है:

const $div = document.querySelector('div')
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.setHTML(user_input, { sanitizer: new Sanitizer() }) // <div><em>hello world</em><img src=""></div>

हालांकि, { sanitizer: new Sanitizer() } डिफ़ॉल्ट आर्ग्युमेंट है. इसलिए, यह इसके जैसा नीचे दिया गया हो सकता है.

$div.setHTML(user_input) // <div><em>hello world</em><img src=""></div>

ध्यान दें कि setHTML() की जानकारी Element पर दी गई है. Element वाला तरीका होने की वजह से, पार्स करने के लिए कॉन्टेक्स्ट अपने-आप जानकारी देने वाला होता है (इस मामले में <div>). पार्स करने के बाद, डेटा सीधे डीओएम में दिखाया जाता है.

स्ट्रिंग के रूप में सैनिटाइज़ेशन का नतीजा पाने के लिए, setHTML() के नतीजों में से .innerHTML का इस्तेमाल किया जा सकता है.

const $div = document.createElement('div')
$div.setHTML(user_input)
$div.innerHTML // <em>hello world</em><img src="">

कॉन्फ़िगरेशन के ज़रिए पसंद के मुताबिक बनाएं

सैनिटाइज़र एपीआई को, स्क्रिप्ट एक्ज़ीक्यूशन को ट्रिगर करने वाली स्ट्रिंग हटाने के लिए डिफ़ॉल्ट रूप से कॉन्फ़िगर किया जाता है. हालांकि, आपके पास कॉन्फ़िगरेशन ऑब्जेक्ट के ज़रिए सैनिटाइज़ेशन प्रोसेस में, अपने हिसाब से बदलाव करने का विकल्प भी है.

const config = {
  allowElements: [],
  blockElements: [],
  dropElements: [],
  allowAttributes: {},
  dropAttributes: {},
  allowCustomElements: true,
  allowComments: true
};
// sanitized result is customized by configuration
new Sanitizer(config)

नीचे दिए गए विकल्प तय करते हैं कि सैनिटाइज़ेशन नतीजे में बताए गए एलिमेंट के साथ क्या होना चाहिए.

allowElements: उन चीज़ों के नाम जिन्हें सैनिटाइज़र को रखना चाहिए.

blockElements: उन चीज़ों के नाम जिन्हें सैनिटाइज़र को अपने बच्चों को बनाए रखते हुए हटाना चाहिए.

dropElements: सैनिटाइज़र को जिन चीज़ों को हटाना चाहिए उनके नाम और बच्चों के नाम भी उन चीज़ों के नाम हैं.

const str = `hello <b><i>world</i></b>`

$div.setHTML(str)
// <div>hello <b><i>world</i></b></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: [ "b" ]}) })
// <div>hello <b>world</b></div>

$div.setHTML(str, { sanitizer: new Sanitizer({blockElements: [ "b" ]}) })
// <div>hello <i>world</i></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: []}) })
// <div>hello world</div>

आप यह भी नियंत्रित कर सकते हैं कि सैनिटाइज़र इन विकल्पों की मदद से किसी खास एट्रिब्यूट को अनुमति देगा या अस्वीकार करेगा:

  • allowAttributes
  • dropAttributes

allowAttributes और dropAttributes प्रॉपर्टी, एट्रिब्यूट के मैच की सूचियों की उम्मीद करती हैं—ऐसी ऑब्जेक्ट जिनकी कुंजियां, एट्रिब्यूट के नाम हैं और वैल्यू, टारगेट एलिमेंट या * वाइल्डकार्ड की सूचियां हैं.

const str = `<span id=foo class=bar style="color: red">hello</span>`

$div.setHTML(str)
// <div><span id="foo" class="bar" style="color: red">hello</span></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["span"]}}) })
// <div><span style="color: red">hello</span></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["p"]}}) })
// <div><span>hello</span></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["*"]}}) })
// <div><span style="color: red">hello</span></div>

$div.setHTML(str, { sanitizer: new Sanitizer({dropAttributes: {"id": ["span"]}}) })
// <div><span class="bar" style="color: red">hello</span></div>

$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {}}) })
// <div>hello</div>

allowCustomElements, कस्टम एलिमेंट को अनुमति देने या अस्वीकार करने का विकल्प है. अगर अनुमति दी गई है, तो एलिमेंट और एट्रिब्यूट के लिए दूसरे कॉन्फ़िगरेशन अब भी लागू होंगे.

const str = `<custom-elem>hello</custom-elem>`

$div.setHTML(str)
// <div></div>

const sanitizer = new Sanitizer({
  allowCustomElements: true,
  allowElements: ["div", "custom-elem"]
})
$div.setHTML(str, { sanitizer })
// <div><custom-elem>hello</custom-elem></div>

एपीआई प्लैटफ़ॉर्म

DomPurify से तुलना

DOMPurify एक जानी-मानी लाइब्रेरी है, जिसमें सैनिटाइज़ेशन फ़ंक्शन उपलब्ध हैं. Sanitizer API और DOMPurify के बीच मुख्य अंतर यह है कि DOMPurify, सैनिटाइज़ेशन के नतीजे को स्ट्रिंग के रूप में दिखाता है, जिसे आपको .innerHTML के ज़रिए DOM एलिमेंट में लिखना होता है.

const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
const sanitized = DOMPurify.sanitize(user_input)
$div.innerHTML = sanitized
// `<em>hello world</em><img src="">`

ब्राउज़र में Sanitizer API लागू न होने पर, DOMPurify का इस्तेमाल फ़ॉलबैक के तौर पर किया जा सकता है.

DOMPurify के लागू होने में कुछ समस्याएं हैं. अगर कोई स्ट्रिंग दिखती है, तो इनपुट स्ट्रिंग को DOMPurify और .innerHTML में दो बार पार्स किया जाता है. दो बार पार्स करने से प्रोसेस में लगने वाला समय बर्बाद होता है. हालांकि, दूसरी बार पार्स करने पर होने वाले नतीजे से अलग होने की वजह से जोखिम की आशंका भी पैदा हो सकती है.

एचटीएमएल को पार्स करने के लिए कॉन्टेक्स्ट भी होना ज़रूरी है. उदाहरण के लिए, <td> का मतलब <table> में है, लेकिन <div> में नहीं. DOMPurify.sanitize() सिर्फ़ स्ट्रिंग को तर्क के तौर पर लेता है, इसलिए पार्स करने के संदर्भ का अनुमान लगाना पड़ता है.

Sanitizer API को DOMPurify तरीके से बेहतर बनाया गया है. इसे दो बार पार्स करने की ज़रूरत को खत्म करने और पार्स करने के संदर्भ को साफ़ तौर पर समझाने के लिए डिज़ाइन किया गया है.

एपीआई की स्थिति और ब्राउज़र सहायता

सैनिटाइज़र एपीआई के बारे में मानक तय करने की प्रक्रिया चल रही है और Chrome इसे लागू करने की प्रोसेस में है.

चरण स्थिति
1. जानकारी देने वाला वीडियो बनाएं पूरा हुआ
2. स्पेसिफ़िकेशन का ड्राफ़्ट बनाएं पूरा हुआ
3. लोगों के सुझाव, राय या शिकायतें इकट्ठा करें और डिज़ाइन पर दोहराएं पूरा हुआ
4. Chrome के ऑरिजिन ट्रायल पूरा हुआ
5. लॉन्च करें M105 पर शिपिंग करने की इच्छा

Mozilla: इस प्रस्ताव को प्रोटोटाइप करना ज़रूरी है और हम इसे लागू कर रहे हैं.

WebKit: WebKit मेलिंग सूची पर उत्तर देखें.

सैनिटाइज़र एपीआई को चालू करने का तरीका

ब्राउज़र सहायता

  • Chrome: समर्थित नहीं. अभी तक किसी भी व्यक्ति ने चेक इन नहीं किया है
  • Edge: इस्तेमाल नहीं किया जा सकता. अभी तक किसी भी व्यक्ति ने चेक इन नहीं किया है
  • Firefox: किसी झंडे के पीछे.
  • Safari: समर्थित नहीं. अभी तक किसी भी व्यक्ति ने चेक इन नहीं किया है

सोर्स

about://flags या सीएलआई विकल्प के ज़रिए चालू किया जा रहा है

Chrome

Chrome, सैनिटाइज़र एपीआई को लागू कर रहा है. Chrome 93 या इसके बाद के वर्शन में, about://flags/#enable-experimental-web-platform-features फ़्लैग को चालू करके, इस सेटिंग को आज़माया जा सकता है. Chrome कैनरी और डेव चैनल के पुराने वर्शन में, आप इसे --enable-blink-features=SanitizerAPI की मदद से चालू कर सकते हैं और अभी आज़मा सकते हैं. Chrome को फ़्लैग करने का तरीका जानने के लिए निर्देश देखें.

Firefox

Firefox सैनिटाइज़र एपीआई को भी प्रयोग के तौर पर लागू करता है. इसे चालू करने के लिए, about:config में dom.security.sanitizer.enabled फ़्लैग को true पर सेट करें.

सुविधा की पहचान

if (window.Sanitizer) {
  // Sanitizer API is enabled
}

सुझाव/राय दें या शिकायत करें

अगर आपने इस एपीआई को आज़माया है और आपके पास कोई सुझाव, शिकायत या राय है, तो हमें ज़रूर बताएं. Sanitizer API GitHub से जुड़ी समस्याओं के बारे में अपनी राय दें. साथ ही, इस एपीआई में दिलचस्पी रखने वाले खास लेखकों और लोगों से चर्चा करें.

अगर आपको Chrome को लागू करने के दौरान कोई गड़बड़ी या अनचाहा व्यवहार दिखता है, तो उसकी शिकायत करने के लिए गड़बड़ी की शिकायत करें. Blink>SecurityFeature>SanitizerAPI कॉम्पोनेंट चुनें और इसकी जानकारी शेयर करें, ताकि लागू करने वाले लोग समस्या को ट्रैक कर सकें.

डेमो

सैनिटाइज़र एपीआई को इस्तेमाल करने का तरीका देखने के लिए, माइक वेस्ट का Sanitizer API प्लेग्राउंड देखें:

रेफ़रंस


Unस्प्लैश पर टॉफ़िक़ बारभुइया की फ़ोटो.