ব্যবহারকারীর তৈরি করা HTML নিরাপদে দেখানো একটি সাধারণ নিরাপত্তা সংক্রান্ত চ্যালেঞ্জ। নেটিভ Sanitizer API বিপজ্জনক কন্টেন্ট (যেমন <script> ট্যাগ বা on* অ্যাট্রিবিউট) DOM-এ পৌঁছানোর আগেই তা সরিয়ে দেওয়ার জন্য একটি বিল্ট-ইন, ব্রাউজার-অপ্টিমাইজ করা উপায় প্রদান করে।
ক্লায়েন্ট-সাইড স্যানিটাইজেশন সেই পৃষ্ঠা সুরক্ষিত রাখে যা কন্টেন্ট রেন্ডার করে। একই ব্যবহারকারীর তৈরি করা কন্টেন্ট অন্য কোথাও (অন্য ক্লায়েন্ট, সার্ভার-রেন্ডার করা পৃষ্ঠা বা ইমেল) রেন্ডার করা হলে, সার্ভারেও এটি স্যানিটাইজ করুন এবং নিশ্চিত করুন যে সার্ভার ও ক্লায়েন্ট স্যানিটাইজেশন নিয়মের মধ্যে কোনও বিরোধ নেই।
১. setHTML()-এর মাধ্যমে নিরাপদে HTML যোগ করা
Sanitizer API ব্যবহার করার প্রাথমিক উপায় হল যেকোনও Element-এ setHTML() পদ্ধতি ব্যবহার করা। এই পদ্ধতিটি একটি ধাপে পার্সিং, স্যানিটাইজেশন এবং ইনসার্ট করার কাজ করে, এটি নিশ্চিত করে যে কন্টেন্টটি বিশেষভাবে সেই প্রসঙ্গের জন্য স্যানিটাইজ করা হয়েছে যেখানে এটি ইনসার্ট করা হবে।
const untrustedHTML = `
<p>Hello!</p>
<script>alert('XSS')</script>
<img src="x" onerror="alert('XSS')">
<div onclick="doBadThing()">Click me</div>
`;
const container = document.getElementById('output');
// Sanitize and insert in one call
// The default sanitizer removes scripts and event handlers, plus other
// elements and attributes outside its built-in allowlist (such as style)
container.setHTML(untrustedHTML);
২. কাস্টম স্যানিটাইজার কনফিগারেশন ব্যবহার করা হচ্ছে
আপনি Sanitizer ইনস্ট্যান্স তৈরি করে স্যানিটাইজেশন সংক্রান্ত নিয়ম কাস্টমাইজ করতে পারবেন। এটি আপনাকে নির্দিষ্ট ব্যবহারের ক্ষেত্রে কোন কোন এলিমেন্ট এবং অ্যাট্রিবিউট অনুমোদিত তা সঠিকভাবে নির্ধারণ করতে দেয়।
Element.setHTML() এবং Document.parseHTML() ব্যবহার করার সময়, ডিফল্ট Sanitizer সেটিংস সব XSS-অসুরক্ষিত এলিমেন্ট এবং অ্যাট্রিবিউট সরিয়ে দেয়। আপনি আরও বেশি অনুমতি দেয় এমন Sanitizer তৈরি করতে পারবেন না যা এইসব এলিমেন্টকে অনুমতি দেয়, তবে কী কী অনুমতি দেওয়া হয়েছে তা আপনি আরও সীমাবদ্ধ করতে পারবেন।
// Define a restrictive configuration that only allows the specified elements and attributes.
const config = {
elements: ["p", "b", "i", "strong", "em"],
attributes: ["class"],
replaceWithChildrenElements: ['div']
};
const mySanitizer = new Sanitizer(config);
// Apply custom sanitization
container.setHTML(untrustedHTML, { sanitizer: mySanitizer });
elements-এ নেই এমন এলিমেন্ট সেগুলির কন্টেন্টের সাথে সরিয়ে দেওয়া হয়। replaceWithChildrenElements-এ আপনি যেসব র্যাপার এলিমেন্টের টেক্সট রাখতে চান (যেমন উপরে div) সেগুলি তালিকাভুক্ত করুন, এটি এলিমেন্ট সরিয়ে দেয় কিন্তু তার চাইল্ড এলিমেন্ট রেখে দেয়।
আপনার যদি আরও বেশি অনুমতি দেয় এমন Sanitizer-এর প্রয়োজন হয় যা কিছু XSS-এর জন্য অসুরক্ষিত এলিমেন্ট বা অ্যাট্রিবিউটকে অনুমতি দেয়, তাহলে আপনি Element.setHTMLUnsafe() এবং Document.parseHTMLUnsafe()-এর সাথে কাস্টম স্যানিটাইজার ব্যবহার করতে পারেন।
৩. Document.parseHTML()-এর মাধ্যমে ব্যবহারকারীর ইনপুট পার্স করা হচ্ছে
লাইভ DOM-এ অবিলম্বে যোগ না করে আপনাকে যদি বিশ্বস্ত নয় এমন HTML পার্স করতে হয়, তাহলে Document.parseHTML() ব্যবহার করুন। এই পদ্ধতিটি একটি Document প্রদান করে যা ডিফল্ট বা কাস্টম নিয়ম অনুযায়ী স্যানিটাইজ করা হয়েছে, যাতে আপনি স্যানিটাইজ করা নোডগুলি (উদাহরণস্বরূপ, একটি প্রিভিউ তৈরি করতে বা টেক্সট এক্সট্র্যাক্ট করতে) সন্নিবেশ করার আগে পরিদর্শন বা রূপান্তর করতে পারেন।
ফলাফল হিসেবে পাওয়া নোডগুলিকে স্ট্রিংয়ে সিরিয়ালাইজ করে innerHTML-এর মাধ্যমে আবার পার্স করার পরিবর্তে পৃষ্ঠায় সরান, যা মিউটেশন XSS (mXSS) আবার চালু করতে পারে।
const rawHTML = '<p>Hello <script>console.log("bad")</script></p>';
const doc = Document.parseHTML(rawHTML);
// doc.body now contains the sanitized nodes: <p>Hello </p>
preview.replaceChildren(...doc.body.childNodes);
Sanitizer API-এর জন্য ফলব্যাক ও ব্রাউজার সহায়তা
আপনার টার্গেট ব্রাউজারে নেটিভ স্যানিটাইজার API উপলভ্য না থাকলে, শুধুমাত্র নেটিভ সাপোর্ট না থাকলে DOMPurify-এর মতো লাইব্রেরি লোড করুন, যাতে এটি এমন ব্রাউজারে শিপিং করা এড়ানো যায় যেগুলির প্রয়োজন নেই। ফিচার শনাক্তকরণের জন্য in চেক ব্যবহার করুন যাতে চেকটি নিজেই পুরনো ব্রাউজারে কাজ করে।
let domPurifyPromise;
function loadDOMPurify() {
// Code-split with your bundler so DOMPurify is only downloaded when needed
domPurifyPromise ??= import('dompurify').then((mod) => mod.default ?? mod);
return domPurifyPromise;
}
export async function safeSetHTML(el, html) {
const elements = ['p', 'b', 'i', 'strong', 'em'];
const attributes = ['class'];
if ('setHTML' in Element.prototype) {
// setHTML() accepts a plain configuration object as the sanitizer
el.setHTML(html, { sanitizer: { elements, attributes } });
} else {
const DOMPurify = await loadDOMPurify();
el.innerHTML = DOMPurify.sanitize(html, {
ALLOWED_TAGS: [...elements, '#text'],
ALLOWED_ATTR: attributes,
// Match the native Sanitizer: strip aria-*/data-* and disallowed element content
ALLOW_ARIA_ATTR: false,
ALLOW_DATA_ATTR: false,
KEEP_CONTENT: false
});
}
}
ডিফল্ট হিসেবে, DOMPurify aria-* এবং data-* অ্যাট্রিবিউট (এমনকি ALLOWED_ATTR নির্দিষ্ট করা থাকলেও) এবং এটি সরিয়ে দেয় এমন এলিমেন্টের টেক্সট কন্টেন্ট রেখে দেয়, যেখানে নেটিভ স্যানিটাইজার aria-* এবং data-* অ্যাট্রিবিউট স্ট্রিপ করে এবং অননুমোদিত এলিমেন্ট ও তার কন্টেন্ট একসাথে সরিয়ে দেয়। ALLOW_ARIA_ATTR: false, ALLOW_DATA_ATTR: false, KEEP_CONTENT: false সেট করা এবং #text-কে অনুমতি দেওয়ার ফলে DOMPurify নেটিভ আচরণের সাথে ম্যাচ করে। DOMPurify-তে replaceWithChildrenElements-এর সমতুল্য কোনও বিল্ট-ইন বিকল্প নেই, তাই কনফিগারেশনে সেই বিকল্প এড়িয়ে চলুন যার জন্য ম্যাচিং ফলব্যাক প্রয়োজন।
পৃষ্ঠাটি বিশ্বস্ত প্রকার (require-trusted-types-for 'script') এনফোর্স করলে, innerHTML-এ একটি স্ট্রিং অ্যাসাইন করলে থ্রো করে। setHTML()-এর জন্য বিশ্বস্ত প্রকারের নীতি প্রয়োজন নেই, তবে ফলব্যাকের জন্য প্রয়োজন: RETURN_TRUSTED_TYPE: true-কে DOMPurify.sanitize()-এ পাস করুন এবং আপনার trusted-types CSP ডাইরেক্টিভে এর dompurify নীতির নাম অনুমোদন করুন।