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

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

جک جی
Jack J

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

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

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

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

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

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

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

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

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

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

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

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

مثال

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

// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerror=alert(0)>`
// Sanitized ⛑
$div.innerHTML = `<em>hello world</em><img src="">`

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

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

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 گسترش می یابد.

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

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>

سطح API

مقایسه با 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 تجزیه می شود. این تجزیه مضاعف زمان پردازش را تلف می‌کند، اما همچنین می‌تواند منجر به آسیب‌پذیری‌های جالبی شود که ناشی از مواردی است که نتیجه تجزیه دوم با اولی متفاوت است.

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

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

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

Sanitizer API در فرآیند استانداردسازی در دست بحث است و Chrome در حال اجرای آن است.

مرحله وضعیت
1. توضیح دهنده ایجاد کنید کامل شود
2. پیش نویس مشخصات را ایجاد کنید کامل شود
3. جمع آوری بازخورد و تکرار در طراحی کامل شود
4. آزمایش اولیه کروم کامل شود
5. راه اندازی کنید قصد ارسال در M105

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

WebKit: پاسخ را در لیست پستی WebKit ببینید.

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

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

کروم

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

فایرفاکس

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

تشخیص ویژگی

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

بازخورد

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

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

نسخه ی نمایشی

برای دیدن Sanitizer API در عمل، Sanitizer API Playground توسط Mike West را بررسی کنید:

مراجع


عکس از Towfiqu barbhuiya در Unsplash .