Ngăn chặn các cuộc tấn công XSS dựa trên DOM bằng Trusted Types

Tính năng web: Loại đáng tin cậy

Trusted Types là một tính năng bảo mật giúp ngăn chặn lỗi tập lệnh trên nhiều trang web dựa trên DOM (DOM XSS) bằng cách yêu cầu dữ liệu được truyền vào các API trình duyệt "nguy hiểm" (gọi là bồn lưu trữ) trước tiên phải được chuyển đổi thành một loại cụ thể, chẳng hạn như TrustedHTML.

Theo mặc định, nhiều API DOM (như innerHTML hoặc document.write) chấp nhận chuỗi thô. Nếu một chuỗi chứa tập lệnh độc hại từ một nguồn không đáng tin cậy, thì tập lệnh đó có thể được thực thi. Trusted Types thay đổi hành vi này: sau khi được bật thông qua CSP, các bồn lưu trữ này sẽ chỉ chấp nhận các đối tượng TrustedHTML do các chính sách được uỷ quyền tạo ra.

Thông tin chi tiết quan trọng về việc triển khai

Để sử dụng Trusted Types, hãy làm theo 4 bước chính sau:

  1. Xác định chính sách bảo mật chỉ định cách dọn dẹp hoặc chuyển đổi chuỗi.
  2. Sử dụng chính sách đó để tạo các đối tượng Trusted Type từ chuỗi thô.
  3. Sử dụng các đối tượng Loại đáng tin cậy khi ghi vào bồn lưu trữ DOM.
  4. Thực thi Trusted Types bằng Chính sách bảo mật nội dung (CSP).

1. Tạo chính sách

Chính sách là một tập hợp các quy tắc để dọn dẹp nội dung. Bạn tạo chính sách này bằng trustedTypes.createPolicy(), thường uỷ quyền cho một thư viện dọn dẹp như DOMPurify hoặc các quy tắc thoát HTML của ứng dụng:

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. Sử dụng chính sách

Thay vì truyền trực tiếp một chuỗi đến bồn lưu trữ, bạn gọi phương thức tạo của chính sách.

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. Chuyển đổi bồn lưu trữ để sử dụng Trusted Types

Cập nhật từng thực thể mà bạn ghi vào bồn lưu trữ để trước tiên chuyển đổi thực thể đó thành đối tượng Loại tin cậy. Để giúp xác định vị trí cần cập nhật mã, bạn có thể xác định một chính sách default tạm thời ghi nhật ký khi một bồn lưu trữ được ghi bằng một chuỗi thay vì TrustedType.

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

Các bồn lưu trữ phổ biến hỗ trợ Trusted Types bao gồm:

  • HTML: element.innerHTML, element.outerHTML, document.write(), element.setHTMLUnsafe(), Document.parseHTMLUnsafe()
  • Kịch bản: <script> nội dung văn bản, eval()
  • ScriptURL: <script src>, Worker(), SharedWorker()
// Assign the TrustedHTML object.
element.innerHTML = cleanHTML;

Xin lưu ý rằng các phương thức dọn dẹp HTML "an toàn" (element.setHTML() và Document.parseHTML(), được đề cập trong phần Dọn dẹp HTML không đáng tin cậy) không yêu cầu chính sách Loại đáng tin cậy vì các phương thức này luôn dọn dẹp các cuộc tấn công XSS tiềm ẩn theo cách gốc.

4. Thực thi Trusted Types bằng CSP

Bạn nên triển khai dần Trusted Types để tránh làm hỏng ứng dụng. Mặc dù việc thực thi đầy đủ mang lại khả năng bảo vệ mạnh mẽ nhất, nhưng việc này có nguy cơ làm hỏng trang web nếu mọi hoạt động ghi bồn lưu trữ chưa được di chuyển.

Điều kiện tiên quyết

  • Hợp tác với khung: Nếu khung của ứng dụng (hoặc bất kỳ thư viện bên thứ ba nào ghi vào bồn lưu trữ DOM) không tạo ra giá trị TrustedHTML hoặc TrustedScript, thì bạn không thể thực thi Loại tin cậy mà không làm hỏng mã đó. Kiểm tra khả năng hỗ trợ của khung trước khi bắt đầu triển khai.
  • Tái cấu trúc bồn lưu trữ: Đảm bảo bạn đã xác định và tái cấu trúc các bồn lưu trữ nguy hiểm để sử dụng chính sách Loại tin cậy.

Chiến lược triển khai

  1. Chỉ báo cáo: Trước tiên, hãy triển khai bằng tiêu đề Content-Security-Policy-Report-Only. Thao tác này sẽ ghi lại các lỗi vi phạm vào điểm cuối báo cáo mà không thực sự chặn các hoạt động ghi.

    Content-Security-Policy-Report-Only: require-trusted-types-for 'script';
    
  2. Thực thi: Sau khi bạn tích hợp các chính sách và số lượng báo cáo vi phạm giảm xuống bằng 0, hãy chuyển sang thực thi đầy đủ.

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

Bạn cũng có thể hạn chế những chính sách được phép tạo (liệt kê tên chính sách của bạn, chẳng hạn như sanitize-html hoặc escape-html, bao gồm dompurify nếu chính sách của bạn uỷ quyền cho DOMPurify.sanitize() – chính sách này sẽ tạo chính sách dompurify trong nội bộ – và bao gồm default nếu bạn đăng ký chính sách mặc định trong quá trình di chuyển):

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

Khi áp dụng CSP này, mọi hoạt động ghi vào bồn lưu trữ không sử dụng Loại tin cậy sẽ gửi một lỗi. Ngoài ra, việc cố gắng tạo chính sách Loại đáng tin cậy có tên không có trong trusted-types sẽ gây ra lỗi.

Bạn cũng có thể đặt CSP này làm thẻ <meta> trong <head> của tài liệu, mặc dù cách này có thể kém an toàn hơn.

<!-- 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;"
/>

Chiến lược dự phòng

Đối với những trình duyệt chưa hỗ trợ Trusted Types, bạn có thể sử dụng một "tinyfill" tối thiểu để đảm bảo mã của bạn không bị hỏng trong khi vẫn cung cấp tính năng dọn dẹp.

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

Nhờ đó, bạn có thể viết mã bằng mẫu Trusted Types ngay hôm nay. Các trình duyệt hỗ trợ sẽ thực thi các loại, trong khi các trình duyệt cũ vẫn sẽ thực thi logic dọn dẹp của bạn thông qua chính sách mô phỏng.

Nếu một trình duyệt không hỗ trợ Loại tin cậy, trình duyệt đó sẽ chỉ coi các đối tượng TrustedHTML là chuỗi (nếu sử dụng tinyfill) hoặc sẽ không có quy trình kiểm tra thực thi ở cấp bồn lưu trữ.