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

Hỗ trợ trình duyệt

  • Chrome: 83.
  • Edge: 83.
  • Firefox: không được hỗ trợ.
  • Safari: không được hỗ trợ.

Nguồn

Tập lệnh trên nhiều trang web 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 (như tên người dùng hoặc URL chuyển hướng lấy từ mảnh URL) đến một bếp, là một hàm như eval() hoặc một hàm setter có thể thực thi mã JavaScript tuỳ ý..innerHTML

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

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 web phổ biến và nguy hiểm nhất.

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 tạo thành trang web một cách không an toàn. Một số khác lại có nguyên nhân gốc rễ từ máy khách, trong đó mã JavaScript gọi các hàm nguy hiểm có nội dung do người dùng kiểm soát.

Để ngă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 tạo mẫu tự động thoát theo bối cảnh an toàn, cùng với Chính sách bảo mật nội dung dựa trên một lần duy nhất để giảm thiểu lỗi bổ sung.

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 Loại đáng tin cậy.

Giới thiệu về API

Loại đáng tin cậy hoạt động bằng cách khoá các hàm sink có nguy cơ sau. Có thể bạn đã nhận ra một vài tính năng trong số đó, vì các nhà cung cấp trình duyệt và khung web đã ngăn bạn sử dụng các tính năng này vì lý do bảo mật.

Các loại đáng tin cậy yêu cầu bạn xử lý dữ liệu trước khi truyền dữ liệu đó vào các hàm bồn rửa này. Việc 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 bật tính năng Loại đáng tin cậy, trình duyệt sẽ gửi TypeError và ngăn việc sử dụng bồn lưu trữ dữ liệu 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 – Loại đáng tin cậy.

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

Loại đáng tin cậy giúp giảm đáng kể mặt trận 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 bước kiểm tra bảo mật dựa trên loại được thực hiện khi biên dịch, tìm lỗi mã nguồn hoặc gói mã trong thời gian chạy, trong trình duyệt.

Cách sử dụng Loại đáng tin cậy

Chuẩn bị cho các 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 trình thu thập báo cáo, chẳng hạn như reporting-api-processor nguồn mở hoặc go-csp-collector, hoặc sử dụng một trong các trình thu thập báo cáo 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 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 vào các tài liệu mà bạn muốn di chuyển sang loại Trusted Types (Loại đáng tin cậy):

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 giải thích cách hoạt động của //my-csp-endpoint.example.

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

Từ giờ trở đi, mỗi khi Trusted Types phát hiện một lỗi vi phạm, trình duyệt sẽ gửi một báo cáo đến report-uri đã đị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 đây:

{
"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 trên 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 những phần mã có thể đang đưa 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 về Loại đáng tin cậy. Bạn có thể xoá mã vi phạm, dùng thư viện, tạo chính sách Loại đáng tin cậy hoặc tạo chính sách mặc định trong phương án cuối cùng.

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

Có thể bạn không cần mã không tuân thủ nữa hoặc có thể viết lại mã mà không cần 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 các Loại đáng tin cậy mà bạn có thể truyền đến các hàm sink. Ví dụ: bạn có thể sử dụng DOMPurify để dọn dẹp một đoạn mã HTML, xoá tải trọng XSS.

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

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

Tạo chính sách về 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 Loại đáng tin cậy cho bạn. Trong những trường hợp đó, bạn có thể tự tạo đối tượng Loại đáng tin cậy.

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

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

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

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ư khi bạn đang tải 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ứ khi nào một chuỗi được dùng trong một bồn lưu trữ 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 lỗi vi phạm nữa, bạn có thể bắt đầu thực thi Các loại đáng tin cậy:

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

Giờ đây, bất kể ứng dụng web của bạn phức tạp đến mức nào, điều duy nhất có thể gây ra lỗ hổng XSS DOM là mã trong một trong các chính sách của bạn. 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