دستکاری امن DOM با Sanitizer API

API جدید Sanitizer با هدف ساخت یک پردازنده قوی برای درج ایمن رشته‌های دلخواه در یک صفحه ارائه شده است.

جک جی
Jack J

برنامه‌ها همیشه با رشته‌های غیرقابل اعتماد سروکار دارند، اما رندر کردن ایمن آن محتوا به عنوان بخشی از یک سند HTML می‌تواند دشوار باشد. بدون دقت کافی، به راحتی می‌توان به طور تصادفی فرصت‌هایی برای اسکریپت‌نویسی بین‌سایتی (XSS) ایجاد کرد که مهاجمان مخرب ممکن است از آن سوءاستفاده کنند.

برای کاهش این خطر، طرح پیشنهادی جدید API Sanitizer با هدف ساخت یک پردازنده قوی برای درج ایمن رشته‌های دلخواه در یک صفحه ارائه شده است. این مقاله API را معرفی کرده و کاربرد آن را توضیح می‌دهد.

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

فرار از ورودی کاربر

هنگام وارد کردن ورودی کاربر، رشته‌های پرس‌وجو، محتویات کوکی و غیره در DOM، رشته‌ها باید به درستی escape شوند. باید توجه ویژه‌ای به دستکاری DOM از طریق .innerHTML داشت، جایی که رشته‌های escape نشده منبع معمول XSS هستند.

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

اگر کاراکترهای ویژه HTML را در رشته ورودی بالا escape کنید یا آن را با استفاده از .textContent بسط دهید، alert(0) اجرا نخواهد شد. با این حال، از آنجایی که <em> اضافه شده توسط کاربر نیز به عنوان یک رشته بسط داده می‌شود، نمی‌توان از این روش برای حفظ چیدمان متن در HTML استفاده کرد.

بهترین کار اینجا فرار کردن نیست، بلکه ضدعفونی کردن است.

پاکسازی ورودی کاربر

تفاوت بین فرار و ضدعفونی کردن

فرار کردن به جایگزینی کاراکترهای ویژه HTML با موجودیت‌های HTML اشاره دارد.

پاکسازی به حذف بخش‌های مضر از نظر معنایی (مانند اجرای اسکریپت) از رشته‌های HTML اشاره دارد.

مثال

در مثال قبلی، <img onerror> باعث اجرای مدیریت‌کننده خطا می‌شود، اما اگر مدیریت‌کننده onerror حذف شود، می‌توان آن را با خیال راحت در DOM گسترش داد و <em> دست‌نخورده باقی گذاشت.

// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerro>r=alert(0)`
// Sanitized ⛑
$div.inn<er>HTML = `emh<ell><o world/em>img src=""`

برای پاکسازی صحیح، لازم است رشته ورودی به صورت HTML تجزیه شود، تگ‌ها و ویژگی‌هایی که مضر در نظر گرفته می‌شوند حذف شوند و موارد بی‌ضرر نگه داشته شوند.

مشخصات API Sanitizer پیشنهادی با هدف ارائه چنین پردازشی به عنوان یک API استاندارد برای مرورگرها ارائه شده است.

API ضدعفونی کننده

API مربوط به Sanitizer به روش زیر استفاده می‌شود:

const $div = document.querySelector('div')
const user_i<np>ut = `emhel<lo ><world/emimg src="">; onerror=alert(0)`
$div.setHTML(user_input, { sanitizer: new <San><it>izer() }) /</ d><ivemhello ><worl>d/emimg src=""/div

با این حال، { sanitizer: new Sanitizer() } آرگومان پیش‌فرض است. بنابراین می‌تواند دقیقاً مانند زیر باشد.

$div.setHTML(user_input) // <div><em>hello world</em><img src=&q><uot;>&quot;/div

