Chính sách bảo mật nội dung: Lớp phòng thủ chống mã độc trên web
17 tháng 6, 2026
Khi một trang web tải thư viện từ bên ngoài, nhúng mã theo dõi, hoặc chấp nhận nội dung do người dùng tạo, rủi ro lớn nhất thường không đến từ lỗi luật nghiệp vụ mà từ việc trình duyệt tin tưởng quá nhiều nguồn khác nhau. Chính sách bảo mật nội dung, thường gọi tắt là CSP, là cơ chế bảo mật ở phía trình duyệt giúp đội phát triển nói rõ: trang của tôi được phép tải tập lệnh, kiểu dáng, hình ảnh, phông chữ, khung nhúng và kết nối mạng từ đâu.
CSP không phải lớp thay thế cho xác thực, phân quyền, kiểm thử bảo mật hay HTTPS. Nó giống một hàng rào vận hành cho trình duyệt: khi mã độc tìm cách chèn thêm thẻ tập lệnh, trình duyệt có thể từ chối tải tài nguyên từ miền lạ; khi một thư viện cũ cố mở kết nối đến máy chủ không được phép, trình duyệt có thể chặn yêu cầu đó. Nếu cấu hình đúng, CSP làm tăng đáng kể chi phí khai thác lỗi như tiêm mã chéo, trong khi vẫn giữ trải nghiệm người dùng ổn định.
CSP hoạt động như thế nào
Mỗi chính sách gồm nhiều chỉ thị, mỗi chỉ thị kiểm soát một loại tài nguyên. Ví dụ, default-src là mặc định cho các loại chưa được ghi rõ; script-src kiểm soát tập lệnh; img-src kiểm soát hình ảnh; connect-src kiểm soát gọi fetch, kênh WebSocket và tín hiệu gửi nền; frame-ancestors kiểm soát việc trang có thể được nhúng trong khung hay không.
Một chính sách cơ bản có thể trông như sau:
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self' https://api.example.com; object-src 'none'; frame-ancestors 'none'; base-uri 'self'; upgrade-insecure-requests
Với cấu hình trên, trình duyệt chỉ cho phép tải tài nguyên từ cùng nguồn, ảnh từ cùng nguồn hoặc dữ liệu nhúng, và kết nối API chỉ đến miền đã khai báo. object-src 'none' vô hiệu hóa các thành phần plug-in lỗi thời, còn base-uri 'self' ngăn mã độc thay đổi gốc liên kết tương đối. Đây chưa phải chính sách hoàn hảo cho mọi hệ thống, nhưng là điểm xuất phát dễ hiểu.
Bắt đầu bằng chế độ báo cáo
Đừng áp CSP khóa chặt ngay trong lần đầu. Nếu làm vậy, bạn có thể vô tình chặn mã phân tích, tiện ích trò chuyện, biểu mẫu thanh toán hoặc thư viện giao diện mà không biết. Cách an toàn hơn là chạy chế độ báo cáo trước bằng phần đầu HTTP Content-Security-Policy-Report-Only. Chế độ này không chặn tài nguyên, nhưng gửi báo cáo khi một tài nguyên vi phạm chính sách.
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: https://images.example.com; connect-src 'self' https://api.example.com; report-uri /api/csp-report;
Ở phía máy chủ, bạn cần cung cấp điểm cuối nhận báo cáo, lưu lại miền bị chặn, loại tài nguyên, trang vi phạm và thời điểm xảy ra. Sau vài ngày hoặc vài tuần, hãy gom các vi phạm theo mẫu lặp lại. Có thể một đường dẫn từ nhà cung cấp quảng cáo bị thiếu, một kiểu dáng nội tuyến do framework tạo ra, hoặc một đoạn mã theo dõi chạy trên trang cảm ơn sau khi thanh toán. Việc đọc báo cáo giúp bạn phân biệt nhu cầu thật với mã thừa.
Từ báo cáo đến khóa chặt
Khi đã hiểu tài nguyên nào cần thiết, hãy chuyển sang Content-Security-Policy thật. Nên siết từng nhóm tài nguyên thay vì thay toàn bộ chính sách cùng lúc. Bắt đầu với default-src 'self', sau đó mở từng chỉ thị riêng cho hình ảnh, phông chữ, kết nối API và dịch vụ bên ngoài. Cách này giúp bạn tránh bẫy nguy hiểm: dùng quá nhiều ngoại lệ khiến CSP mất tác dụng.
Một số nguyên tắc hữu ích:
- Ưu tiên miền cụ thể thay vì ký tự đại diện quá rộng.
- Tách miền tĩnh, miền API và miền phân tích thành các chỉ thị riêng.
- Kiểm tra cả môi trường phát triển, kiểm thử và sản phẩm vì mỗi môi trường có thể dùng tài nguyên khác nhau.
- Ghi lại lý do mỗi ngoại lệ tồn tại, để khi bảo trì không ai thêm nguồn mới chỉ vì tiện tay.
- Theo dõi tỷ lệ báo cáo sau mỗi lần triển khai, vì thay đổi giao diện có thể sinh ra vi phạm mới.
Nếu chính sách quá lỏng, ví dụ cho phép script-src * hoặc style-src 'unsafe-inline' ở mọi nơi, CSP vẫn có giá trị giới hạn nhưng khả năng ngăn mã độc giảm mạnh. Mục tiêu không phải viết một chính sách thật ngắn, mà viết một chính sách đủ rõ để người đọc hiểu hệ thống đang tin tưởng những ai.
Xử lý mã nội tuyến
Mã nội tuyến là phần khó nhất khi triển khai CSP. Nhiều trang có đoạn tập lệnh khởi tạo ứng dụng, mã theo dõi, hoặc mã do công cụ xây dựng giao diện sinh ra. Thay vì cho phép unsafe-inline, hãy dùng nonce hoặc băm. Nonce là chuỗi ngẫu nhiên được tạo mới cho mỗi phản hồi, gắn vào thẻ tập lệnh hợp lệ, rồi được khai báo trong chính sách. Chỉ những thẻ có nonce khớp mới được thực thi.
Ví dụ:
<script nonce="v4R1d0m">
window.initApp();
</script>
Và trong phần đầu HTTP:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-v4R1d0m'; style-src 'self'
Nonce phải được tạo ở phía máy chủ, không dùng chuỗi cố định, không lưu trong bộ nhớ đệm công khai, và không tái sử dụng giữa các phiên. Nếu hệ thống dùng bộ đệm trang tĩnh, hãy tách phần chứa nonce ra khỏi nội dung có thể đệm. Với mã không đổi, băm là lựa chọn khác, nhưng nó kém linh hoạt hơn vì mỗi lần thay mã là phải cập nhật chính sách.
Tích hợp vào quy trình vận hành
CSP nên được xem như cấu hình sản phẩm, không phải tùy chọn trang trí. Trong luồng triển khai, hãy thêm bước kiểm tra phần đầu trả về, kiểm tra phản hồi lỗi, phản hồi chuyển hướng và phản hồi từ máy chủ biên. Nhiều lỗi xảy ra vì trang chính có CSP đúng, nhưng trang đăng nhập, trang lỗi 404 hoặc miền phụ lại thiếu phần đầu.
Bạn cũng nên đưa CSP vào tài liệu kiến trúc phía trình duyệt. Khi thêm thư viện bên ngoài, người phát triển cần biết phải khai báo miền mới ở đâu, ai duyệt ngoại lệ, và báo cáo vi phạm được gửi về hệ thống nào. Với ứng dụng nhiều đội, một chính sách chung có thể không phù hợp cho mọi trang. Khi đó, hãy dùng chính sách mặc định an toàn, rồi chỉ nới theo từng tuyến đường hoặc từng tính năng khi có lý do rõ ràng.
Những lỗi thường gặp
Lỗi phổ biến đầu tiên là nhầm giữa chặn tải tài nguyên và chặn nội dung người dùng. CSP không tự làm sạch dữ liệu nhập; bạn vẫn cần mã hóa đầu ra, xác thực và phân quyền. Lỗi thứ hai là quá phụ thuộc vào unsafe-eval. Một số thư viện cũ dùng cơ chế đánh giá mã để biên dịch mẫu hoặc biểu thức, nhưng việc cho phép unsafe-eval làm suy yếu khả năng chống mã độc của script-src. Nếu thư viện bắt buộc dùng cơ chế này, hãy cân nhắc thay thế hoặc cô lập nó trong khung riêng.
Một lỗi khác là quên frame-ancestors. Chỉ thị này bảo vệ trang khỏi bị nhúng vào miền khác để thực hiện tấn công nhấp lừa hoặc giao diện giả mạo. Với trang đăng nhập hoặc trang quản trị, frame-ancestors 'none' thường là lựa chọn hợp lý. Cuối cùng, đừng quên cập nhật chính sách khi thay đổi nhà cung cấp ảnh, dịch vụ thanh toán, hoặc công cụ phân tích. CSP chỉ hiệu quả khi được bảo trì như một phần của vòng đời phần mềm.
Kết luận
Chính sách bảo mật nội dung là một trong những lớp phòng thủ thực dụng nhất cho ứng dụng web hiện đại. Nó không yêu cầu thay đổi toàn bộ kiến trúc, nhưng buộc đội phát triển hiểu rõ luồng tài nguyên của hệ thống. Nếu bắt đầu bằng chế độ báo cáo, loại bỏ dần ngoại lệ nguy hiểm và giám sát vi phạm sau mỗi lần triển khai, CSP sẽ trở thành công cụ vận hành bền vững chứ không chỉ là một phần đầu bảo mật.