اسکریپتنویسی بینسایتی مبتنی بر DOM (DOM XSS) زمانی اتفاق میافتد که دادهها از یک منبع تحت کنترل کاربر (مانند نام کاربری یا یک URL ریدایرکت گرفته شده از قطعه URL) به یک sink میرسند، که تابعی مانند eval() یا یک تنظیمکننده ویژگی مانند .innerHTML است که میتواند کد جاوا اسکریپت دلخواه را اجرا کند.
DOM XSS یکی از رایجترین آسیبپذیریهای امنیتی وب است و معمولاً تیمهای توسعهدهنده آن را بهطور تصادفی در برنامههای خود معرفی میکنند. Trusted Types با ایمنسازی پیشفرض توابع خطرناک API وب، ابزارهایی را برای نوشتن، بررسی امنیتی و دور نگه داشتن برنامهها از آسیبپذیریهای DOM XSS در اختیار شما قرار میدهد. Trusted Types بهعنوان یک polyfill برای مرورگرهایی که هنوز از آنها پشتیبانی نمیکنند، در دسترس هستند.
پیشینه
سالهاست که DOM XSS یکی از شایعترین و خطرناکترین آسیبپذیریهای امنیتی وب بوده است.
دو نوع اسکریپتنویسی بینسایتی وجود دارد. برخی از آسیبپذیریهای XSS توسط کد سمت سرور ایجاد میشوند که به طور ناامن کد HTML تشکیلدهنده وبسایت را ایجاد میکند. برخی دیگر ریشه در سمت کلاینت دارند، جایی که کد جاوا اسکریپت توابع خطرناکی را با محتوای کنترلشده توسط کاربر فراخوانی میکند.
برای جلوگیری از XSS سمت سرور ، HTML را با الحاق رشتهها تولید نکنید. در عوض از کتابخانههای قالببندی ایمن با قابلیت پوشش خودکار زمینهای، همراه با یک سیاست امنیتی محتوای مبتنی بر عدم قطعیت (nonce-based Content Security Policy) برای کاهش بیشتر اشکالات استفاده کنید.
اکنون مرورگرها میتوانند با استفاده از Trusted Types از XSS مبتنی بر DOM در سمت کلاینت نیز جلوگیری کنند.
معرفی API
انواع قابل اعتماد با قفل کردن توابع سینک پرخطر زیر کار میکنند. ممکن است برخی از آنها را از قبل بشناسید، زیرا فروشندگان مرورگر و چارچوبهای وب به دلایل امنیتی شما را از استفاده از این ویژگیها منع میکنند.
- دستکاری اسکریپت :
<script src>و تنظیم محتوای متن عناصر<script>. - تولید HTML از یک رشته :
- اجرای محتوای افزونه :
- کامپایل کد جاوا اسکریپت در زمان اجرا :
-
eval -
setTimeout -
setInterval -
new Function()
-
انواع قابل اعتماد (Trusted Types) شما را ملزم میکنند که قبل از ارسال دادهها به این توابع sink، آنها را پردازش کنید. استفادهی صرف از رشته، ناموفق است، زیرا مرورگر نمیداند که آیا دادهها قابل اعتماد هستند یا خیر:
anElement.innerHTML = location.href;
برای نشان دادن اینکه دادهها به طور ایمن پردازش شدهاند، یک شیء خاص - یک نوع مورد اعتماد - ایجاد کنید.
anElement.innerHTML = aTrustedHTML;
TrustedHTML را برای sinkهایی که انتظار قطعه کدهای HTML را دارند میپذیرد. همچنین اشیاء TrustedScript و TrustedScriptURL برای سایر sinkهای حساس وجود دارد.انواع قابل اعتماد (Trusted Types) به طور قابل توجهی سطح حمله DOM XSS برنامه شما را کاهش میدهند. این کار بررسیهای امنیتی را ساده میکند و به شما امکان میدهد بررسیهای امنیتی مبتنی بر نوع را که هنگام کامپایل، linting یا bundling کد شما در زمان اجرا، در مرورگر انجام میشود، اعمال کنید.
نحوه استفاده از انواع قابل اعتماد
آماده شدن برای گزارشهای نقض سیاست امنیت محتوا
شما میتوانید یک جمعکننده گزارش، مانند reporting-api-processor متنباز یا go-csp-collector ، مستقر کنید یا از یکی از معادلهای تجاری آن استفاده کنید. همچنین میتوانید با استفاده از ReportingObserver ، گزارشگیری سفارشی و نقضهای اشکالزدایی را در مرورگر اضافه کنید:
const observer = new ReportingObserver((reports, observer) => {
for (const report of reports) {
if (report.type !== 'csp-violation' ||
report.body.effectiveDirective !== 'require-trusted-types-for') {
continue;
}
const violation = report.body;
console.log('Trusted Types Violation:', violation);
// ... (rest of your logging and reporting logic)
}
}, { buffered: true });
observer.observe();
یا با اضافه کردن یک شنونده رویداد:
document.addEventListener('securitypolicyviolation',
console.error.bind(console));
یک هدر CSP فقط برای گزارش اضافه کنید
هدر HTTP Response زیر را به اسنادی که میخواهید به Trusted Types منتقل کنید، اضافه کنید:
Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
اکنون تمام تخلفات به //my-csp-endpoint.example گزارش میشوند، اما وبسایت همچنان به کار خود ادامه میدهد. بخش بعدی نحوهی کار //my-csp-endpoint.example را توضیح میدهد.
شناسایی تخلفات مربوط به انواع قابل اعتماد
از این پس، هر بار که Trusted Types تخلفی را تشخیص دهد، مرورگر گزارشی را به report-uri پیکربندی شده ارسال میکند. برای مثال، وقتی برنامه شما رشتهای را به innerHTML ارسال میکند، مرورگر گزارش زیر را ارسال میکند:
{
"csp-report": {
"document-uri": "https://my.url.example",
"violated-directive": "require-trusted-types-for",
"disposition": "report",
"blocked-uri": "trusted-types-sink",
"line-number": 39,
"column-number": 12,
"source-file": "https://my.url.example/script.js",
"status-code": 0,
"script-sample": "Element innerHTML <img src=x"
}
}
این نشان میدهد که در https://my.url.example/script.js در خط ۳۹، innerHTML با رشتهای که با <img src=x شروع میشود، فراخوانی شده است. این اطلاعات باید به شما کمک کند تا بخشهایی از کد را که ممکن است باعث DOM XSS شوند و نیاز به تغییر دارند، محدود کنید.
تخلفات را برطرف کنید
چند گزینه برای رفع تخلف Trusted Type وجود دارد. میتوانید کد متخلف را حذف کنید ، از یک کتابخانه استفاده کنید ، یک سیاست Trusted Type ایجاد کنید یا به عنوان آخرین راه حل، یک سیاست پیشفرض ایجاد کنید .
کد مشکلساز را بازنویسی کنید
این امکان وجود دارد که کد نامنطبق دیگر مورد نیاز نباشد، یا بتوان آن را بدون توابعی که باعث نقض میشوند، بازنویسی کرد:
el.textContent = ''; const img = document.createElement('img'); img.src = 'xyz.jpg'; el.appendChild(img);
el.innerHTML = '<img src=xyz.jpg>';
از یک کتابخانه استفاده کنید
برخی از کتابخانهها از قبل انواع قابل اعتمادی (Trusted Types) تولید میکنند که میتوانید آنها را به توابع sink ارسال کنید. برای مثال، میتوانید از DOMPurify برای پاکسازی یک قطعه کد HTML و حذف کدهای مخرب XSS استفاده کنید.
import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});
DOMPurify از انواع قابل اعتماد (Trusted Types) پشتیبانی میکند و HTML پاکسازیشدهای را که در یک شیء TrustedHTML قرار گرفته است، برمیگرداند تا مرورگر تخلفی ایجاد نکند.
ایجاد یک سیاست نوع قابل اعتماد
گاهی اوقات نمیتوانید کدی که باعث نقض میشود را حذف کنید، و هیچ کتابخانهای برای پاکسازی مقدار و ایجاد یک نوع مورد اعتماد (Trusted Type) برای شما وجود ندارد. در این موارد، میتوانید خودتان یک شیء از نوع مورد اعتماد (Trusted Type) ایجاد کنید.
ابتدا، یک سیاست (policy) ایجاد کنید. سیاستها، کارخانههایی برای انواع قابل اعتماد (Trusted Types) هستند که قوانین امنیتی خاصی را بر روی ورودی خود اعمال میکنند:
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
createHTML: string => string.replace(/\</g, '<')
});
}
این کد یک سیاست به نام myEscapePolicy ایجاد میکند که میتواند با استفاده از تابع createHTML() خود، اشیاء TrustedHTML تولید کند. قوانین تعریف شده با کاراکترهای HTML-escape < از ایجاد عناصر HTML جدید جلوگیری میکنند.
از این پالیسی به این شکل استفاده کنید:
const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML); // true
el.innerHTML = escaped; // '<img src=x onerror=alert(1)>'
استفاده از یک سیاست پیشفرض
گاهی اوقات نمیتوانید کد مشکلساز را تغییر دهید، مثلاً اگر در حال بارگذاری یک کتابخانه شخص ثالث از یک CDN هستید. در این صورت، از یک سیاست پیشفرض استفاده کنید:
if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
trustedTypes.createPolicy('default', {
createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
});
}
سیاستی که default نامیده میشود، هر جا که رشتهای در یک سینک که فقط نوع مطمئن (Trusted Type) را میپذیرد، استفاده شود، استفاده میشود.
به اجرای سیاست امنیت محتوا بروید
وقتی برنامه شما دیگر تخلفی ایجاد نکرد، میتوانید شروع به اعمال انواع قابل اعتماد (Trusted Types) کنید:
Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example
حالا، مهم نیست که برنامه وب شما چقدر پیچیده باشد، تنها چیزی که میتواند آسیبپذیری DOM XSS را ایجاد کند، کد موجود در یکی از سیاستهای شماست و میتوانید با محدود کردن ایجاد سیاست ، آن را حتی بیشتر قفل کنید.