Họ đang đo gì
Bạn có biết ranh giới bảo vệ của framework nằm ở đâu không — chỗ đó mới là chỗ lỗ hổng nằm.
Trả lời ngắn~30 giây
React escape mọi giá trị nhúng trong JSX, nên {userInput} không bao giờ chạy được script. Bốn chỗ nó không bảo vệ: dangerouslySetInnerHTML, thuộc tính href nhận javascript:, truyền dữ liệu người dùng vào thuộc tính style dạng chuỗi, và mọi thư viện bên thứ ba tự ghi vào DOM. Chỗ thứ hai đáng chú ý vì nó trông vô hại: <a href={user.website}> với website là javascript:alert(1) sẽ chạy khi bấm.
Giải thích sâu
Với dangerouslySetInnerHTML, cách xử lý đúng không phải là tránh nó — đôi khi bạn thật sự cần render HTML, ví dụ nội dung từ CMS — mà là làm sạch bằng một thư viện có danh sách cho phép, như DOMPurify hoặc sanitize-html, và làm ở phía SERVER. Làm sạch ở client nghĩa là kẻ tấn công chỉ cần gọi API trực tiếp là bỏ qua được toàn bộ lớp bảo vệ.
Lớp phòng thủ thứ hai đáng có là Content-Security-Policy. Nó không sửa lỗ hổng nhưng giới hạn hậu quả: script nội tuyến bị chặn, script chỉ chạy từ origin bạn cho phép, nên một payload lọt qua cũng khó làm được gì. Cấu hình đúng cần nonce cho script hợp lệ, và đó là việc tốn công một lần nhưng đáng.
Điều cuối cùng, và là chỗ nhiều đội chọn sai: lưu token xác thực ở đâu. localStorage đọc được bằng JavaScript, nên một XSS duy nhất là mất token. Cookie HttpOnly thì JavaScript không đọc được, nên XSS vẫn gửi được request thay người dùng nhưng không lấy được token mang đi nơi khác. Kèm SameSite=Lax để chặn CSRF. Đây là đánh đổi giữa hai nguy cơ, và câu trả lời tốt nói ra cả hai chứ không chỉ nói tên một chỗ lưu.
Câu hỏi tiếp theo họ sẽ hỏi
?CSRF còn là vấn đề với API dùng token không?
Nếu token nằm trong header Authorization và không có cookie nào tự gửi thì CSRF không áp dụng, vì trình duyệt không tự gắn header. Nếu bạn dùng cookie thì có, và SameSite là hàng phòng thủ đầu tiên chứ không phải token CSRF.
Trả lời thế này là mất điểm
- Nói “React an toàn nên không cần lo XSS”. Nó an toàn ở đường mặc định, và lỗ hổng luôn nằm ở đường ngoại lệ.