Bảo mật frontend cơ bản: XSS, CSP và những thói quen an toàn

  • author-image

    Nguyễn Đình Cường

  • blog-tag
  • blog-comment 0 comment
  • created-date 09 Nov, 2026
blog-thumbnail

Nhiều lập trình viên frontend nghĩ bảo mật là việc của backend. Thực tế, một số lỗ hổng phổ biến nhất lại nằm ngay trong mã giao diện, và hậu quả có thể là kẻ xấu đánh cắp phiên đăng nhập hoặc thao tác thay người dùng. Bài này giới thiệu XSS và những thói quen phòng tránh thiết thực.

XSS là gì?

XSS (Cross-Site Scripting) xảy ra khi kẻ tấn công khiến trang web của bạn chạy mã JavaScript của họ trong trình duyệt của người dùng khác. Mã đó chạy với quyền của trang bạn: đọc được dữ liệu trên trang, gọi API bằng phiên đăng nhập của nạn nhân, thay đổi giao diện để lừa nhập mật khẩu.

Ví dụ kinh điển: một ứng dụng bình luận chèn nội dung người dùng gửi thẳng vào trang:

// NGUY HIỂM
document.querySelector('#binh-luan').innerHTML = noiDungNguoiDung;

Nếu noiDungNguoiDung chứa một thẻ có thuộc tính sự kiện chạy mã, mã đó sẽ thực thi khi người khác xem bình luận.

Các dạng XSS

  • Stored (lưu trữ): mã độc được lưu trong cơ sở dữ liệu (bình luận, hồ sơ) và chạy với mọi người xem.
  • Reflected (phản chiếu): mã độc nằm trong URL hoặc tham số, được trang in lại ra mà không xử lý.
  • DOM-based: mã phía trình duyệt đọc dữ liệu không đáng tin (như location.hash) rồi ghi vào DOM một cách nguy hiểm.

Nguyên tắc phòng chống

1. Coi mọi dữ liệu bên ngoài là không đáng tin

Dữ liệu từ người dùng, URL, API của bên thứ ba, thậm chí cơ sở dữ liệu của chính bạn (vì nó có thể chứa dữ liệu người dùng) đều phải được xử lý trước khi chèn vào trang.

2. Dùng API an toàn mặc định

// An toàn: chỉ là chữ, không phải HTML
el.textContent = noiDungNguoiDung;

// An toàn: tạo phần tử rồi gán thuộc tính
const a = document.createElement('a');
a.textContent = ten;
a.href = url;       // vẫn cần kiểm tra url, xem bên dưới

Hãy tránh innerHTML, outerHTML, insertAdjacentHTML, document.write và eval với dữ liệu không đáng tin.

3. Tin vào cơ chế thoát ký tự của framework, nhưng biết giới hạn

React, Vue, Angular mặc định thoát ký tự khi bạn chèn biến vào template, nên {noiDung} trong React an toàn. Rủi ro quay lại khi bạn dùng các "lối thoát" như dangerouslySetInnerHTML trong React hay v-html trong Vue. Chỉ dùng chúng khi thật cần, và với HTML đã được làm sạch.

4. Làm sạch HTML khi buộc phải hiển thị HTML

Nếu cần hiển thị nội dung giàu định dạng từ người dùng (bài viết, bình luận), hãy làm sạch bằng thư viện uy tín như DOMPurify, thay vì tự viết biểu thức chính quy. Tốt nhất là làm sạch cả ở máy chủ khi lưu và ở nơi hiển thị.

import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(htmlTuNguoiDung);

5. Kiểm tra URL

Một URL dạng javascript:... đặt trong href có thể chạy mã khi bấm. Khi nhận liên kết từ người dùng, chỉ chấp nhận giao thức an toàn:

function laUrlAnToan(url) {
  try {
    const u = new URL(url, window.location.origin);
    return ['http:', 'https:', 'mailto:'].includes(u.protocol);
  } catch {
    return false;
  }
}

Content Security Policy (CSP)

CSP là header HTTP cho trình duyệt biết nguồn nào được phép tải script, ảnh, kiểu dáng. Nó là lớp phòng thủ thứ hai: ngay cả khi có lỗi XSS, trình duyệt có thể từ chối chạy script không nằm trong danh sách cho phép.

Content-Security-Policy: default-src 'self'; script-src 'self'; img-src 'self' https:; object-src 'none'

Ví dụ trên chỉ cho phép script cùng nguồn gốc, từ chối script nhúng trực tiếp trong HTML (inline) nếu không có cơ chế nonce hoặc hash. Triển khai CSP cần thử nghiệm kỹ, bạn có thể bắt đầu bằng chế độ chỉ báo cáo (Content-Security-Policy-Report-Only) để xem cái gì sẽ bị chặn trước khi áp dụng thật.

Lưu trữ phiên đăng nhập

Mã thông báo (token) lưu trong localStorage hay sessionStorage mà mã JavaScript đọc được thì sẽ bị lấy nếu có XSS. Nhiều đội ưu tiên cookie với các thuộc tính HttpOnly (JavaScript không đọc được), Secure (chỉ gửi qua HTTPS) và SameSite (giảm nguy cơ CSRF). Mỗi cách đều có đánh đổi, hãy chọn dựa trên kiến trúc của bạn và hỏi đội backend.

Những thói quen an toàn khác

  • Không đặt khóa bí mật (API secret) trong mã frontend; ai cũng xem được.
  • Cập nhật thư viện thường xuyên và chạy npm audit để phát hiện lỗ hổng đã biết.
  • Cẩn thận khi thêm script của bên thứ ba; nó chạy với toàn quyền trên trang. Dùng thuộc tính Subresource Integrity (integrity) khi nạp từ CDN.
  • Với liên kết mở tab mới, dùng rel='noopener noreferrer' cùng target='_blank'.
  • Nhớ rằng kiểm tra ở frontend chỉ phục vụ trải nghiệm; mọi quyết định bảo mật phải được kiểm tra lại ở máy chủ.

Kết luận

Hãy ghi nhớ: không tin dữ liệu bên ngoài, ưu tiên textContent và cơ chế thoát ký tự của framework, làm sạch HTML bằng thư viện uy tín, kiểm tra URL, dùng CSP như lớp bảo vệ bổ sung và giữ bí mật ở máy chủ. Bảo mật là thói quen hằng ngày, không phải tính năng thêm vào cuối.

Để xây dựng ứng dụng web an toàn từ cả hai phía frontend lẫn backend, bạn có thể tham khảo các khóa học tại codebem.com.

author_photo
Nguyễn Đình Cường

0 comment