Bạn mở DevTools và phát hiện có những đoạn script đang chạy mà bạn chưa bao giờ viết? Hoặc user báo cáo trang web “hành động lạ” - đó có thể là dấu hiệu XSS (Cross-Site Scripting) đang xảy ra. Mã độc được chèn vào website qua form input, URL, hoặc từ bên thứ ba không an toàn, khiến dữ liệu người dùng bị đánh cắp hoặc session bị hijack.
Content Security Policy (CSP) là giải pháp giúp bạn kiểm soát chặt chẽ những nguồn tài nguyên (script, style, image…) được phép chạy trên website. Bằng cách thiết lập whitelist rõ ràng, CSP chặn các script không đáng tin cậy ngay từ trình duyệt, bảo vệ người dùng khỏi XSS và session hijacking.
#1. Vấn đề: Script độc hại chạy trên website như thế nào?
#1.1. XSS (Cross-Site Scripting) hoạt động ra sao?
Hacker chèn mã JavaScript độc hại vào website thông qua:
Input không được validate: Form comment, search box, user profile
Third-party scripts: CDN bị tấn công, thư viện không rõ nguồn gốc
Khi script này thực thi, nó có thể:
Đánh cắp cookie/session token
Redirect người dùng sang trang lừa đảo
Thay đổi nội dung trang (defacement)
Gửi dữ liệu nhạy cảm về server của hacker
Luồng tấn công XSS điển hình:
View Mermaid diagram code
sequenceDiagram
participant Hacker
participant Website
participant Victim
participant HackerServer
Hacker->>Website: Chèn script độc vào comment/form
Website->>Website: Lưu vào database (không sanitize)
Victim->>Website: Truy cập trang có script độc
Website-->>Victim: Trả về HTML kèm script độc
Victim->>Victim: Trình duyệt thực thi script
Victim->>HackerServer: Gửi cookie/session token
HackerServer-->>Hacker: Nhận được thông tin nhạy cảm
Dù bạn đã làm sạch input ở backend, vẫn có rủi ro:
Lỗi trong logic validation
Script từ third-party (ads, analytics) bị tấn công
Browser extension độc hại inject code
CSP giải quyết vấn đề này như thế nào? Nó hoạt động ở tầng trình duyệt - chặn mọi script không nằm trong whitelist, kể cả khi chúng đã lọt qua validation.
#2. Giải pháp: Content Security Policy (CSP) là gì?
CSP là một cơ chế bảo mật cho phép bạn quy định danh sách các nguồn nội dung được phép tải. Thông qua HTTP header hoặc meta tag có tên Content-Security-Policy, trình duyệt chỉ thực thi script từ những nguồn bạn tin tưởng.
Cách CSP bảo vệ website:
View Mermaid diagram code
flowchart TD
Start([Website gửi response + CSP header]) --> Browser[Trình duyệt nhận trang HTML]
Browser --> Parse[Parse HTML và CSP policy]
Parse --> Script{Gặp thẻ script}
Script --> Check{Nguồn script có trong whitelist?}
Check -->|Có: 'self', nonce, CDN tin cậy| Allow[✅ Cho phép thực thi]
Check -->|Không: inline, domain lạ| Block[❌ Chặn và báo lỗi console]
Allow --> Safe[Website an toàn]
Block --> Safe
Ví dụ thực tế:
Script từ mywebsite.com/app.js → ✅ Cho phép (nếu dùng 'self')
Script inline <script>alert('XSS')</script> → ❌ Chặn (không có nonce)
Script từ evil.com/hack.js → ❌ Chặn (không nằm trong whitelist)
Bạn có thể thêm CSP vào website bằng cách bổ sung HTTP header sau trên máy chủ (server):
1
Content-Security-Policy: script-src 'self'
script-src 'self': chỉ cho phép tải script từ cùng domain (chẳng hạn mywebsite.com).
Với thiết lập này, mọi script nội tuyến (inline script) sẽ bị chặn nếu không có thêm từ khóa hoặc cài đặt phù hợp.
Ví dụ minh họa:
1 2 3 4 5 6 7 8 9 10 11
<!DOCTYPEhtml><html><head> <metahttp-equiv="Content-Security-Policy"content="script-src 'self'"></head><body> <h1>Nội dung trang</h1><!-- Giả sử main.js nằm cùng domain --> <scriptsrc="/js/main.js"></script></body></html>
Khi chạy trang này, script tại file /js/main.js (nằm cùng domain) sẽ được tải và thực thi bình thường.
Nếu bạn thử đặt script nội tuyến hoặc tải script từ domain khác, CSP sẽ chặn và báo lỗi.
Để cho phép một đoạn mã nội tuyến an toàn, bạn có thể dùng nonce:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
<!DOCTYPEhtml><html><head><!-- script-src 'self' và nonce --> <metahttp-equiv="Content-Security-Policy"content="script-src 'self' 'nonce-ABC123'"></head><body> <h1>Nội dung trang</h1><!-- Đoạn script nội tuyến có nonce khớp với CSP --> <scriptnonce="ABC123"> console.log("Script nội bộ được phép chạy với nonce."); </script></body></html>
Khi chạy trang này, bạn sẽ thấy thông báo trong console (trình duyệt) là “Script nội bộ được phép chạy với nonce.” mà không gặp lỗi CSP.
Nếu bỏnonce="ABC123" hoặc đặt nonce sai, trình duyệt sẽ từ chối thực thi đoạn script nội tuyến.
#3.3. Tải script từ nguồn bên ngoài uy tín (ví dụ CDN jQuery)
Trong nhiều trường hợp, bạn cần tải thư viện JavaScript từ CDN. Lúc này, bạn phải cập nhật chính sách CSP để cho phép tải script từ domain của CDN đó. Ví dụ:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
<!DOCTYPEhtml><html><head><!-- Cho phép script từ chính domain ('self') và từ cdnjs.cloudflare.com --> <metahttp-equiv="Content-Security-Policy"content="script-src 'self' 'nonce-ABC123' https://cdnjs.cloudflare.com"></head><body> <h1>Trang sử dụng jQuery từ CDN</h1> <scriptsrc="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script> <scriptnonce="ABC123">$(document).ready(function() {alert("jQuery từ CDN đã được tải thành công!"); }); </script></body></html>
Kết quả: Khi bạn mở trang, nếu CSP được thiết lập đúng cách, jQuery sẽ được tải từ cdnjs.cloudflare.com và trang sẽ hiển thị một thông báo “jQuery từ CDN đã được tải thành công!”.
Bạn nên tin tưởng vào những CDN uy tín, chẳng hạn cdnjs.cloudflare.com, cdn.jsdelivr.net, hoặc những nhà cung cấp được cộng đồng sử dụng phổ biến. Đồng thời, hãy đảm bảo không vô tình mở rộng script-src quá “rộng” (ví dụ * hoặc http://*) làm giảm hiệu quả bảo mật.
Triệu chứng: Bạn thêm nonce="ABC123" trong script, nhưng header CSP lại khai báo nonce="XYZ999". Trình duyệt chặn script.
Cách khắc phục: Đảm bảo nonce trong thẻ <script> trùng khớp với giá trị nonce trong CSP header hoặc meta tag. Nonce thường được sinh ngẫu nhiên trên server mỗi lần request.
Thỉnh thoảng, lập trình viên viết mã tự động chèn thêm script hoặc reload trang liên tục, dẫn đến vòng lặp vô hạn (infinite loop):
Nguyên nhân: Có một đoạn code cố gắng nạp lại script từ nguồn không được phép, khi bị chặn thì lại thử nạp lại vô hạn.
Phòng tránh vòng lặp vô hạn: Kiểm tra logic, hạn chế việc reload trang vô tội vạ hoặc tự chèn script động mà không kiểm soát. Đặc biệt khi dùng CSP, chỉ chèn script từ nguồn tin cậy.
Lỗi phổ biến: Dùng biến toàn cục hoặc khai báo thiếu var, let, const khiến các script “giẫm đạp” lẫn nhau, nhất là khi bị thay đổi do script ngoài ý muốn.
Liên quan đến CSP: CSP giúp chặn hoặc hạn chế script lạ, nhưng nó không thể thay thế hoàn toàn cho việc tổ chức code tốt. Nếu bạn để quá nhiều biến trong phạm vi toàn cục, một script (được phép hoặc vô tình lọt qua) cũng có thể ghi đè biến, gây lỗi hoặc lỗ hổng bảo mật.
Cách khắc phục:
Gói gọn biến trong function, module hoặc dùng cú pháp khai báo chuẩn (let, const).
Hạn chế biến toàn cục, tránh tình trạng “đụng độ” biến do các script khác nhau.
Kết hợp CSP với code “sạch” giúp tăng cường bảo mật từ hai phía:
CSP: Ngăn script độc hại từ bên ngoài.
Phạm vi biến: Giữ cho luồng thực thi bên trong ứng dụng gọn gàng, giảm nguy cơ bị ghi đè biến.
Chỉ thêm nonce hoặc hash khi thực sự cần script nội tuyến. Nếu được, bạn nên sử dụng file .js riêng để dễ quản lý và tránh phải thêm nonce/hash thủ công.
Vậy là bạn đã có giải pháp để chặn script độc hại chạy trên website. CSP hoạt động ở tầng trình duyệt, bảo vệ người dùng ngay cả khi validation backend có lỗ hổng. Trong thực tế, hãy bắt đầu với script-src 'self' rồi dần mở rộng cho CDN tin cậy - đừng dùng * vì sẽ mất hết tác dụng. Kết hợp CSP với code validation chặt chẽ, bạn có thể ngăn chặn XSS và session hijacking hiệu quả!