Mộc Viên's Blog Mộc Viên's Blog
Làm thế nào để ngăn chặn các cuộc tấn công XSS (Cross-Site Scripting)?

Làm thế nào để ngăn chặn các cuộc tấn công XSS (Cross-Site Scripting)?

Ngày đăng:

Làm thế nào để ngăn chặn các cuộc tấn công XSS (Cross-Site Scripting)?

Để ngăn chặn XSS, quan trọng nhất là: không bao giờ tin dữ liệu từ người dùngluôn xử lý (encode / validate) trước khi hiển thị ra HTML / JS / URL. Mình tóm tắt theo kiểu “checklist” cho bạn dễ áp dụng trong code.


1. Hiểu nhanh XSS là gì

XSS xảy ra khi attacker chèn được một đoạn script (thường là JavaScript) vào trang web của bạn, và đoạn script đó chạy trên trình duyệt của người dùng khác.
Ví dụ: user nhập vào <script>alert('XSS')</script> mà bạn hiển thị lại thô trong HTML => script chạy.


2. Nguyên tắc vàng: Output Encoding (Escape đúng ngữ cảnh)

Khi hiển thị dữ liệu ra HTML, phải encode ký tự đặc biệt:

  • Trong HTML content: encode < > & " '

    • Ví dụ (pseudo):

      String safe = HtmlUtils.htmlEscape(userInput);
      out.println(safe);
      
  • Trong HTML attribute:

    <input value="<!-- phải escape ở đây -->">
    
  • Trong JavaScript context:

    <script>
        var msg = "<!-- phải escape để thành chuỗi JS hợp lệ -->";
    </script>
    
  • Trong URL / query string:

    <a href="/search?q=<!-- phải URL-encode -->">Link</a>
    

👉 Mỗi framework đều có cách encode sẵn:

  • React/Angular/Vue: binding bình thường thường đã escape HTML (trừ khi bạn cố tình dùng dangerouslySetInnerHTML, v-html, innerHTML…).
  • Template engine (Blade, Thymeleaf, JSP, EJS…): thường có {{ }} (escaped) và {!! !!} hoặc tương tự (raw). Luôn ưu tiên escaped.

3. Không cho phép HTML tùy ý (hoặc sanitize cẩn thận)

Nếu tính năng của bạn cho phép user nhập HTML/Rich text (editor WYSIWYG, comment có format, v.v.), thì:

  • Tránh: lưu thô HTML rồi render lại trực tiếp.

  • Nếu bắt buộc cho HTML:

    • Dùng HTML sanitizer (loại bỏ <script>, onerror, onload, javascript:…)
    • Chỉ cho phép một whitelist tag an toàn: <b>, <i>, <u>, <p>, <ul>, <li>, <a>...

Ví dụ thư viện (tùy tech stack):

  • JS: DOMPurify
  • Java: OWASP Java HTML Sanitizer
  • PHP: HTML Purifier

4. Không cho user chèn thẳng vào JavaScript

Tránh pattern kiểu:

<script>
    var name = '<?= $_GET["name"] ?>'; // RẤT nguy hiểm
</script>

Nếu cần, hãy:

  • Encode chuỗi thành JSON rồi parse:

    <script>
        const user = JSON.parse('<?= json_encode($user, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT) ?>');
    </script>
    

Hoặc lấy dữ liệu qua API (JSON) rồi render bằng JS, không nhét trực tiếp HTML từ server vào script.


5. Bật và cấu hình Content Security Policy (CSP)

CSP giúp giảm thiểu thiệt hại nếu bị chèn XSS:

  • Cấu hình header Content-Security-Policy, ví dụ đơn giản:

    Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none';
    
  • Tránh dùng unsafe-inline trừ khi rất cần.

  • Tách code JS ra file .js riêng, hạn chế <script>inline</script>.

CSP không thay thế cho việc encode, nhưng là layer phòng thủ thêm.


XSS thường được dùng để đánh cắp session cookie. Hãy:

  • Đặt cookie auth với flag:

    Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Lax
    
  • HttpOnly: JS không đọc được cookie → giảm nguy cơ bị document.cookie ăn cắp.

  • Secure: chỉ gửi qua HTTPS.

  • SameSite: giảm rủi ro CSRF.

Lưu ý: HttpOnly không ngăn được XSS, nhưng làm giảm thiệt hại nếu bị XSS.


7. Validate input, nhưng đừng nhầm với encode

Input validation không đủ để ngăn XSS nhưng vẫn cần:

  • Chặn những pattern rõ ràng nguy hiểm nếu không cần (tag, thuộc tính, protocol javascript:).
  • Giới hạn độ dài, loại ký tự cho từng field (VD: username chỉ cho [a-zA-Z0-9_], không cần < >).

Tuy nhiên, phòng XSS cốt lõi vẫn là output encoding theo ngữ cảnh.
Validation là lớp phụ, không phải “chủ lực”.


8. Không tin dữ liệu từ database / API nội bộ

Rất nhiều người nghĩ:

“Dữ liệu trong DB là sạch vì đã lưu rồi.”

Sai. Nếu lúc insert không encode, attacker chỉ cần inject 1 lần → dữ liệu độc đã nằm trong DB.
Nên khi output từ DB ra HTML vẫn phải encode.

Rule:

BẤT CỨ THỨ GÌ ĐI RA HTML/JS/URL → encode theo ngữ cảnh, bất kể nó đến từ đâu (form, DB, log, API khác…)


9. Hạn chế dùng innerHTML, v-html, dangerouslySetInnerHTML

Ở front-end:

  • Tránh element.innerHTML = userInput;
  • React: tránh dangerouslySetInnerHTML.
  • Vue: hạn chế v-html.
  • Nếu buộc phải dùng, hãy sanitize nội dung trước (DOMPurify…).

Thay vào đó:

  • Dùng binding text: textContent, {{ variable }}, v.v.

10. Kiểm thử và sử dụng các payload XSS mẫu

Khi muốn test hệ thống có bị XSS không, bạn có thể thử vài payload đơn giản:

<script>alert(1)</script>
"><script>alert(1)</script>
"><img src=x onerror=alert(1)>

Nếu những thứ này xuất hiện nguyên dạng như text → tốt.
Nếu trình duyệt thực thi cảnh báo → có lỗ hổng XSS.


11. Tổng kết theo kiểu checklist cho dev

Khi code, hãy tự check:

  1. [ ] Tất cả dữ liệu từ user/DB/3rd-party khi render ra HTML đều được encode đúng ngữ cảnh?
  2. [ ] Có dùng template engine / framework binding mặc định (escape HTML) không?
  3. [ ] Có dùng innerHTML, v-html, dangerouslySetInnerHTML → nếu có, đã sanitize chưa?
  4. [ ] Đã cấu hình CSP, HttpOnly/Secure/SameSite cho cookie auth chưa?
  5. [ ] Các input có được validate cơ bản (độ dài, loại ký tự, whitelist) không?
  6. [ ] Có test XSS với vài payload basic chưa?
Làm thế nào để triển khai một hệ thống CI/CD từ đầu?
1. Giới thiệu CI/CD (Continuous Integration / Continuous Deployment) là nền tảng của phát triển phần mềm hiện đại. Nó giúp tự động hoá việc build, test, và triển khai, giảm lỗi thủ công và tăng tốc độ phát hành. Bài viết này hướng dẫn bạn từng bước triển khai

Gần đây