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

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> को बिना बदले, DOM में इसे सुरक्षित तरीके से बड़ा किया जा सकता है.

// 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

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="">

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

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

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 mailing list पर जवाब देखें.

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

Browser Support

  • Chrome: 146.
  • Edge: 146.
  • Firefox: 148.
  • Safari: not supported.

Source

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

Firefox

Firefox, Sanitizer API को एक्सपेरिमेंटल सुविधा के तौर पर लागू करता है. इसे चालू करने के लिए, about:config में dom.security.sanitizer.enabled फ़्लैग को true पर सेट करें.

सुविधा का पता लगाना

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

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

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

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

डेमो

Sanitizer API को काम करते हुए देखने के लिए, Mike West का Sanitizer API Playground देखें:

रेफ़रंस