Một lỗi chèn HTML (HTML injection) lưu trữ trên bảng tin tuyển dụng RamenHire đã cho phép bất kỳ ai cũng có thể điền vào một biểu mẫu công khai và biến các email thông báo của đội ngũ thành một nền tảng lừa đảo (phishing).

Lỗi này đã lọt qua như thế nào

RamenHire chạy trên Next.js và Supabase, cho phép các startup đăng tin tuyển dụng mà không cần đăng nhập. Mỗi lần gửi biểu mẫu sẽ kích hoạt một email gửi đến đội ngũ tuyển dụng nội bộ. Nội dung email được xây dựng bằng cách ghép các trường dữ liệu thô từ biểu mẫu—tên công ty, mô tả công việc, tên người liên hệ—trực tiếp vào một chuỗi HTML. Không có quá trình escape (thoát ký tự) hay sanitisation (làm sạch dữ liệu) nào diễn ra trước khi chuỗi này được gửi đến dịch vụ email.

Vì mẫu (template) coi dữ liệu đầu vào của người dùng là HTML an toàn, một ứng viên có ý đồ xấu có thể chèn một liên kết giả mạo kiểu "nhấp vào đây để xác minh". Payload này được lưu trữ trên máy chủ như một phần của tin tuyển dụng, vì vậy mọi email thông báo sau đó đều mang theo mã độc. Kẻ tấn công có thể ẩn liên kết đó ngay cạnh nút "View Dashboard" thật, đánh lừa thành viên trong đội ngũ tiết lộ thông tin đăng nhập hoặc truy cập vào một trang web lừa đảo.

Tại sao điều này lại quan trọng

Các email này được gửi đến đội ngũ tuyển dụng nòng cốt, những người thường xuyên nhấp vào các liên kết để điều phối ứng viên qua các giai đoạn tuyển dụng.

Lỗ hổng kỹ thuật

Việc escape HTML không chỉ là một bước đơn giản. Có hai ngữ cảnh cần được bảo vệ:

  • Text nodes – nội dung nằm giữa các thẻ. Việc chuyển đổi < thành &lt;> thành &gt; sẽ ngăn chặn các thẻ, nhưng không ngăn được kẻ tấn công thoát ra khỏi giá trị của một thuộc tính.
  • Attribute values – các chuỗi nằm trong thẻ như href="…". Việc chèn một dấu ngoặc kép (" hoặc ') cho phép kẻ tấn công đóng thuộc tính sớm và chèn mã markup của riêng chúng.

Mã nguồn ban đầu chỉ xử lý văn bản thuần túy (plain text), khiến việc chèn thuộc tính (attribute injection) hoàn toàn không được ngăn chặn.

Khắc phục vấn đề

Nhà phát triển đã thêm một hàm bổ trợ escapeHtml nhỏ và áp dụng nó cho mọi giá trị do người dùng cung cấp trước khi nội suy (interpolation), bất kể giá trị đó nằm trong một text node hay một thuộc tính.

Các bước xác minh

  • Local testing – Một loạt các chuỗi chèn mã đã được đưa vào các hàm template. Mỗi lần kiểm tra đều chỉ hiển thị các ký tự đã được escape, không có thẻ thực thi nào.
  • Production testing – Sau khi triển khai bản vá, một payload đã được gửi qua trang web thực tế, vượt qua được bước kiểm tra bot của Cloudflare Turnstile. Email kết quả đã gửi đến hộp thư của nhà phát triển; liên kết độc hại và bất kỳ thẻ script nào đều hiển thị dưới dạng văn bản bị lỗi, chứ không phải là các thành phần có thể nhấp vào.

Lưu ý về Gmail: dịch vụ này có thể vẫn tô màu xanh cho URL và làm cho nó có thể nhấp vào được, nhưng đó chỉ là sự tiện lợi ở phía client và không có nghĩa là việc chèn HTML đã thành công. Bản vá giúp ngăn chặn việc chèn mã tạo ra các nút bấm thật hoặc chạy các đoạn script.

Những điều nhà phát triển cần lưu ý

  • Không bao giờ tin tưởng dữ liệu từ các biểu mẫu công khai – Ngay cả những biểu mẫu trông có vẻ vô hại cũng có thể tạo ra dữ liệu lưu trữ được sử dụng ở nơi khác.
  • Escape tại thời điểm sử dụng – Áp dụng việc escape theo ngữ cảnh (văn bản so với thuộc tính) ngay trước khi render, thay vì thực hiện sớm hơn trong quy trình.
  • Kiểm tra cả phía client và phía server – Unit test giúp phát hiện các lỗi hiển nhiên; kiểm tra thủ công end-to-end thông qua giao diện người dùng thực tế sẽ xác nhận các biện pháp phòng thủ vẫn hoạt động ổn định dưới lưu lượng truy cập thực.
  • Lưu ý các đặc điểm kỳ lạ của các trình duyệt email – Một số trình duyệt email tự động tạo liên kết cho URL, tạo ra cảm giác an toàn giả tạo. Hãy xác minh rằng mã HTML bên dưới không chứa bất kỳ thành phần hoạt động (active elements) nào.

Kết luận

Một sơ suất duy nhất trong việc xử lý dữ liệu đầu vào của người dùng đã biến các email thông báo thông thường thành một vector tấn công lừa đảo. Việc áp dụng một quy trình escape HTML toàn diện và xác thực bản vá ở cả môi trường local lẫn production đã khôi phục tính toàn vẹn cho hộp thư đến. Sự việc này nhấn mạnh một bài học vượt thời gian cho bất kỳ ứng dụng web nào tạo HTML từ dữ liệu không đáng tin cậy: việc làm sạch dữ liệu (sanitisation) đúng cách là điều bắt buộc, và việc phớt lờ nó có thể mở đường trực tiếp đến hộp thư của người dùng của bạn.