Lỗi XSS từ file SVG – Cách xử lý an toàn khi cho phép upload SVG để tránh XSS

SVG (Scalable Vector Graphics) là định dạng hình ảnh vector phổ biến vì nhẹ, nét và dễ chỉnh sửa. Tuy nhiên, ít người biết rằng khác với PNG hay JPG thì SVG không chỉ là “hình ảnh” mà thực chất là một tệp XML có thể chứa mã JavaScript hoặc event độc hại, khiến nó trở thành một trong những vector tấn công XSS tinh vi nhất nếu không được kiểm soát đúng cách.

Trong bài viết này, chúng ta sẽ cùng tìm hiểu vì sao SVG có thể gây lỗi XSS, cách phát hiện và quy trình xử lý an toàn khi cho phép upload SVG trên website.

Vì sao SVG có thể gây lỗi XSS?

SVG thực chất là một tệp XML:

<svg xmlns="http://www.w3.org/2000/svg" onload="alert('XSS')">
<a xlink:href="javascript:alert(1)">click</a>
</svg>

Nếu website cho phép người dùng upload SVG mà không kiểm tra, kẻ tấn công có thể: Tải lên tệp .svg chứa mã độc lên website của bạn, khi quản trị viên hoặc người dùng khác mở tệp, mã JavaScript sẽ chạy ngay trong trình duyệt. Từ đó chiếm session, đánh cắp cookie, hoặc thực hiện hành động thay người dùng.

Nếu website không thực sự cần dùng SVG, hãy tắt hẳn tính năng upload SVG, các định dạng PNG hoặc WebP vẫn đáp ứng tốt nhu cầu hiển thị hình ảnh và hoàn toàn an toàn hơn.

Các hình thức tấn công lỗi XSS từ file SVG

SVG có thể chứa:

1. Inline Script & Event Handler: Các thẻ như <script> hoặc thuộc tính onload, onclick cho phép chạy JavaScript ngay khi hình hiển thị:

<svg onload="alert('XSS')"></svg>

2. Liên kết độc hại (xlink:href, data: URI): Kẻ tấn công có thể nhúng mã vào liên kết:

<a xlink:href="javascript:alert(1)">Bấm vào đây</a>

3. Thẻ nhúng HTML hoặc tài nguyên ngoài: SVG cho phép <foreignObject>, <iframe>, <embed> hoặc animation SMIL, CSS độc hại.

4. Polyglot & MIME sniffing: Một file có thể mang tên .jpg nhưng nội dung lại là SVG hoặc HTML. Nếu server không gửi đúng header (Content-Type: image/svg+xml) và thiếu:

X-Content-Type-Options: nosniff

Như vậy trình duyệt có thể đoán sai loại file và chạy JavaScript bên trong.

5. Metadata & EXIF: Bản thân metadata không độc, nhưng nếu bạn in nội dung EXIF hoặc tên file người dùng nhập mà không escape ký tự HTML, nó có thể trở thành Reflected hoặc Stored XSS.

6. Trang xem trước (Preview): Nếu website hiển thị preview ảnh và in ra caption, alt, title của user mà không escape, cũng có thể bị khai thác.

Vậy thì tại sao thẻ <img> cũng vẫn chưa đủ an toàn?

Nhiều người nghĩ rằng: <img src="evil.svg"> là an toàn — vì <img> không thực thi script. Điều này đúng phần lớn, nhưng vẫn có rủi ro nếu:

  • Bạn render lại nội dung SVG từ server ra HTML.
  • Server trả sai MIME hoặc thiếu header nosniff.
  • Một số trình duyệt cũ vẫn chạy event handler bên trong SVG.

Cách xử lý an toàn khi cho phép upload SVG để tránh XSS

Để giúp bạn tránh được lỗi XSS từ file SVG gây nguy hiểm và bảo vệ người dùng cùng hệ thống khỏi các tấn công tiềm ẩn thì websitedanang sẽ hướng dẫn cho bạn một số cách dưới đây:

