API Sanitizer mới hướng đến việc xây dựng một bộ xử lý mạnh mẽ cho các chuỗi tuỳ ý được chèn vào trang một cách an toàn.
Các ứng dụng luôn xử lý các chuỗi không đáng tin cậy, nhưng việc hiển thị an toàn nội dung đó trong một tài liệu HTML có thể rất khó khăn. Nếu không cẩn thận, bạn có thể vô tình tạo cơ hội cho các cuộc tấn công tập lệnh trên nhiều trang web (XSS) mà kẻ tấn công độc hại có thể khai thác.
Để giảm thiểu rủi ro đó, đề xuất Sanitizer API mới nhằm mục đích xây dựng một bộ xử lý mạnh mẽ để chèn các chuỗi tuỳ ý vào một trang một cách an toàn. Bài viết này giới thiệu về API và giải thích cách sử dụng API.
// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerror=alert(0)>`, new Sanitizer())
Tách biệt dữ liệu đầu vào của người dùng
Khi chèn hoạt động đầu vào của người dùng, chuỗi truy vấn, nội dung cookie, v.v. vào DOM, các chuỗi phải được thoát đúng cách. Bạn cần đặc biệt chú ý đến việc thao tác DOM thông qua .innerHTML
, trong đó các chuỗi chưa thoát là nguồn XSS điển hình.
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.innerHTML = user_input
Nếu bạn thoát các ký tự đặc biệt của HTML trong chuỗi nhập ở trên hoặc mở rộng chuỗi đó bằng .textContent
, thì alert(0)
sẽ không được thực thi. Tuy nhiên, vì <em>
do người dùng thêm vào cũng được mở rộng dưới dạng chuỗi nên không thể dùng phương thức này để duy trì phần trang trí văn bản trong HTML.
Việc tốt nhất cần làm ở đây không phải là tránh thoát mà là làm sạch.
Dọn dẹp dữ liệu do người dùng nhập
Sự khác biệt giữa việc thoát và dọn dẹp
Thao tác thoát là thay thế các ký tự HTML đặc biệt bằng Thực thể HTML.
Việc dọn dẹp là để xoá các phần gây hại về mặt ngữ nghĩa (chẳng hạn như thực thi tập lệnh) khỏi các chuỗi HTML.
Ví dụ:
Trong ví dụ trước, <img onerror>
khiến trình xử lý lỗi được thực thi, nhưng nếu trình xử lý onerror
đã bị xoá, bạn có thể mở rộng trình xử lý này một cách an toàn trong DOM mà vẫn giữ nguyên <em>
.
// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerror=alert(0)>`
// Sanitized ⛑
$div.innerHTML = `<em>hello world</em><img src="">`
Để dọn dẹp đúng cách, bạn cần phân tích cú pháp chuỗi đầu vào dưới dạng HTML, bỏ qua các thẻ và thuộc tính được coi là có hại, đồng thời giữ lại các thẻ và thuộc tính không gây hại.
Thông số kỹ thuật API Trình dọn dẹp được đề xuất nhằm cung cấp quy trình xử lý đó dưới dạng API tiêu chuẩn cho trình duyệt.
API Trình dọn dẹp
Sanitizer API được sử dụng theo cách sau:
const $div = document.querySelector('div')
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
$div.setHTML(user_input, { sanitizer: new Sanitizer() }) // <div><em>hello world</em><img src=""></div>
Tuy nhiên, { sanitizer: new Sanitizer() }
là đối số mặc định. Vì vậy, mã có thể giống như bên dưới.
$div.setHTML(user_input) // <div><em>hello world</em><img src=""></div>
Xin lưu ý rằng setHTML()
được xác định trên Element
. Vì là một phương thức của Element
nên ngữ cảnh cần phân tích cú pháp sẽ tự giải thích (trong trường hợp này là <div>
), quá trình phân tích cú pháp được thực hiện một lần trong nội bộ và kết quả sẽ được mở rộng trực tiếp vào DOM.
Để nhận được kết quả dọn dẹp dưới dạng chuỗi, bạn có thể sử dụng .innerHTML
từ kết quả setHTML()
.
const $div = document.createElement('div')
$div.setHTML(user_input)
$div.innerHTML // <em>hello world</em><img src="">
Tuỳ chỉnh thông qua cấu hình
Theo mặc định, Sanitizer API được định cấu hình để xoá các chuỗi sẽ kích hoạt quá trình thực thi tập lệnh. Tuy nhiên, bạn cũng có thể thêm các chế độ tuỳ chỉnh của riêng mình vào quá trình dọn dẹp thông qua một đối tượng cấu hình.
const config = {
allowElements: [],
blockElements: [],
dropElements: [],
allowAttributes: {},
dropAttributes: {},
allowCustomElements: true,
allowComments: true
};
// sanitized result is customized by configuration
new Sanitizer(config)
Các tuỳ chọn sau đây chỉ định cách kết quả dọn dẹp sẽ xử lý phần tử được chỉ định.
allowElements
: Tên của các phần tử mà trình dọn dẹp sẽ giữ lại.
blockElements
: Tên của các phần tử mà trình dọn dẹp sẽ xoá, trong khi vẫn giữ lại các phần tử con.
dropElements
: Tên của các phần tử mà trình dọn dẹp sẽ xoá, cùng với các phần tử con của chúng.
const str = `hello <b><i>world</i></b>`
$div.setHTML(str)
// <div>hello <b><i>world</i></b></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: [ "b" ]}) })
// <div>hello <b>world</b></div>
$div.setHTML(str, { sanitizer: new Sanitizer({blockElements: [ "b" ]}) })
// <div>hello <i>world</i></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: []}) })
// <div>hello world</div>
Bạn cũng có thể kiểm soát việc trình dọn dẹp sẽ cho phép hay từ chối các thuộc tính được chỉ định bằng các tuỳ chọn sau:
allowAttributes
dropAttributes
Các thuộc tính allowAttributes
và dropAttributes
yêu cầu có danh sách so khớp thuộc tính — các đối tượng có khoá là tên thuộc tính và giá trị là danh sách phần tử mục tiêu hoặc ký tự đại diện *
.
const str = `<span id=foo class=bar style="color: red">hello</span>`
$div.setHTML(str)
// <div><span id="foo" class="bar" style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["span"]}}) })
// <div><span style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["p"]}}) })
// <div><span>hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {"style": ["*"]}}) })
// <div><span style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({dropAttributes: {"id": ["span"]}}) })
// <div><span class="bar" style="color: red">hello</span></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {}}) })
// <div>hello</div>
allowCustomElements
là lựa chọn cho phép hoặc từ chối các phần tử tuỳ chỉnh. Nếu được cho phép, các cấu hình khác cho các phần tử và thuộc tính vẫn được áp dụng.
const str = `<custom-elem>hello</custom-elem>`
$div.setHTML(str)
// <div></div>
const sanitizer = new Sanitizer({
allowCustomElements: true,
allowElements: ["div", "custom-elem"]
})
$div.setHTML(str, { sanitizer })
// <div><custom-elem>hello</custom-elem></div>
Nền tảng API
So sánh với DomPurify
DOMPurify là một thư viện nổi tiếng cung cấp chức năng dọn dẹp. Điểm khác biệt chính giữa API Trình dọn dẹp và DOMPurify là DOMPurify trả về kết quả của quá trình dọn dẹp dưới dạng một chuỗi mà bạn cần ghi vào phần tử DOM thông qua .innerHTML
.
const user_input = `<em>hello world</em><img src="" onerror=alert(0)>`
const sanitized = DOMPurify.sanitize(user_input)
$div.innerHTML = sanitized
// `<em>hello world</em><img src="">`
DOMPurify có thể đóng vai trò dự phòng khi Sanitizer API không được triển khai trong trình duyệt.
Việc triển khai DOMPurify có một số nhược điểm. Nếu một chuỗi được trả về, thì chuỗi đầu vào sẽ được phân tích cú pháp hai lần, bằng DOMPurify và .innerHTML
. Việc phân tích cú pháp hai lần này làm lãng phí thời gian xử lý, nhưng cũng có thể dẫn đến các lỗ hổng thú vị do các trường hợp kết quả của lần phân tích cú pháp thứ hai khác với lần đầu tiên.
HTML cũng cần ngữ cảnh để được phân tích cú pháp. Ví dụ: <td>
có ý nghĩa trong <table>
nhưng không có ý nghĩa trong <div>
. Vì DOMPurify.sanitize()
chỉ nhận chuỗi làm đối số, nên ngữ cảnh phân tích cú pháp phải được đoán.
Sanitizer API cải thiện phương pháp DOMPurify và được thiết kế để loại bỏ việc cần phải phân tích cú pháp hai lần và làm rõ ngữ cảnh phân tích cú pháp.
Trạng thái API và hỗ trợ trình duyệt
Sanitizer API đang được thảo luận trong quá trình chuẩn hoá và Chrome đang trong quá trình triển khai API này.
Bước | Trạng thái |
---|---|
1. Tạo thông báo giải thích | Hoàn tất |
2. Tạo bản nháp thông số kỹ thuật | Hoàn tất |
3. Thu thập ý kiến phản hồi và lặp lại thiết kế | Hoàn tất |
4. Bản dùng thử theo nguyên gốc Chrome | Hoàn tất |
5. Khởi chạy | Ý định vận chuyển trên M105 |
Mozilla: Xem xét đề xuất này đáng để tạo bản minh hoạ và đang tích cực triển khai đề xuất này.
WebKit: Xem nội dung phản hồi trên danh sách gửi thư WebKit.
Cách bật Sanitizer API
Bật thông qua tuỳ chọn about://flags
hoặc CLI
Chrome
Chrome đang trong quá trình triển khai API Trình dọn dẹp. Trong Chrome 93 trở lên, bạn có thể thử hành vi này bằng cách bật cờ about://flags/#enable-experimental-web-platform-features
. Trong các phiên bản trước của Chrome Canary và kênh Dev, bạn có thể bật tính năng này thông qua --enable-blink-features=SanitizerAPI
và dùng thử ngay. Hãy xem hướng dẫn cách chạy Chrome có cờ.
Firefox
Firefox cũng triển khai API Trình dọn dẹp dưới dạng một tính năng thử nghiệm. Để bật tính năng này, hãy đặt cờ dom.security.sanitizer.enabled
thành true
trong about:config
.
Phát hiện tính năng
if (window.Sanitizer) {
// Sanitizer API is enabled
}
Phản hồi
Nếu bạn dùng thử API này và có ý kiến phản hồi, chúng tôi rất mong được biết. Chia sẻ suy nghĩ của bạn về các vấn đề trên GitHub về API Trình dọn dẹp và thảo luận với các tác giả thông số kỹ thuật cũng như những người quan tâm đến API này.
Nếu bạn phát hiện bất kỳ lỗi hoặc hành vi không mong muốn nào trong quá trình triển khai của Chrome, hãy gửi lỗi để báo cáo. Chọn các thành phần Blink>SecurityFeature>SanitizerAPI
và chia sẻ thông tin chi tiết để giúp người triển khai theo dõi vấn đề.
Bản minh hoạ
Để xem cách hoạt động của API Sanitizer trong thực tế, hãy xem Sanitizer API Playground của Mike West:
Tài liệu tham khảo
- Quy cách của HTML Sanitizer API
- Kho lưu trữ WICG/sanitizer-api
- Câu hỏi thường gặp về API Trình dọn dẹp
- Tài liệu tham khảo về API Trình dọn dẹp HTML trên MDN
Ảnh chụp của Towfiqu barbhuiya trên Unsplash.