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

  • 83
  • 83
  • x
  • x

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) truy cập vào một bể chứa. Đây là một hàm như eval() hoặc một phương thức setter 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 web phổ biến nhất và các nhóm nhà phát triển thường vô tình đưa lỗ hổng này vào ứng dụng của họ. 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 ứng dụng không có lỗ hổng DOM XSS bằng cách bảo mật các hàm API web nguy hiểm theo mặc định. Các loại đáng tin cậy được cung cấp dưới dạng polyfill cho các trình duyệt chưa hỗ trợ loại dữ liệu 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 ra mã HTML tạo trang web một cách không an toàn. Một số khác có nguyên nhân gốc từ 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 dùng các thư viện tạo mẫu tự động theo bối cảnh an toàn, cùng với Chính sách bảo mật nội dung chỉ dùng một lần để giảm thiểu lỗi thêm.

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 bồn lưu trữ dữ liệu tiềm ẩn rủi ro sau đây. 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 đã hướng bạn đến việc không sử dụng những tính năng này vì lý do bảo mật.

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 lưu trữ dữ liệu này. Nếu chỉ sử dụng chuỗi sẽ không thành công vì trình duyệt không biết dữ liệu có đáng tin cậy hay không:

Không nên
anElement.innerHTML  = location.href;
Khi bật Loại đáng tin cậy, trình duyệt sẽ gửi ra một TypeError và ngăn việc sử dụng bồn lưu trữ dữ liệu DOM XSS 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, đó là 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 chứa đoạn mã HTML. Ngoài ra, còn có các đối tượng TrustedScriptTrustedScriptURL cho các bồn lưu trữ dữ liệu nhạy cảm khác.

Các loại đáng tin cậy làm giảm đáng kể giao diện tấn công XSS DOM của ứng dụng. Công cụ này đơ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 kiểu được thực hiện khi biên dịch, tìm lỗi mã nguồn hoặc gói mã của bạn 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 một trình thu thập báo cáo, chẳng hạn như go-csp-collector nguồn mở hoặc sử dụng một trong các trình thu thập thương mại tương đương. Bạn cũng có thể gỡ lỗi vi phạm trong trình duyệt:

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 các tài liệu mà bạn muốn di chuyển sang Loại đáng tin cậy:

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

Hiện tại, tất cả lỗi vi phạm đều được báo cáo cho //my-csp-endpoint.example, nhưng trang web vẫn 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 loại đáng tin cậy

Từ nay trở đi, mỗi khi Loại đáng tin cậy phát hiện thấy lỗi vi phạm, trình duyệt sẽ gửi 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:

{
"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 nói 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ể giới thiệu 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, sử dụng thư viện, tạo chính sách Loại đáng tin cậy hoặc tạo một chính sách mặc định sau cùng.

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

Có thể mã không tuân thủ không cần thiết nữa hoặc có thể được viết lại 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 = '';

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 lưu trữ dữ liệu. Ví dụ: bạn có thể sử dụng DOMPurify để dọn dẹp đ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 gây 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à sẽ không có thư viện để dọn dẹp giá trị cũng như 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 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 dữ liệu đầu vào:

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

Đoạn mã này tạo một chính sách có tên là myEscapePolicy, có thể tạo các đối tượng TrustedHTML bằng cách sử dụng hàm createHTML(). Các ký tự < thoát HTML đã xác định trong các quy tắc đã xác định ngăn việc tạo 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 của bên thứ ba từ mộ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 chuỗi được dùng trong bồn lưu trữ dữ liệu 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 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, cho dù ứng dụng web của bạn phức tạp đến mức nào thì điều duy nhất có thể gây ra lỗ hổng bảo mật DOM XSS là mã trong một trong các chính sách của bạn. Bạn cò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