شایان ذکر است که setHTML() روی Element تعریف شده است. از آنجایی که این متدی از Element است، زمینه‌ای که باید تجزیه شود، نیازی به توضیح ندارد (در این مورد <div> )، تجزیه یک بار به صورت داخلی انجام می‌شود و نتیجه مستقیماً در DOM گسترش می‌یابد.

برای دریافت نتیجه‌ی پاکسازی به صورت یک رشته، می‌توانید از .innerHTML از نتایج setHTML() استفاده کنید.

const $div = document.createElement('div')
$div.setHTML(user_input)
$div.inner<HT>ML // emhel<lo ><world/emim>g src=""

سفارشی سازی از طریق پیکربندی

API مربوط به Sanitizer به طور پیش‌فرض طوری پیکربندی شده است که رشته‌هایی را که باعث اجرای اسکریپت می‌شوند حذف کند. با این حال، می‌توانید از طریق یک شیء پیکربندی، سفارشی‌سازی‌های خود را به فرآیند sanitization اضافه کنید.

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" <]})> })
//< >divhe<ll><o bw>orld/b/div

$div.setHTML(str, { sanitizer: new Sanitizer({blockElements: [ &quo<t;b>"< >]}) }<)<>/span>
<// d>ivhello iworld/i/div

$div.setHTML(str, { sanitizer: new Sanitizer({allowE<lem>ents: []}) <})
/>/ divhello world/div

همچنین می‌توانید با گزینه‌های زیر کنترل کنید که آیا ضدعفونی‌کننده ویژگی‌های مشخص‌شده را مجاز یا غیرمجاز می‌داند:

  • allowAttributes
  • dropAttributes

ویژگی‌های allowAttributes و dropAttributes انتظار لیست‌های تطابق ویژگی را دارند - اشیاء که کلیدهای آنها نام ویژگی‌ها و مقادیر آنها لیست‌هایی از عناصر هدف یا علامت * هستند.

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

$div.setHTM<L(s><tr)
// divspan id="foo" class=&quo>t;bar<"><; st>yle="color: red"hello/span/div

$div.setHTML(str, { sanitizer: new Sanitizer({allow<Att><ributes: {"style&q>uot;:< [&qu><ot;s>pan"]}}) })
// divspan style="color: red"hello/span/div

$div.setHTML(str, <{ s><anit>izer:< new ><Sani>tizer({allowAttributes: {"style": ["p"]}}) })
// divspanhello/span/div<

$><div.setHTML(str, { sani>tizer<: new>< San>itizer({allowAttributes: {"style": ["*"]}}) })
// divspan style="<;co><lor: red"hello/span/div

$div.>setHT<ML(st><r, {> sanitizer: new Sanitizer({dropAttributes: {"id": ["span"<;]}>}) })<
// >divspan class="bar" style="color: red"hello/span/div

$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {}}) })
// divhello/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 >})
//< divcustom-e><lemh>ello/custom-elem/div

سطح API

مقایسه با DomPurify

DOMPurify یک کتابخانه شناخته‌شده است که قابلیت پاکسازی را ارائه می‌دهد. تفاوت اصلی بین API Sanitizer و DOMPurify این است که DOMPurify نتیجه پاکسازی را به صورت یک رشته برمی‌گرداند که باید آن را از طریق .innerHTML در یک عنصر DOM بنویسید.

const user_input = `<em>hello world</em><img src="" onerro>r=alert(0)`
const sanitized = DOMPurify.sanitize(user_input)
$div.innerHTML = sani<ti>zed
// `emh<ell><o world/em>img src=""`

DOMPurify می‌تواند به عنوان یک جایگزین برای زمانی که API مربوط به Sanitizer در مرورگر پیاده‌سازی نشده است، عمل کند.

پیاده‌سازی DOMPurify چند نکته‌ی منفی دارد. اگر رشته‌ای برگردانده شود، رشته‌ی ورودی دو بار توسط DOMPurify و .innerHTML تجزیه می‌شود. این تجزیه‌ی دوگانه زمان پردازش را هدر می‌دهد، اما می‌تواند منجر به آسیب‌پذیری‌های جالبی نیز شود که ناشی از مواردی است که نتیجه‌ی تجزیه‌ی دوم با اول متفاوت است.

