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

Kiểu tấn công tập lệnh trên nhiều trang web (DOM XSS) dựa trên DOM 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ừ mảnh URL) đến một chậu lưu trữ. Đâ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à 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ọ. Trusted Types (Loại đáng tin cậy) cung cấp cho bạn các công cụ để viết, xem xét bảo mật và giúp ứ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ó sẵn dưới dạng polyfill cho các trình duyệt chưa hỗ trợ các 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. Các lỗi khác có nguyên nhân gốc rễ 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 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 an toàn theo ngữ cảnh, 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, 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 Các 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. Bạn có thể đã nhận ra một số trong số đó, vì các nhà cung cấp trình duyệt và khung web đã hướng bạn tránh sử dụng các 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 đó đến các hàm bồn lưu trữ 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ạn bật Loại đáng tin cậy, trình duyệt sẽ gửi một TypeError và ngăn việc sử dụng bồn lưu trữ DOM XSS bằng một chuỗi.

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

Nên
anElement.innerHTML = aTrustedHTML;
  
Khi bạn bật 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ự kiến sẽ 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 tin cậy

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 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 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ả 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:

{
"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, sử 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 (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 đã được dọn dẹp được gói trong đối tượng TrustedHTML để trình duyệt không tạo 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 có thể tạo các đối tượng TrustedHTML bằng hàm createHTML(). Các quy tắc đã xác định sẽ thoát HTML các ký tự < để 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 đ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