Cách ngăn chặn lỗi bảo mật Cross-Site Scripting XSS
Cách hiệu quả nhất để ngăn chặn Cross-Site Scripting (XSS) là mã hóa đầu ra theo đúng ngữ cảnh mỗi khi đưa dữ liệu không tin cậy vào trang, dùng framework có cơ chế tự động escape, làm sạch HTML bằng thư viện chuyên dụng khi buộc phải cho phép định dạng, và bổ sung Content Security Policy cùng cookie HttpOnly làm lớp phòng thủ thứ hai. XSS xảy ra khi trình duyệt hiểu nhầm dữ liệu người dùng là mã cần chạy; mọi biện pháp đều nhằm giữ ranh giới dữ liệu và mã cho rõ ràng.
XSS gây hại như thế nào
Khi kẻ tấn công chèn được script vào trang mà người khác xem, script đó chạy với đầy đủ quyền của trang trong trình duyệt nạn nhân. Nó có thể:
- Thực hiện hành động thay người dùng: đổi email, đổi mật khẩu, chuyển tiền, đăng bài.
- Đọc dữ liệu hiển thị trên trang: tin nhắn, thông tin cá nhân.
- Đánh cắp cookie phiên nếu cookie không có cờ HttpOnly.
- Hiển thị biểu mẫu đăng nhập giả ngay trên tên miền thật.
- Ghi lại phím gõ trên trang.
Vì chạy trên tên miền thật, XSS vượt qua mọi cảnh giác của người dùng về địa chỉ trang. Đây là lý do nó luôn nằm trong nhóm lỗi nguy hiểm của OWASP Top 10 (thuộc nhóm Injection).
Ba dạng XSS
| Dạng | Dữ liệu độc nằm ở đâu | Cách nạn nhân bị ảnh hưởng |
|---|---|---|
| Phản chiếu (Reflected) | Trong yêu cầu, ví dụ tham số tìm kiếm, được máy chủ trả lại ngay | Bấm vào link được chế tạo sẵn |
| Lưu trữ (Stored) | Được lưu vào cơ sở dữ liệu: bình luận, tên hiển thị, hồ sơ | Chỉ cần mở trang hiển thị dữ liệu đó |
| Dựa trên DOM | JavaScript phía client đọc dữ liệu (URL, fragment) và ghi vào trang | Bấm link; máy chủ có thể không hề thấy dữ liệu độc |
XSS lưu trữ thường nguy hiểm nhất vì ảnh hưởng mọi người xem, kể cả quản trị viên khi mở trang quản lý bình luận.
Biện pháp 1: Mã hóa đầu ra theo ngữ cảnh
Cùng một dữ liệu, cách mã hóa phụ thuộc vào vị trí nó được chèn:
- Nội dung HTML: mã hóa các ký tự như <, >, &, dấu nháy thành thực thể HTML.
- Giá trị thuộc tính: luôn đặt trong dấu nháy và mã hóa thuộc tính.
- Bên trong JavaScript: tránh chèn trực tiếp; nếu cần, mã hóa JSON và đặt vào thuộc tính data hoặc khối dữ liệu riêng.
- URL: mã hóa tham số và kiểm tra giao thức, chỉ cho phép http và https, chặn "javascript:".
- CSS: tránh đưa dữ liệu người dùng vào style.
Nguyên tắc: mã hóa ở thời điểm xuất ra, không phải lúc nhận vào, vì lúc nhận bạn chưa biết dữ liệu sẽ được hiển thị ở ngữ cảnh nào.
Biện pháp 2: Dùng framework đúng cách
Các framework giao diện hiện đại và template engine thường tự động escape dữ liệu khi hiển thị. Lỗ hổng xuất hiện khi lập trình viên chủ động tắt cơ chế này, qua các API như chèn HTML thô, bộ lọc "safe" hay "raw". Mỗi lần dùng những API đó cần lý do rõ ràng và dữ liệu đã được làm sạch.
Với JavaScript thuần, ưu tiên textContent thay vì innerHTML, setAttribute cho thuộc tính an toàn, và tránh eval, setTimeout với chuỗi, document.write.
Biện pháp 3: Làm sạch HTML khi cần cho phép định dạng
Trình soạn thảo bình luận, bài viết thường cần cho phép một số thẻ như in đậm, liên kết. Không tự viết bộ lọc bằng biểu thức chính quy; kẻ tấn công có vô số cách vượt qua. Dùng thư viện làm sạch HTML uy tín với danh sách thẻ, thuộc tính được phép tối thiểu, và giữ thư viện luôn cập nhật.
Biện pháp 4: Content Security Policy
CSP là header cho phép bạn khai báo nguồn script nào được chạy trên trang. Một chính sách chặt chẽ dùng nonce hoặc hash, không cho phép script nội tuyến tùy ý và không cho phép eval. Khi đó, ngay cả nếu kẻ tấn công chèn được thẻ script, trình duyệt sẽ từ chối chạy. CSP là lớp phòng thủ thứ hai, không thay thế mã hóa đầu ra.
Triển khai CSP nên bắt đầu ở chế độ report-only để thu thập vi phạm mà không làm hỏng trang, sau đó mới chuyển sang chế độ thực thi.
Biện pháp 5: Giảm thiệt hại khi XSS xảy ra
- Cookie phiên đặt cờ HttpOnly để script không đọc được, Secure và SameSite phù hợp.
- Yêu cầu xác nhận lại mật khẩu cho hành động nhạy cảm như đổi email.
- Không lưu token truy cập dài hạn trong localStorage nếu có thể tránh.
Nếu dùng JWT ở phía client, cách lưu token ảnh hưởng trực tiếp tới hậu quả của XSS; xem thêm bài triển khai JWT an toàn.
Kiểm thử XSS trên ứng dụng của bạn
- Liệt kê mọi nơi dữ liệu người dùng được hiển thị lại: kết quả tìm kiếm, hồ sơ, bình luận, thông báo lỗi, tên tệp tải lên.
- Nhập chuỗi chứa ký tự đặc biệt và quan sát mã nguồn trang: ký tự có được mã hóa không?
- Kiểm tra mã JavaScript phía client đọc từ location, document.referrer, postMessage rồi ghi vào DOM.
- Dùng công cụ quét động và phân tích tĩnh để hỗ trợ, nhưng không bỏ qua kiểm tra thủ công.
Kiểm thử chỉ thực hiện trên ứng dụng bạn sở hữu hoặc được phép. Quy trình tổng quát có trong bài các bước rà soát lỗ hổng bảo mật.
Những quan niệm sai thường gặp
- "Đã lọc thẻ script là an toàn": XSS có thể chạy qua thuộc tính sự kiện, thẻ ảnh, liên kết javascript:, SVG.
- "Chỉ quản trị viên nhập được dữ liệu này": tài khoản quản trị cũng có thể bị chiếm, và dữ liệu có thể đến từ nguồn nhập khẩu.
- "Ứng dụng nội bộ không cần lo": người dùng nội bộ vẫn mở link từ email lừa đảo.
- "HTTPS chống được XSS": HTTPS bảo vệ đường truyền, không liên quan đến việc trang chèn script.
Câu hỏi thường gặp
XSS và SQL Injection khác nhau thế nào?
Cả hai đều do dữ liệu bị hiểu nhầm thành mã. SQL Injection tấn công cơ sở dữ liệu ở phía máy chủ, còn XSS chạy mã trong trình duyệt của người dùng khác. Cách phòng chống cũng khác: tham số hóa truy vấn cho SQL, mã hóa đầu ra cho XSS.
Dùng framework giao diện hiện đại thì có còn lo XSS không?
Rủi ro giảm nhiều nhờ cơ chế tự động escape, nhưng vẫn còn khi dùng API chèn HTML thô, liên kết với giao thức javascript:, hoặc thư viện bên thứ ba thao tác DOM trực tiếp.
CSP có thay thế được việc mã hóa đầu ra không?
Không. CSP là lớp bảo vệ bổ sung giúp giảm hậu quả khi có sơ hở. Chính sách CSP cũng có thể bị cấu hình lỏng hoặc bị vượt qua, nên mã hóa đầu ra vẫn là biện pháp chính.
Self-XSS có nguy hiểm không?
Self-XSS chỉ ảnh hưởng người tự dán mã vào trình duyệt của mình, nên thường không được tính là lỗ hổng. Tuy vậy kẻ lừa đảo vẫn dụ nạn nhân dán mã vào console, vì thế nhiều trang lớn hiển thị cảnh báo trong công cụ nhà phát triển.
Bài liên quan

Bảo mật API trong môi trường phát triển ứng dụng Web
Các nguyên tắc bảo mật API cho lập trình viên web: xác thực, phân quyền theo đối tượng, kiểm tra đầu vào, giới hạn tần suất, quản lý khóa, nhật ký và kiểm thử.

Bảo mật tệp tin cấu hình nhạy cảm tránh bị lộ lọt
Cách bảo vệ tệp .env, config, khóa API, mật khẩu cơ sở dữ liệu: không đưa vào Git, chặn truy cập web, phân quyền tệp, dùng trình quản lý bí mật và xử lý khi lộ.

Các bước cơ bản để rà soát lỗ hổng bảo mật ứng dụng
Quy trình rà soát lỗ hổng ứng dụng web: xác định phạm vi, lập danh mục tài sản, quét tự động, rà soát mã, kiểm thử thủ công và theo dõi khắc phục.

Các chương trình tìm lỗi nhận thưởng Bug Bounty và lưu ý
Bug Bounty hoạt động thế nào, các dạng chương trình, quy tắc phạm vi và safe harbor, cách viết báo cáo tốt và lưu ý pháp lý cho người mới.