- Để ngăn chặn tấn công XSS — một trong những lỗ hổng lớn nhất của web — Firefox là trình duyệt đầu tiên hỗ trợ Sanitizer API được chuẩn hóa
- Khi dùng phương thức setHTML() thay cho innerHTML trước đây, HTML không đáng tin cậy sẽ được tự động làm sạch (sanitize) trước khi chèn vào DOM, từ đó loại bỏ script độc hại
- Nếu cấu hình mặc định quá chặt hoặc chưa đủ, nhà phát triển có thể kiểm soát các phần tử và thuộc tính được cho phép thông qua thiết lập tùy chỉnh
- Tính năng này của Firefox khi kết hợp với Trusted Types sẽ nâng cao mức độ bảo mật của web nói chung, đồng thời giúp nhà phát triển có thể ngăn chặn XSS mà không cần một đội ngũ bảo mật riêng
Lỗ hổng XSS và cách Firefox ứng phó
- Cross-site scripting (XSS) xảy ra khi kẻ tấn công chèn HTML hoặc JavaScript tùy ý thông qua nội dung do người dùng nhập
- Kẻ tấn công có thể lợi dụng điều này để theo dõi tương tác của người dùng hoặc đánh cắp dữ liệu
- XSS đã được xếp vào top 3 lỗ hổng web nghiêm trọng (CWE-79) trong gần 10 năm
- Từ năm 2009, Firefox đã dẫn dắt tiêu chuẩn Content-Security-Policy (CSP) để tăng cường phòng vệ trước XSS
- CSP giới hạn các tài nguyên mà website có thể tải và thực thi
- Tuy nhiên, vì đòi hỏi thay đổi cấu trúc website hiện có và rà soát bảo mật liên tục nên việc áp dụng rộng rãi còn hạn chế
Vai trò của Sanitizer API và setHTML()
- Sanitizer API cung cấp một phương thức chuẩn hóa để chuyển đổi HTML độc hại thành dạng vô hại
- Trong ví dụ mã, phần tử
<img src="x" onclick="alert('XSS')"> bị loại bỏ và chỉ còn lại <h1>Hello my name is</h1>
- Phương thức setHTML() tự động thực hiện quá trình làm sạch khi chèn HTML, bảo đảm hành vi an toàn theo mặc định
- Chỉ cần thay phép gán
innerHTML hiện tại bằng setHTML() là đã có thể có cơ chế phòng vệ XSS mạnh mẽ
- Nếu thiết lập mặc định quá nghiêm ngặt hoặc quá lỏng, nhà phát triển có thể định nghĩa các phần tử và thuộc tính HTML được cho phép bằng cấu hình tùy chỉnh
- Có thể dùng công cụ Sanitizer API playground để thử nghiệm
Kết hợp với Trusted Types
- Trusted Types API cung cấp thêm một lớp bảo mật bằng cách kiểm soát tập trung việc phân tích và chèn HTML
- Khi dùng
setHTML(), có thể áp dụng chính sách Trusted Types một cách dễ dàng
- Chính sách nghiêm ngặt có thể chỉ cho phép
setHTML() và chặn các cách chèn nguy hiểm khác, góp phần ngăn XSS tái xuất trong tương lai
Tác động nâng cao bảo mật trong Firefox 148
- Firefox 148 hỗ trợ cả Sanitizer API và Trusted Types, qua đó nâng cao đáng kể mức bảo mật mặc định
- Nhà phát triển có thể ngăn chặn XSS chỉ với thay đổi mã đơn giản, không cần chính sách bảo mật phức tạp hay đội ngũ bảo mật riêng
- Việc đưa tiêu chuẩn này vào trình duyệt được kỳ vọng sẽ thúc đẩy một môi trường web an toàn hơn trên mọi trình duyệt
Tóm tắt
- Firefox 148 hỗ trợ nhà phát triển web dễ dàng chặn tấn công XSS thông qua phương thức setHTML() và Sanitizer API
- Tính năng này khắc phục các giới hạn của CSP và là bước đệm để phổ biến cách chèn HTML an toàn theo mặc định như một tiêu chuẩn web
- Việc kết hợp với Trusted Types cho phép duy trì bảo mật lâu dài và ngăn XSS tái diễn
- Kết quả là Firefox đang dẫn dắt quá trình chuyển đổi sang một môi trường web nơi bảo mật là mặc định
2 bình luận
Ồ đúng là kiểu này thực sự cần thiết. Nếu được hỗ trợ trên mọi trình duyệt thì có lẽ sẽ tuyệt vời lắm.
Ý kiến trên Hacker News
Những tính năng kiểu này lúc nào cũng hơi đáng lo
Vì có sự pha trộn giữa các phương thức xử lý an toàn khi nhận đầu vào người dùng tùy ý và các phương thức không an toàn, nhưng chỉ nhìn tên thì khó phân biệt
Lý tưởng nhất là ngay từ đầu, các hàm nguy hiểm phải được thể hiện rõ trong tên gọi
Ngoài ra, chính khái niệm “sanitize” HTML cũng khá mơ hồ, và khó đánh giá liệu nó có thực sự an toàn hay không
Nó loại bỏ các phần tử hoặc thuộc tính có thể thực thi script, và logic này chạy bên trong engine trình duyệt nên xử lý chính xác hơn các sanitizer dựa trên chuỗi
Xem thêm trong tài liệu MDN về setHTML
elementNode.textContentan toàn với đầu vào không đáng tin cậy, cònelementNode.innerHTMLthì khôngCái trước escape mọi ký tự, còn cái sau thì không escape gì cả
Cũng có ý kiến cho rằng “HTML sanitization” là vấn đề về bản chất không thể giải quyết triệt để
Xem thảo luận liên quan ở bình luận này
Các API kiểu này lẽ ra không nên được thông qua ngay từ giai đoạn đề xuất
innerHTMLvàsetHTMLcùng tồn tại, lẽ ra nên loại bỏ hoàn toàninnerHTML, và nếu cần hành vi cũ thì dùngsetHTMLUnsafeinnerHTMLbằng một thiết lập toàn cụcTuy nhiên làm vậy có thể khiến website không hoạt động trên các trình duyệt cũ
Content-Security-Policy: require-trusted-types-for 'script', thì có thể chặn việc truyền chuỗi thông thường vào các phương thức không có sanitizerNếu như ví dụ cho thấy có thể chèn các thẻ như
<h1>hay<br>vào tên người dùng, thì dù có chặn thực thi script đi nữa vẫn vẫn có thể tiêm markup tùy ýThậm chí còn có thể thay đổi CSS bằng thẻ
<style>, ví dụ làm biến đổi giao diện của trang hồ sơ PayPalĐiều đó khiến người ta phải tự hỏi ai lại muốn như vậy
Có thể thêm một lớp phòng vệ bằng cách tiếp tục giới hạn HTML sinh ra từ Markdown bằng sanitizer để chỉ cho phép một số thẻ nhất định
innerHTMLmà không phảiinnerTexthaytextContentsetHTMLlà phương án thay thế choinnerHTMLsetHTML()quá chặt hoặc quá lỏng, nhà phát triển có thể cung cấp cấu hình tùy chỉnh để tự định nghĩa các phần tử và thuộc tính HTML được phépXem thảo luận liên quan ở chuỗi này
Cuối cùng thì ở backend vẫn cần sanitize tên người dùng theo cách tiêu chuẩn, và khi xuất ra phải áp dụng HTML escape
Theo RFC 2119, đây là yêu cầu ở mức “SHOULD”
Rất vui khi thấy tính năng này xuất hiện, nhưng có lẽ sẽ mất thời gian để mức độ hỗ trợ của trình duyệt đủ phổ biến
Có thể kiểm tra tình trạng hỗ trợ trên Can I use
Trong thời gian đó có thể dùng polyfill để thay thế
Tiêu đề hơi giật gân một chút
Thực ra cũng có thể triển khai sanitization bằng một hàm kiểm tra đầu vào trước khi đưa vào
innerHTMLTuy vậy, những nỗ lực kiểu này rốt cuộc vẫn tạo cảm giác như đang phát minh lại bánh xe
Ngoài ra, trên Firefox cũ thì hacks.mozilla.org thậm chí không mở được, còn trên Pale Moon hay SeaMonkey thì MDN bị hiển thị lỗi
Cứ như thể “liên minh trình duyệt” đang cố phá hỏng web vậy
Đây là lập luận còn chưa tính đến vấn đề khác biệt giữa các parser (parser differential)
Nếu dùng sai Sanitizer API thì nó có thể trở thành footgun
Đặc biệt phải cẩn thận khi dùng chế độ “remove”
Tôi nghĩ tốt hơn là chỉ dùng
setText, và hoàn toàn không cho phép người dùng thêm HTMLsetHTMLthì sẽ không phát sinh XSSNhìn vào thực tế
innerHTMLđược dùng thường xuyên, rất khó loại bỏ hoàn toànĐiều gây ấn tượng là mọi khía cạnh của truy cập mạng giờ đây đều được kiểm soát đúng cách, nên chuỗi bảo mật đã chuyển từ niềm tin vào mã nguồn sang niềm tin vào cấu hình máy chủ
Các giá trị mặc định cũng được thiết lập an toàn
Điều tôi thực sự muốn là một phần tử
<sandbox>có thể chạy mã nguy hiểm một cách an toànKhông phải sửa mã nguy hiểm, mà là cho phép chạy nó trong môi trường cô lập
iframe có hạn chế là không thể chảy cùng DOM, và trong thời đại AI cùng nội dung động ngày càng nhiều, chúng ta cần khả năng đóng gói có thể cấu hợp
Tôi thực sự thích cái tên
setHTMLUnsafeCác tính năng bảo mật sẽ thất bại nếu được thiết kế theo kiểu bắt nhà phát triển phải opt-in
Thay vào đó, cách hiệu quả hơn là khiến “con đường nguy hiểm trông thật sự nguy hiểm”
Cái tên
set_html()trực quan hơn nhiều so vớiinner_htmlAPI của JavaScript thực sự quá lộn xộn và đến lúc nào đó cần được dọn dẹp
Thảo luận lần này tập trung vào bảo mật, nhưng khi công bố API mới thì bản thân thiết kế cũng phải gọn gàng
DOM API từ trước đến nay, và cả bây giờ nữa, vẫn tạo cảm giác như được làm ra bởi “những người chưa từng thiết kế API”
Lập trình viên thập niên 90:
Lập trình viên thập niên 2020:
Chúng ta đã chẳng học được gì từ thập niên 90