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

Jack J
Jack J

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

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

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

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

उपयोगकर्ता के इनपुट, क्वेरी स्ट्रिंग, कुकी के कॉन्टेंट वगैरह को DOM में डालते समय, स्ट्रिंग को सही तरीके से एस्केप किया जाना चाहिए. .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.

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

रेफ़रंस