جلوگیری از حملات XSS مبتنی بر DOM با «انواع مطمئن»

ویژگی‌های وب: انواع مطمئن

«انواع مطمئن» یک ویژگی امنیتی است که با الزام تبدیل داده‌های منتقل‌شده به میاناهای برنامه‌سازی کاربردی «خطرناک» مرورگر (معروف به سینک) به نوعی خاص، مانند TrustedHTML، به جلوگیری از «دستورگان‌نویسی بین‌سایتی مبتنی بر DOM»‏ (DOM XSS) کمک می‌کند.

به‌طور پیش‌فرض، بسیاری از میاناهای برنامه‌سازی کاربردی DOM (مانند innerHTML یا document.write) رشته‌های خام را می‌پذیرند. اگر رشته‌ای حاوی دستورگان مخرب از منبعی غیرقابل‌اعتماد باشد، می‌تواند اجرا شود. Trusted Types این رفتار را تغییر می‌دهد: پس‌از فعال شدن ازطریق CSP، این سینک‌ها فقط اشیاء TrustedHTML ایجادشده توسط خط‌مشی‌های مجاز را می‌پذیرند.

جزئیات کلیدی پیاده‌سازی

برای استفاده از Trusted Types، چهار مرحله اصلی را دنبال کنید:

  1. خط‌مشی امنیتی‌ای تعریف کنید که نحوه پاک‌سازی یا تبدیل رشته‌ها را مشخص کند.
  2. از آن خط‌مشی برای ایجاد اشیای Trusted Type از رشته‌های خام استفاده کنید.
  3. هنگام نوشتن در سینک‌های DOM، از اشیای Trusted Type استفاده کنید.
  4. بااستفاده از «خط‌مشی امنیتی محتوا» (CSP)، «انواع مطمئن» را اعمال کنید.

۱. درحال ایجاد خط‌مشی

خط‌مشی مجموعه‌ای از قوانین برای پاک‌سازی محتوا است. آن را بااستفاده از trustedTypes.createPolicy() ایجاد می‌کنید، معمولاً به کتابخانه پاک‌سازی مانند DOMPurify یا قوانین گریز HTML برنامه‌تان واگذار می‌شود:

import DOMPurify from 'dompurify';

const myPolicy = trustedTypes.createPolicy('sanitize-html', {
  createHTML: (input) =>
    // Return a sanitized string; createPolicy() wraps it in a TrustedHTML object.
    // Example using DOMPurify:
    DOMPurify.sanitize(input, { RETURN_TRUSTED_TYPE: false }),
});

// Example using HTML escaping (for environments without third-party sanitizers):
const escapePolicy = trustedTypes.createPolicy('escape-html', {
  createHTML: (input) =>
    String(input)
      .replaceAll('&', '&')
      .replaceAll('<', '&lt;')
      .replaceAll('>', '&gt;')
      .replaceAll('"', '&quot;')
      .replaceAll("'", '&#39;'),
});

۲. استفاده از خط‌مشی

به‌جای ارسال مستقیم رشته به سینک، روش ایجاد خط‌مشی را فراخوانی می‌کنید.

const untrustedInput = '<p>Hello, <strong>world</strong>!</p><img src="x" onerror="alert(1)">';

// In an enforced environment, this would throw a TypeError:
// element.innerHTML = untrustedInput;

// This returns a TrustedHTML object with unsafe elements and attributes removed:
const cleanHTML = myPolicy.createHTML(untrustedInput);
// Result: "<p>Hello, <strong>world</strong>!</p><img src=\"x\">"

۳. انتقال سینک‌ها برای استفاده از «انواع مطمئن»

هر نمونه‌ای را که در آن به سینک می‌نویسید به‌روز کنید تا ابتدا آن را به شیء «نوع مطمئن» تبدیل کنید. برای کمک به پیدا کردن جایی که کد باید به‌روز شود، می‌توانید خط‌مشی موقت default را تعریف کنید که وقتی سینک با رشته‌ای به‌جای TrustedType نوشته می‌شود، ثبت می‌کند.

trustedTypes.createPolicy("default", {
  createHTML(value) {
    console.warn("String passed instead of Trusted Type.");
    return DOMPurify.sanitize(value, { RETURN_TRUSTED_TYPE: false });
  },
});

سینک‌های رایجی که از «انواع مطمئن» پشتیبانی می‌کنند عبارت‌اند از:

  • HTML: element.innerHTML, element.outerHTML, document.write(), element.setHTMLUnsafe(), Document.parseHTMLUnsafe()
  • متن: <script> محتوای نوشتاری، eval()
  • ScriptURL: <script src>، Worker()، SharedWorker()
