Ngăn chặn lỗ hổng tập lệnh trên nhiều trang web dựa trên DOM với Loại đáng tin cậy

Krzysztof Kotowicz
Krzysztof Kotowicz

Browser Support

  • Chrome: 83.
  • Edge: 83.
  • Firefox: behind a flag.
  • Safari: 26.

Source

Lỗ hổng tập lệnh chéo trang dựa trên DOM (DOM XSS) xảy ra khi dữ liệu từ một nguồn do người dùng kiểm soát (chẳng hạn như tên người dùng hoặc URL chuyển hướng lấy từ đoạn URL) đến một đích, là một hàm như eval() hoặc một phương thức thiết lập thuộc tính như .innerHTML có thể thực thi mã JavaScript tuỳ ý.

DOM XSS là một trong những lỗ hổng bảo mật phổ biến nhất trên web và các nhóm phát triển thường vô tình đưa lỗ hổng này vào ứng dụng của họ. Trusted Types cung cấp cho bạn các công cụ để viết, đánh giá bảo mật và giữ cho các ứng dụng không bị lỗ hổng DOM XSS bằng cách mặc định bảo mật các hàm API web nguy hiểm. Trusted Types có sẵn dưới dạng polyfill cho những trình duyệt chưa hỗ trợ Trusted Types.

Thông tin khái quát

Trong nhiều năm, DOM XSS là một trong những lỗ hổng bảo mật phổ biến và nguy hiểm nhất trên web.

Có hai loại tập lệnh trên nhiều trang web. Một số lỗ hổng XSS là do mã phía máy chủ tạo mã HTML một cách không an toàn, tạo thành trang web. Những người khác có nguyên nhân gốc trên máy khách, trong đó mã JavaScript gọi các hàm nguy hiểm bằng nội dung do người dùng kiểm soát.

Để ngăn chặn XSS phía máy chủ, đừng tạo HTML bằng cách nối các chuỗi. Thay vào đó, hãy sử dụng các thư viện mẫu tự động thoát ngữ cảnh an toàn, cùng với Chính sách bảo mật nội dung dựa trên số chỉ dùng một lần để giảm thiểu thêm lỗi.

Giờ đây, các trình duyệt cũng có thể giúp ngăn chặn XSS dựa trên DOM phía máy khách bằng cách sử dụng Trusted Types.

Giới thiệu về API

Trusted Types hoạt động bằng cách khoá các hàm đích có rủi ro sau đây. Bạn có thể đã nhận ra một số tính năng trong số này, vì các nhà cung cấp trình duyệt và khung web đã hướng dẫn bạn không nên sử dụng những tính năng này vì lý do bảo mật.

Trusted Types yêu cầu bạn xử lý dữ liệu trước khi truyền dữ liệu đó đến các hàm nhận này. Chỉ sử dụng một chuỗi sẽ không thành công, vì trình duyệt không biết liệu dữ liệu có đáng tin cậy hay không:

Không nên
anElement.innerHTML  = location.href;
Khi Trusted Types được bật, trình duyệt sẽ gửi một TypeError và ngăn việc sử dụng một đích XSS DOM bằng một chuỗi.

Để biểu thị rằng dữ liệu đã được xử lý an toàn, hãy tạo một đối tượng đặc biệt – một Trusted Type (Loại đáng tin cậy).

Nên
anElement.innerHTML = aTrustedHTML;
  
Khi bạn bật Trusted Types, trình duyệt sẽ chấp nhận một đối tượng TrustedHTML cho các đích nhận dự kiến nhận được đoạn mã HTML. Ngoài ra, còn có các đối tượng TrustedScriptTrustedScriptURL cho các nguồn nhận nhạy cảm khác.

Trusted Types giúp giảm đáng kể bề mặt tấn công DOM XSS của ứng dụng. Tính năng này giúp đơn giản hoá quy trình đánh giá bảo mật và cho phép bạn thực thi các quy trình kiểm tra bảo mật dựa trên loại được thực hiện khi biên dịch, linting hoặc kết hợp mã của bạn trong thời gian chạy, trong trình duyệt.

Cách sử dụng Trusted Types

Chuẩn bị cho báo cáo vi phạm Chính sách bảo mật nội dung

Bạn có thể triển khai một trình thu thập báo cáo, chẳng hạn như reporting-api-processor hoặc go-csp-collector nguồn mở, hoặc sử dụng một trong các phiên bản tương đương thương mại. Bạn cũng có thể thêm tính năng ghi nhật ký tuỳ chỉnh và gỡ lỗi các lỗi vi phạm trong trình duyệt bằng cách sử dụng 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();