1. Dùng Sanitizer chuyên dụng nếu cần cho phép

Nếu bạn buộc phải chấp nhận SVG (ví dụ: icon hoặc logo vector), hãy dùng các thư viện đáng tin cậy để làm sạch SVG (sanitize) file trước khi lưu:

  • PHP: enshrined/svg-sanitizer
  • Node.js: sanitize-svg hoặc DOMPurify với JSDOM (nếu xử lý trên server)

Các bước sanitize tối thiểu:

  1. Xóa hoàn toàn các thẻ nguy hiểm: <script>, <foreignObject>, <iframe>, <object>, <embed>, <style>
  2. Loại bỏ các thuộc tính bắt đầu bằng on (như onload, onclick, …)
  3. Xóa hoặc vô hiệu hóa liên kết chứa javascript: hoặc data: trong href/xlink:href
  4. Chuẩn hóa namespace:
  5. Giữ lại xmlns="http://www.w3.org/2000/svg"
  6. Xóa DOCTYPE hoặc external entity
  7. Chặn mọi tham chiếu ra ngoài (xlink:href="http://..." trỏ đến URL bên ngoài )

Sau khi sanitize, ghi lại file sạch, và chỉ dùng phiên bản này để hiển thị.

2. Chuyển SVG sang PNG/WebP(Rasterize)

Nếu không chắc chắn việc làm sạch đủ an toàn, hãy chuyển SVG sang PNG hoặc WebP bằng công cụ server-side. Đây là cách an toàn tuyệt đối — chuyển đổi SVG sang PNG/WebP trên server.

chuyển svg sang png/webp
Chuyển SVG sang PNG/WebP

Sử dụng công cụ:

rsvg-convert -o output.png input.svg
# hoặc
inkscape --export-type=png --export-filename=output.png input.svg

Nên chạy quá trình này trong container hoặc worker riêng, vì các công cụ xử lý ảnh như ImageMagick/InkScape cũng từng dính lỗ hổng RCE.

3. Thiết lập HTTP header bảo mật khi hiển thị SVG

Ngay cả khi đã làm sạch SVG được, bạn vẫn nên đặt các header an toàn:

add_header Content-Type "image/svg+xml";
add_header X-Content-Type-Options "nosniff";
add_header Content-Security-Policy "default-src 'self'; img-src 'self' data: https:; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; script-src 'self'";

Và nếu bạn muốn buộc trình duyệt tải file thay vì mở trực tiếp trên site cuat mình (render inline): add_header Content-Disposition "attachment";

4. Escape dữ liệu khi hiển thị

Khi hiển thị lại thông tin do người dùng nhập (như tên file, mô tả, caption, hoặc alt ảnh) — bạn cần “mã hóa” (escape) những ký tự đặc biệt để tránh trình duyệt hiểu nhầm đó là mã HTML hoặc JavaScript.

  • Cụ thể trong nội dung HTML → thay các ký tự đặc biệt: <&lt;, >&gt;, &&amp;
  • Trong thuộc tính (attribute) → mã hóa dấu ngoặc kép: "&quot;, '&#39;
  • Trong đường dẫn (URL) → dùng hàm encodeURIComponent(…) để đảm bảo URL không chứa ký tự nguy hiểm.

Nói đơn giản đó là luôn “lọc” dữ liệu đầu ra trước khi hiển thị lên trang web — vì chỉ cần một ký tự đặc biệt không được escape đúng cách, website của bạn có thể bị chèn mã XSS.

SVG là một công nghệ hình ảnh mạnh mẽ và linh hoạt cho web hiện đại — nhưng nếu không được xử lý cẩn thận, nó có thể trở thành kẽ hở bảo mật giúp hacker chèn mã độc vào website. Hãy luôn nhớ rằng: Đừng bao giờ tin tưởng dữ liệu do người dùng tải lên. Trước khi hiển thị bất kỳ tệp SVG nào, hãy kiểm tra, làm sạch hoặc chuyển đổi định dạng để đảm bảo an toàn tuyệt đối cho website của bạn.