HTML escape/unescape khi debug entity | Khác XSS sanitize thế nào【2026】
Escape/unescape là thao tác debug entity, không phải tường lửa XSS. Dùng khi log/CMS hiện <div> hoặc &amp; và bạn cần đọc / đối chiếu một lớp encoding. Chống XSS thật sự = escape đúng context khi render hoặc sanitize allowlist nếu bắt buộc HTML giàu — cộng CSP. HTML Escape trên Kawa: escape/unescape local trên trình duyệt, không thay DOMPurify hay policy bảo mật của app.
Sinh viên và freelancer Việt Nam hay gặp cảnh này trên Slack: khách gửi screenshot CMS — tiêu đề hiện nguyên &amp; Khuyến mãi <b>50%</b>. Ai đó bảo “escape lại là xong”, người khác bảo “cài thư viện XSS”. Hai việc khác mục đích.
Bài này đi theo góc escape/unescape để debug entity nhanh, phân biệt rõ với sanitize chống XSS, kèm ví dụ double-encode và checklist context (body / attribute / JS).
Bài viết này giúp bạn
- Hiểu entity (
<,&,",') và vì sao chúng xuất hiện - Phân biệt escape debug vs sanitize XSS
- Nhận double-encode (
&amp;) và cách gỡ từng lớp - Biết khi nào không được unescape rồi nhét lại
innerHTML - Dùng tool trình duyệt trong 30 giây khi review bug
Escape là gì — và không phải gì
Escape (mã hóa entity)
Đổi ký tự có nghĩa trong HTML thành dạng an toàn để hiển thị như chữ:
| Ký tự | Entity thường dùng |
|---|---|
& | & |
< | < |
> | > |
" | " |
' | ' hoặc ' |
Ví dụ:
Input: <script>alert('x')</script>
Escape: <script>alert('x')</script>
Trình duyệt không chạy script — nó vẽ ra chữ <script>.... Đó là đúng khi bạn muốn text, không phải HTML.
Unescape (giải mã entity)
Làm ngược lại: < → <. Hữu ích khi đọc dữ liệu đã entity-hóa. Không biến chuỗi không tin cậy thành HTML an toàn.
Sanitize (chống XSS) — lớp khác
Sanitize giữ subset HTML (ví dụ <p>, <strong>) và loại <script>, onerror=, javascript:. Thư viện kiểu DOMPurify làm việc này. Escape thuần không cho phép “HTML giàu an toàn” — nó biến mọi thứ thành text.
| Thao tác | Mục đích | Khi nào dùng |
|---|---|---|
| Escape | Text an toàn trong HTML | Render user input dạng chữ; chuẩn bị snippet demo |
| Unescape | Đọc lại bản rõ từ entity | Debug CMS/API/log bị encode |
| Sanitize | HTML giàu nhưng cắt payload | Rich text editor, comment cho phép thẻ giới hạn |
Góc debug: khi nào bấm Unescape trước
1. Log hoặc email hiện entity
User name: Nguyễn & Co.
Unescape (hoặc decode đúng charset) để xác nhận tên thật trước khi đổ lỗi font tiếng Việt. Lưu ý: một số chuỗi là decimal/hex numeric character reference (ễ) — tool escape cơ bản có thể không phủ hết mọi dạng; khi đó cần decoder Unicode chuyên biệt.
2. CMS / headless API double-encode
Pipeline điển hình:
- Editor lưu
<b>Sale</b> - Middleware escape →
<b>Sale</b> - Template escape lần nữa →
&lt;b&gt;Sale&lt;/b&gt;
Màn hình khách thấy chữ <b>... thay vì in đậm. Unescape một lần trên HTML Escape giúp thấy lớp giữa; sửa bằng cách bỏ một lớp escape trong code — không escape thêm lần ba.
3. So sánh snapshot test
Test snapshot bắt entity; mắt người khó đọc. Unescape tạm để review diff nội dung, rồi giữ snapshot dạng escaped nếu đó là contract API.
4. Copy từ DevTools “Copy outerHTML”
Đôi khi bạn copy nhầm chuỗi đã escape trong attribute. Unescape để lấy payload gốc trước khi tái hiện bug.
Context quyết định — escape “đúng chỗ”
Cùng một chuỗi user, context khác → rule khác:
| Context | Cách an toàn phổ biến | Sai thường gặp |
|---|---|---|
| Text node HTML | Escape & < > (framework thường làm sẵn) | innerHTML = userText không escape |
Attribute value="..." | Escape cả dấu " | Chỉ escape < rồi vỡ attribute |
| URL query | Percent-encode, không chỉ HTML entity | Nhét entity vào URL rồi expect server hiểu |
JS trong <script> | JSON.stringify / CSP | Unescape HTML rồi nối chuỗi JS |
React / Vue: nội dung trong {user} / {{ user }} thường đã escape. Nguy hiểm khi dangerouslySetInnerHTML / v-html với HTML từ CMS — lúc đó cần sanitize, không phải chỉ bấm Escape trên tool rồi dán vào.
Công cụ Kawa làm gì (và không làm gì)
HTML Escape hỗ trợ:
- Escape:
& < > " '→ entity (& < > " ') - Unescape: các entity trên (+
') → ký tự gốc - Chạy 100% trên trình duyệt — phù hợp snippet nội bộ
Không phải:
- Bộ lọc XSS đầy đủ / allowlist thẻ
- Decoder mọi numeric entity Unicode tiếng Việt
- Thay CSP, HttpOnly cookie, hay audit bảo mật
Workflow gợi ý:
- Dán đoạn nghi ngờ
- Unescape nếu đang đầy
</&— đọc nội dung - Escape một lần nếu cần entity sạch để nhúng text
- Quay lại code: sửa chỗ encode thừa hoặc chỗ render sai context
Case study
Outsourcing: form liên hệ hiện thẻ <br>
Khách nhập xuống dòng; backend lưu có <br>; frontend escape → hiện chữ <br>. Product muốn xuống dòng thật.
- Sai: unescape rồi
innerHTML— mở XSS nếu ai đó nhập<img onerror=...>. - Đúng hơn: lưu newline thuần, render bằng CSS
white-space/ tách đoạn; hoặc sanitize allowlist chỉ<br>/<p>.
Bug &amp; trên title SEO
Title trong DB đã là A & B. Layer OGP escape → A & B. Layer theme escape lại → A &amp; B trên tab trình duyệt.
Debug: unescape dần trên tool → thấy chỉ cần một lớp escape ở biên HTML. Bookmark checklist “ai đang escape?” cho team.
Checklist 5 phút khi thấy entity lạ
- Đây là text cần hiện nguyên hay HTML có chủ đích?
- Đếm lớp: có
&amp;/&lt;không? - Unescape trên tool để đọc — không deploy chuỗi unescape vào
innerHTML. - Xác định context: body, attribute, URL, hay JS.
- Framework đã escape sẵn chưa? Có
v-html/dangerouslySetInnerHTMLkhông? - Cần HTML giàu → sanitize + CSP; chỉ cần chữ → escape một lần đúng chỗ.
Câu hỏi thường gặp
Escape khác sanitize thế nào?
Escape → text entity. Sanitize → HTML giàu đã cắt payload. Unescape chỉ để đọc/debug.
Khi nào unescape?
CMS/API/log đã entity-hóa; double-encode; review snapshot. Không dùng unescape như “vá XSS”.
Chỉ escape < > có đủ?
Không đủ mọi context — attribute và JS cần rule riêng; framework text node thường đủ hơn innerHTML thủ công.
Tool có upload dữ liệu không?
Kawa HTML Escape chạy local trên trình duyệt.
Thấy &amp; thì sao?
Escape hai lần — gỡ một lớp trong pipeline, đừng escape thêm.