hoặc bằng cách thêm một trình nghe sự kiện:

document.addEventListener('securitypolicyviolation',
    console.error.bind(console));

Thêm tiêu đề CSP chỉ báo cáo

Thêm tiêu đề Phản hồi HTTP sau đây vào những tài liệu mà bạn muốn di chuyển sang Trusted Types:

Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

Giờ đây, tất cả các lỗi vi phạm đều được báo cáo cho //my-csp-endpoint.example, nhưng trang web vẫn tiếp tục hoạt động. Phần tiếp theo sẽ giải thích cách hoạt động của //my-csp-endpoint.example.

Xác định lỗi vi phạm Loại đáng tin cậy

Từ giờ trở đi, mỗi khi Trusted Types phát hiện thấy một lỗi vi phạm, trình duyệt sẽ gửi báo cáo đến một report-uri đã được định cấu hình. Ví dụ: khi ứng dụng của bạn truyền một chuỗi đến innerHTML, trình duyệt sẽ gửi báo cáo sau:

{
"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"
}
}

Điều này cho biết rằng trong https://my.url.example/script.js ở dòng 39, innerHTML đã được gọi bằng chuỗi bắt đầu bằng <img src=x. Thông tin này sẽ giúp bạn thu hẹp phạm vi những phần mã có thể gây ra DOM XSS và cần thay đổi.

Khắc phục lỗi vi phạm

Có một số cách để khắc phục lỗi vi phạm Loại đáng tin cậy. Bạn có thể xoá mã vi phạm, sử dụng một thư viện, tạo chính sách về Loại đáng tin cậy hoặc, phương án cuối cùng là tạo chính sách mặc định.

Viết lại mã vi phạm

Có thể bạn không cần đến mã không tuân thủ nữa hoặc có thể viết lại mã đó mà không cần dùng các hàm gây ra lỗi vi phạm:

Nên
el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);
Không nên
el.innerHTML = '<img src=xyz.jpg>';

Sử dụng thư viện

Một số thư viện đã tạo sẵn Trusted Types mà bạn có thể truyền đến các hàm đích. Ví dụ: bạn có thể sử dụng DOMPurify để dọn dẹp một đoạn mã HTML, xoá các tải trọng XSS.

import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});

DOMPurify hỗ trợ Trusted Types và trả về HTML đã được dọn dẹp trong một đối tượng TrustedHTML để trình duyệt không tạo ra lỗi vi phạm.

Tạo chính sách Loại đáng tin cậy

Đôi khi, bạn không thể xoá mã gây ra lỗi vi phạm và không có thư viện nào để dọn dẹp giá trị và tạo một Loại đáng tin cậy cho bạn. Trong những trường hợp đó, bạn có thể tự tạo một đối tượng Trusted Type.

Trước tiên, hãy tạo một chính sách. Chính sách là các nhà máy cho Loại đáng tin cậy, thực thi một số quy tắc bảo mật nhất định đối với đầu vào của chúng:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
    createHTML: string => string.replace(/\</g, '&lt;')
  });
}

Mã này tạo ra một chính sách có tên là myEscapePolicy có thể tạo ra các đối tượng TrustedHTML bằng hàm createHTML(). Các quy tắc được xác định sẽ thoát HTML cho các ký tự < để ngăn việc tạo các phần tử HTML mới.

Hãy sử dụng chính sách như sau:

const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML);  // true
el.innerHTML = escaped;  // '&lt;img src=x onerror=alert(1)>'

Sử dụng chính sách mặc định

Đôi khi, bạn không thể thay đổi mã vi phạm, chẳng hạn như nếu bạn đang tải một thư viện bên thứ ba từ CDN. Trong trường hợp đó, hãy sử dụng chính sách mặc định:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  trustedTypes.createPolicy('default', {
    createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
  });
}

Chính sách có tên default được dùng ở bất cứ nơi nào một chuỗi được dùng trong một đích nhận chỉ chấp nhận Loại đáng tin cậy.

Chuyển sang thực thi Chính sách bảo mật nội dung

Khi ứng dụng của bạn không còn tạo ra các lỗi vi phạm, bạn có thể bắt đầu thực thi Trusted Types:

Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

Giờ đây, dù ứng dụng web của bạn phức tạp đến đâu, thì chỉ có mã trong một trong các chính sách của bạn mới có thể gây ra lỗ hổng DOM XSS và bạn có thể hạn chế điều đó hơn nữa bằng cách giới hạn việc tạo chính sách.

Tài liệu đọc thêm