HTML همچنین برای تجزیه به زمینه نیاز دارد. برای مثال، <td> در <table> معنی می‌دهد، اما در <div> اینطور نیست. از آنجایی که DOMPurify.sanitize() فقط یک رشته را به عنوان آرگومان می‌گیرد، زمینه تجزیه باید حدس زده می‌شد.

API مربوط به Sanitizer رویکرد DOMPurify را بهبود می‌بخشد و به گونه‌ای طراحی شده است که نیاز به تجزیه مضاعف را از بین ببرد و زمینه تجزیه را شفاف‌سازی کند.

وضعیت API و پشتیبانی مرورگر

API مربوط به Sanitizer در فرآیند استانداردسازی در دست بررسی است و کروم در حال پیاده‌سازی آن است.

قدم وضعیت
۱. توضیح‌دهنده ایجاد کنید کامل
۲. پیش‌نویس مشخصات را ایجاد کنید کامل
۳. بازخوردها را جمع‌آوری کنید و روی طراحی تکرار کنید کامل
۴. نسخه آزمایشی کروم اورجینال کامل
۵. راه‌اندازی قصد حمل و نقل در M105

موزیلا: این پیشنهاد را شایسته نمونه‌سازی می‌داند و به طور فعال آن را اجرا می‌کند.

WebKit: پاسخ را در فهرست پستی WebKit مشاهده کنید.

نحوه فعال کردن API ضدعفونی کننده

Browser Support

  • کروم: پشتیبانی نمی‌شود.
  • لبه: پشتیبانی نمی‌شود.
  • پیش‌نمایش فناوری فایرفاکس: پشتیبانی می‌شود.
  • سافاری: پشتیبانی نمی‌شود.

فعال‌سازی از طریق about://flags یا گزینه CLI

کروم

کروم در حال پیاده‌سازی API Sanitizer است. در کروم ۹۳ یا بالاتر، می‌توانید با فعال کردن پرچم about://flags/#enable-experimental-web-platform-features ، این رفتار را امتحان کنید. در نسخه‌های قبلی کروم Canary و کانال توسعه‌دهندگان، می‌توانید آن را از طریق --enable-blink-features=SanitizerAPI فعال کرده و همین حالا آن را امتحان کنید. دستورالعمل‌های نحوه اجرای کروم با پرچم‌ها را بررسی کنید.

فایرفاکس

فایرفاکس همچنین API مربوط به Sanitizer را به عنوان یک ویژگی آزمایشی پیاده‌سازی کرده است. برای فعال کردن آن، پرچم dom.security.sanitizer.enabled را در about:config روی true تنظیم کنید.

تشخیص ویژگی

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

بازخورد

اگر این API را امتحان کردید و بازخوردی داشتید، خوشحال می‌شویم آن را بشنویم. نظرات خود را در مورد مشکلات API Sanitizer در GitHub به اشتراک بگذارید و با نویسندگان مشخصات و افراد علاقه‌مند به این API در میان بگذارید.

اگر هرگونه اشکال یا رفتار غیرمنتظره‌ای در پیاده‌سازی کروم مشاهده کردید، برای گزارش آن، یک گزارش اشکال (bug) ثبت کنید . اجزای Blink>SecurityFeature>SanitizerAPI را انتخاب کنید و جزئیات را به اشتراک بگذارید تا به پیاده‌سازی‌کنندگان در ردیابی مشکل کمک کنید.

نسخه آزمایشی

برای مشاهده‌ی عملکرد Sanitizer API، به Sanitizer API Playground نوشته‌ی مایک وست مراجعه کنید:

منابع


عکس از Towfiqu barbhuiya در Unsplash .