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

Sanitizer API के नए वर्शन का मकसद, किसी भी स्ट्रिंग को पेज में सुरक्षित तरीके से डालने के लिए, एक बेहतर प्रोसेसर बनाना है.

Jack J
Jack J

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

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

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

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

डीओएम में उपयोगकर्ता इनपुट, क्वेरी स्ट्रिंग, कुकी कॉन्टेंट वगैरह डालते समय, स्ट्रिंग को सही तरीके से एस्केप करना ज़रूरी है. .innerHTML की मदद से, DOM में बदलाव करने पर खास ध्यान दिया जाना चाहिए. यहां बिना एस्केप की गई स्ट्रिंग, 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 के लिए सुझाई गई खास बातें का मकसद, ब्राउज़र के लिए स्टैंडर्ड एपीआई के तौर पर ऐसी प्रोसेसिंग उपलब्ध कराना है.

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

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>). पार्सिंग को अंदरूनी तौर पर एक बार किया जाता है और नतीजे को सीधे तौर पर DOM में बड़ा किया जाता है.

डेटा को सुरक्षित करने के बाद, उसे स्ट्रिंग के तौर पर देखने के लिए, 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 तरीके से बेहतर बनाया गया है. इसे दो बार पार्स करने की ज़रूरत को खत्म करने और पार्स करने के संदर्भ को साफ़ तौर पर समझाने के लिए डिज़ाइन किया गया है.

एपीआई का स्टेटस और ब्राउज़र के साथ काम करना

Sanitizer API को स्टैंडर्ड बनाने की प्रक्रिया में चर्चा की जा रही है. Chrome इसे लागू करने की प्रक्रिया में है.

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

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

WebKit: WebKit की मेलिंग सूची पर जवाब देखें.

Sanitizer API को चालू करने का तरीका

about://flags या सीएलआई विकल्प की मदद से चालू करना

Chrome

Chrome, Sanitizer API को लागू करने की प्रोसेस में है. Chrome 93 या उसके बाद के वर्शन में, about://flags/#enable-experimental-web-platform-features फ़्लैग को चालू करके, इस सुविधा को आज़माया जा सकता है. Chrome Canary और डेवलपर चैनल के पुराने वर्शन में, --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 को काम करते हुए देखने के लिए, माइक वेस्ट का Sanitizer API Playground देखें:

रेफ़रंस


Unsplash पर Towfiqu barbhuiya की फ़ोटो.