ป้องกันการโจมตี XSS ที่อิงตาม DOM ด้วย Trusted Types

ฟีเจอร์บนเว็บ: ประเภทที่เชื่อถือได้

Trusted Types เป็นฟีเจอร์ความปลอดภัยที่ช่วยป้องกัน Cross-Site Scripting ที่อิงตาม DOM (DOM XSS) โดยกำหนดให้ข้อมูลที่ส่งไปยัง API ของเบราว์เซอร์ที่ "เป็นอันตราย" (หรือที่เรียกว่าซิงก์) ต้องแปลงเป็นประเภทที่เฉพาะเจาะจงก่อน เช่น TrustedHTML

โดยค่าเริ่มต้น DOM API จำนวนมาก (เช่น innerHTML หรือ document.write) จะยอมรับสตริงดิบ หากสตริงมีสคริปต์ที่เป็นอันตรายจากแหล่งที่มาที่ไม่น่าเชื่อถือ ระบบจะเรียกใช้สคริปต์ดังกล่าวได้ Trusted Types จะเปลี่ยนลักษณะการทำงานนี้ โดยเมื่อเปิดใช้ผ่าน CSP แล้ว ซิงก์เหล่านี้จะยอมรับเฉพาะออบเจ็กต์ TrustedHTML ที่สร้างโดยนโยบายที่ได้รับอนุญาตเท่านั้น

รายละเอียดการติดตั้งใช้งานที่สำคัญ

หากต้องการใช้ Trusted Types ให้ทำตามขั้นตอนหลัก 4 ขั้นตอนต่อไปนี้

  1. กำหนดนโยบายความปลอดภัยที่ระบุวิธีทำความสะอาดหรือเปลี่ยนรูปแบบสตริง
  2. ใช้นโยบายดังกล่าวเพื่อสร้างออบเจ็กต์ประเภทที่เชื่อถือได้จากสตริงดิบ
  3. ใช้ออบเจ็กต์ประเภทที่เชื่อถือได้เมื่อเขียนไปยังซิงก์ DOM
  4. บังคับใช้ประเภทที่เชื่อถือได้โดยใช้นโยบายรักษาความปลอดภัยเนื้อหา (CSP)

1. การสร้างนโยบาย

นโยบายคือชุดกฎสำหรับการล้างข้อมูลเนื้อหา คุณสร้างขึ้นโดยใช้ 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;'),
});

2. การใช้นโยบาย

คุณเรียกใช้เมธอดการสร้างของนโยบายแทนการส่งสตริงไปยังซิงก์โดยตรง

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\">"

3. การเปลี่ยนผ่านซิงก์เพื่อใช้ Trusted Types

อัปเดตแต่ละอินสแตนซ์ที่คุณเขียนไปยังซิงก์เพื่อแปลงเป็นออบเจ็กต์ประเภทที่เชื่อถือได้ก่อน หากต้องการช่วยค้นหาตำแหน่งที่ต้องอัปเดตโค้ด คุณสามารถกำหนดนโยบาย 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 ที่อาจเกิดขึ้นโดยค่าเริ่มต้นเสมอ

4. การบังคับใช้ Trusted Types ด้วย CSP

ควรเปิดตัว Trusted Types แบบค่อยเป็นค่อยไปเพื่อไม่ให้แอปพลิเคชันขัดข้อง แม้ว่าการบังคับใช้เต็มรูปแบบจะให้การปกป้องที่รัดกุมที่สุด แต่ก็มีความเสี่ยงที่เว็บไซต์จะเสียหายหากไม่ได้ย้ายข้อมูลการเขียน Sink ทั้งหมด

ข้อกำหนดเบื้องต้น

  • การทำงานร่วมกันของเฟรมเวิร์ก: หากเฟรมเวิร์กของแอปพลิเคชัน (หรือไลบรารีของบุคคลที่สามที่เขียนไปยังซิงก์ DOM) ไม่สร้างค่า TrustedHTML หรือ TrustedScript คุณจะบังคับใช้ประเภทที่เชื่อถือได้ไม่ได้โดยไม่ทำให้โค้ดนั้นเสียหาย ตรวจสอบการรองรับเฟรมเวิร์กก่อนเริ่มการเปิดตัว
  • การเปลี่ยนโครงสร้างภายในโค้ดของซิงก์: ตรวจสอบว่าคุณได้ระบุและเปลี่ยนโครงสร้างภายในโค้ดของซิงก์ที่เป็นอันตรายเพื่อใช้นโยบายประเภทที่เชื่อถือได้

กลยุทธ์การเปิดตัว

  1. รายงานเท่านั้น: ขั้นแรก ให้เปิดตัวโดยใช้ส่วนหัว Content-Security-Policy-Report-Only ซึ่งจะบันทึกการละเมิดไปยังปลายทางการรายงานโดยไม่บล็อกการเขียนจริงๆ

    Content-Security-Policy-Report-Only: require-trusted-types-for 'script';
    
  2. การบังคับใช้: เมื่อคุณผสานรวมนโยบายและรายงานการละเมิดลดลงเหลือ 0 แล้ว ให้เปลี่ยนไปใช้การบังคับใช้อย่างเต็มรูปแบบ

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

นอกจากนี้ คุณยังจำกัดนโยบายที่อนุญาตให้สร้างได้ (ระบุชื่อนโยบาย เช่น sanitize-html หรือ escape-html, รวม dompurify หากนโยบายมอบสิทธิ์ให้ DOMPurify.sanitize() ซึ่งจะสร้างนโยบาย dompurify ภายใน และรวม default หากคุณลงทะเบียนนโยบายเริ่มต้นระหว่างการย้ายข้อมูล)

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

เมื่อใช้ CSP นี้ การเขียนไปยังซิงก์ที่ไม่ได้ใช้ประเภทที่เชื่อถือได้จะทำให้เกิดข้อผิดพลาด นอกจากนี้ การพยายามสร้างนโยบายประเภทที่เชื่อถือได้ซึ่งมีชื่อที่ไม่ได้ระบุไว้ใน 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 ได้ตั้งแต่วันนี้ เบราว์เซอร์ที่รองรับจะบังคับใช้ประเภทต่างๆ ส่วนเบราว์เซอร์รุ่นเก่าจะยังคงเรียกใช้ตรรกะการล้างข้อมูลผ่านนโยบายจำลอง

หากเบราว์เซอร์ไม่รองรับประเภทที่เชื่อถือได้ เบราว์เซอร์จะถือว่าออบเจ็กต์ TrustedHTML เป็นสตริง (หากใช้ Tinyfill) หรือจะไม่มีการตรวจสอบการบังคับใช้ที่ระดับซิงก์