// Assign the TrustedHTML object.
element.innerHTML = cleanHTML;

توجه داشته باشید که روش‌های پاک‌سازی HTML «ایمن» (element.setHTML() و Document.parseHTML()، که در پاک‌سازی HTML غیرقابل‌اعتماد پوشش داده شده‌اند)، به خط‌مشی «انواع مطمئن» نیاز ندارند، زیرا همیشه حملات احتمالی XSS را به‌طور بومی پاک‌سازی می‌کنند.

۴. اجرای «انواع مطمئن» با CSP

«انواع مورداعتماد» باید به‌صورت افزایشی عرضه شود تا از خراب شدن برنامه‌تان جلوگیری شود. درحالی‌که اجرای کامل قوی‌ترین محافظت را ارائه می‌دهد، اگر همه نوشتن‌های سینک منتقل نشده باشند، خطر خرابی سایت وجود دارد.

پیش‌نیازها

  • همکاری چارچوب: اگر چارچوب برنامه شما (یا هر کتابخانه شخص ثالثی که در سینک‌های DOM می‌نویسد) مقادیر TrustedHTML یا TrustedScript تولید نمی‌کند، نمی‌توانید «انواع مطمئن» را بدون شکستن آن کد اعمال کنید. قبل‌از شروع عرضه، از چارچوب ممیزی پشتیبانی کنید.
  • بازسازی کد سینک: مطمئن شوید که سینک‌های خطرناک را شناسایی و بازسازی کرده‌اید تا از خط‌مشی‌های Trusted Types شما استفاده کنند.

استراتژی عرضه

  1. فقط گزارش‌دهی: ابتدا، بااستفاده از سرایند Content-Security-Policy-Report-Only عرضه کنید. این کار موارد نقض را بدون مسدود کردن واقعی نوشتن‌ها در نقطه پایانی گزارش‌دهی شما ثبت می‌کند.

    Content-Security-Policy-Report-Only: require-trusted-types-for 'script';
    
  2. اجرا: وقتی خط‌مشی‌هایتان را ادغام کردید و گزارش‌های نقض به صفر رسید، به اجرای کامل بروید.

    Content-Security-Policy: require-trusted-types-for 'script';
    

همچنین می‌توانید محدود کنید که کدام خط‌مشی‌ها مجاز به ایجاد شدن هستند (نام خط‌مشی‌هایتان را مانند sanitize-html یا escape-html فهرست کنید، اگر خط‌مشی شما به DOMPurify.sanitize() واگذار می‌شود dompurify را اضافه کنید—که به‌طور داخلی خط‌مشی dompurify ایجاد می‌کند—و اگر خط‌مشی پیش‌فرضی را درطول انتقال ثبت می‌کنید default را اضافه کنید):

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types sanitize-html escape-html dompurify;

با وجود این CSP، هرگونه نوشتن در سینک که از Trusted Types استفاده نکند، خطا ایجاد می‌کند. علاوه‌براین، تلاش برای ایجاد خط‌مشی «نوع مطمئن» با نامی که در trusted-types فهرست نشده است، خطا ایجاد می‌کند.

همچنین می‌توانید این CSP را به‌عنوان برچسب <meta> در <head> سندتان تنظیم کنید، هرچند این کار ممکن است امنیت کمتری داشته باشد.

<!-- First child of `<head>`, as earlier tags will not enforce this CSP. -->
<meta
  http-equiv="Content-Security-Policy"
  content="require-trusted-types-for 'script'; trusted-types sanitize-html escape-html dompurify;"
/>

راهبردهای جایگزین

برای مرورگرهایی که هنوز از Trusted Types پشتیبانی نمی‌کنند، می‌توانید از یک «tinyfill» حداقلی استفاده کنید تا مطمئن شوید کد شما خراب نمی‌شود و درعین‌حال پاک‌سازی را ارائه می‌دهد.

if (!window.trustedTypes) {
  window.trustedTypes = {
    createPolicy: (name, rules) => rules,
  };
}

این کار به شما امکان می‌دهد امروز کدتان را بااستفاده از الگوی Trusted Types بنویسید. مرورگرهای پشتیبانی‌کننده انواع را اعمال خواهند کرد، درحالی‌که مرورگرهای قدیمی‌تر همچنان منطق پاک‌سازی شما را ازطریق خط‌مشی ساختگی اجرا خواهند کرد.

اگر مرورگری از Trusted Types پشتیبانی نکند، به‌سادگی با اشیاء TrustedHTML به‌عنوان رشته رفتار خواهد کرد (اگر از tinyfill استفاده شود) یا بررسی اجرایی را در سطح سینک نخواهد داشت.