Một đoạn JavaScript độc hại chạy được trong trình duyệt có thể biến website của bạn thành công cụ tấn công chính khách hàng của mình. Kẻ tấn công không nhất thiết phải chiếm máy chủ, có quyền quản trị hay truy cập trực tiếp cơ sở dữ liệu.
Chỉ cần website nhận dữ liệu do người dùng nhập và hiển thị dữ liệu đó không an toàn, XSS có thể xuất hiện. Đây là lỗi bảo mật phổ biến nhưng hoàn toàn có thể giảm thiểu hiệu quả nếu bạn xử lý đúng từ đầu vào đến lúc hiển thị.
XSS là gì và vì sao nó nguy hiểm?
XSS là viết tắt của Cross-Site Scripting, một lỗ hổng cho phép kẻ tấn công chèn mã JavaScript vào nội dung mà website tin tưởng. Khi người khác mở trang có nội dung độc hại, trình duyệt sẽ thực thi đoạn mã đó như một phần hợp lệ của website.
Điểm nguy hiểm nằm ở chỗ website không bị phá từ bên ngoài theo cách dễ nhận ra. Thay vào đó, chính hệ thống của bạn vô tình phân phối mã độc tới trình duyệt của người dùng.
Khi khai thác thành công, XSS có thể dẫn đến ba nhóm hậu quả chính:
- Can thiệp giao diện: Kẻ tấn công có thể hiển thị cửa sổ giả mạo, thông báo trúng thưởng hoặc cảnh báo khẩn cấp để dụ người dùng bấm vào liên kết nguy hiểm.
- Thu thập dữ liệu nhạy cảm: Mã độc có thể lấy những dữ liệu JavaScript nhìn thấy, bao gồm cookie có thể truy cập, dữ liệu trong localStorage, token hoặc thông tin liên quan đến phiên đăng nhập.
- Thao tác thay người dùng: Nếu người dùng đang đăng nhập, mã độc có thể gửi yêu cầu thay đổi email, mật khẩu hoặc thực hiện một số hành động trong phạm vi quyền hiện có.
Vì vậy, XSS không chỉ là một lỗi giao diện. Nó có thể ảnh hưởng trực tiếp đến tài khoản khách hàng, dữ liệu doanh nghiệp và mức độ tin cậy của thương hiệu.
XSS đi vào website của bạn bằng cách nào?
Kẻ tấn công không cần yêu cầu ai mở công cụ lập trình rồi tự dán mã độc. Con đường thực tế hơn là đưa JavaScript vào một trường dữ liệu mà website sẽ nhận và hiển thị lại.
Các điểm nhập liệu quen thuộc như ô tìm kiếm, bình luận, mô tả sản phẩm, biểu mẫu cập nhật hồ sơ hoặc trình soạn thảo nội dung đều có thể trở thành điểm rủi ro. Nếu nội dung đầu vào được đưa thẳng vào HTML, trình duyệt có thể diễn giải nó như mã thay vì văn bản bình thường.
Một tình huống điển hình là ô tìm kiếm hiển thị lại từ khóa vừa nhập trên trang kết quả. Với từ khóa thông thường, mọi thứ hoạt động bình thường. Nhưng nếu giá trị này chứa nội dung có thể kích hoạt JavaScript và được render trực tiếp, người gửi chỉ cần sao chép đường dẫn chứa tham số đó rồi gửi cho nạn nhân.
Một hiểu lầm rất phổ biến là JavaScript chỉ chạy khi nằm trong thẻ script. Trên thực tế, HTML có nhiều vị trí và thuộc tính khác cũng có thể dẫn tới việc thực thi mã. Vì thế, chỉ kiểm tra hoặc chặn riêng thẻ script là không đủ.
Nguyên tắc quan trọng nhất: render văn bản, không render HTML
Biện pháp phòng chống XSS hiệu quả nhất là không hiển thị dữ liệu người dùng nhập dưới dạng HTML nếu nghiệp vụ không thật sự cần. Hãy để trình duyệt hiển thị nội dung đó như văn bản thuần.
Khi bạn render theo cách an toàn, các ký tự đặc biệt vẫn hiện nguyên dạng trên giao diện. Trình duyệt không phân tích chúng thành thẻ HTML, không chạy sự kiện và không thực thi JavaScript ẩn bên trong.
Với JavaScript thuần, bạn cần ưu tiên cơ chế gán nội dung dạng văn bản thay vì cơ chế chèn HTML. Với Vue, React hoặc các framework khác, hãy dùng cơ chế render mặc định và thận trọng với những tính năng chủ động cho phép chèn HTML trực tiếp.
Điều này đặc biệt quan trọng với các khu vực tưởng như vô hại: tên sản phẩm, từ khóa tìm kiếm, ghi chú nội bộ, tên người dùng, tin nhắn hoặc thông báo hệ thống. Mọi dữ liệu do người dùng kiểm soát đều cần được xem là không đáng tin cậy.
Khi bắt buộc phải render HTML: sanitize dữ liệu đúng chỗ
Một số tình huống không thể chỉ render văn bản thuần. Ví dụ, mô tả sản phẩm cần in đậm, in nghiêng, danh sách hoặc nội dung được tạo từ trình soạn thảo như CKEditor. Trong trường hợp này, bạn vẫn có thể cho phép HTML, nhưng tuyệt đối không render thẳng dữ liệu đầu vào.
Giải pháp là sanitize HTML, tức lọc bỏ các phần tử, thuộc tính và ký tự có khả năng dẫn đến XSS trước khi dữ liệu được hiển thị. Mục tiêu không phải xóa toàn bộ định dạng, mà là giữ lại phần HTML hợp lệ phục vụ nội dung và loại bỏ phần nguy hiểm.
Trong phần minh họa, nội dung nhập qua CKEditor được xử lý nên mã độc không chạy. Tuy nhiên, khi cùng dữ liệu được gửi bằng Postman, luồng xử lý của trình soạn thảo bị bỏ qua và XSS lại xuất hiện. Đây là lời nhắc rõ ràng rằng bạn không thể chỉ dựa vào giao diện để bảo vệ hệ thống.
CKEditor có thể hỗ trợ mã hóa hoặc lọc một số ký tự nhạy cảm, nhưng việc đó không thay thế trách nhiệm kiểm soát dữ liệu của ứng dụng. Dữ liệu có thể đến từ API, Postman, công cụ kiểm thử, tích hợp bên thứ ba hoặc bất kỳ luồng nào không đi qua giao diện chính.
DOMPurify là một thư viện phù hợp để làm sạch HTML trước khi render. Khi áp dụng, các thẻ hoặc thuộc tính nguy hiểm bị loại bỏ, còn những định dạng văn bản hợp lệ như in đậm và in nghiêng vẫn có thể được giữ lại.
Đừng chỉ lọc ở giao diện: hãy chặn dữ liệu độc hại trước khi lưu
Sanitize trước khi hiển thị là cần thiết, nhưng chưa đủ để xử lý triệt để. Nếu dữ liệu nguy hiểm đã được lưu trong cơ sở dữ liệu, nó giống như một quả bom chờ kích hoạt.
Có thể hôm nay trang chi tiết đã render an toàn, nhưng một màn hình khác trong tương lai lại lấy cùng dữ liệu đó và hiển thị thiếu kiểm soát. Khi ấy, lỗ hổng XSS sẽ xuất hiện trở lại dù phần giao diện ban đầu đã được sửa.
Vì vậy, bạn nên làm sạch dữ liệu trước khi lưu vào cơ sở dữ liệu, đồng thời vẫn duy trì cơ chế render an toàn ở đầu ra. Hai lớp này bổ trợ cho nhau, không nên thay thế lẫn nhau.
Các lớp giảm thiểu thiệt hại khi XSS xảy ra
Không có biện pháp bổ sung nào thay thế được việc sửa lỗi XSS gốc trong mã nguồn. Tuy nhiên, các lớp bảo vệ sau sẽ giúp hạn chế hậu quả nếu mã độc vẫn tìm được đường chạy trong trình duyệt.
Bật HttpOnly cho cookie đăng nhập
Cookie có cờ HttpOnly không thể bị JavaScript truy cập qua document.cookie. Điều này không ngăn XSS xảy ra, nhưng làm giảm đáng kể nguy cơ kẻ tấn công đọc và gửi cookie phiên đăng nhập ra ngoài.
Nói cách khác, mã độc có thể vẫn chạy, nhưng khả năng đánh cắp phiên đăng nhập để chiếm quyền tài khoản sẽ bị hạn chế hơn. Đây là lớp bảo vệ rất quan trọng với dữ liệu xác thực.
Không lưu token đăng nhập trong localStorage nếu có thể
Dữ liệu trong localStorage có thể được JavaScript trên trang đọc. Khi XSS xuất hiện, token lưu tại đây có nguy cơ bị lấy đi.
Bạn cần cân nhắc kiến trúc lưu trữ thông tin đăng nhập phù hợp với hệ thống, ưu tiên giảm khả năng để JavaScript độc hại tiếp cận thông tin phiên.
Dùng Content Security Policy để giới hạn JavaScript
Content Security Policy, thường gọi là CSP, là bộ quy tắc yêu cầu trình duyệt chỉ tải và chạy tài nguyên từ các nguồn được cho phép. CSP có thể giới hạn mã JavaScript chỉ được tải từ cùng tên miền hoặc các nguồn tin cậy đã khai báo.
Với chính sách phù hợp, JavaScript nội tuyến và các trình xử lý sự kiện trong HTML có thể bị chặn. Dù vậy, CSP chỉ là hàng rào giảm thiểu phạm vi tấn công, không phải lý do để bỏ qua việc xử lý dữ liệu đầu vào và render HTML an toàn.
Checklist kiểm tra XSS cho website
- Rà soát mọi vị trí nhận dữ liệu từ người dùng, bao gồm tìm kiếm, biểu mẫu, bình luận, mô tả và API.
- Render dữ liệu mặc định dưới dạng văn bản thuần.
- Không dùng cơ chế chèn HTML trực tiếp nếu không thật sự cần.
- Sanitize HTML khi cần giữ định dạng nội dung.
- Không phụ thuộc hoàn toàn vào CKEditor hoặc lớp kiểm tra ở giao diện.
- Làm sạch dữ liệu trước khi lưu vào cơ sở dữ liệu để tránh dữ liệu độc hại tồn tại lâu dài.
- Bật HttpOnly cho cookie đăng nhập và cân nhắc kỹ việc lưu token trong localStorage.
- Cấu hình CSP phù hợp để giới hạn nguồn JavaScript được phép chạy.
- Dùng công cụ như Burp Suite để kiểm thử các luồng dữ liệu ngoài giao diện thông thường.
FAQ
XSS có phải chỉ xảy ra ở ô tìm kiếm không?
Không. XSS có thể xuất hiện ở bất kỳ nơi nào website nhận dữ liệu và hiển thị lại dữ liệu đó không an toàn, như bình luận, mô tả sản phẩm, thông tin hồ sơ hoặc nội dung từ API.
CKEditor đã lọc nội dung thì có cần sanitize thêm không?
Có. Dữ liệu có thể được gửi qua những luồng khác như API hoặc Postman mà không đi qua CKEditor. Hệ thống nên tự sanitize dữ liệu thay vì phụ thuộc vào một thành phần giao diện.
HttpOnly có ngăn được XSS hoàn toàn không?
Không. HttpOnly không sửa được lỗ hổng XSS, nhưng ngăn JavaScript đọc cookie đăng nhập và giúp giảm nguy cơ bị đánh cắp phiên làm việc.
CSP có thể thay thế việc sanitize HTML không?
Không. CSP chỉ giới hạn khả năng thực thi mã và giảm mức độ ảnh hưởng. Giải pháp cốt lõi vẫn là không render HTML từ dữ liệu người dùng hoặc sanitize kỹ dữ liệu khi việc render HTML là bắt buộc.
Nếu website của bạn có biểu mẫu, CMS hoặc dữ liệu động từ nhiều nguồn, một quy trình [thiết kế website và tối ưu bảo mật](https://mintstack.vn) bài bản sẽ giúp bạn kiểm soát các điểm nhập liệu ngay từ kiến trúc hệ thống.
