Bộ mã hóa / Giải mã thực thể HTML
Chuyển đổi các ký tự đặc biệt thành các thực thể HTML và ngược lại.
Các thực thể HTML phổ biến
| Ký tự | Thực thể | Số | Mô tả |
|---|---|---|---|
| & | & | & | Dấu ampersand |
| < | < | < | Nhỏ hơn |
| > | > | > | Lớn hơn |
| " | " | " | Dấu ngoặc kép |
| ' | ' | ' | Dấu nháy đơn |
| |   | Khoảng trắng không ngắt | |
| © | © | © | Bản quyền |
| ® | ® | ® | Nhãn hiệu đã đăng ký |
| ™ | ™ | ™ | Nhãn hiệu thương mại |
| € | € | € | Ký hiệu euro |
Tại sao sử dụng các thực thể HTML?
Các ký tự như <, > và & có ý nghĩa đặc biệt trong HTML. Nếu bạn đưa chúng theo nghĩa đen vào HTML của mình, trình duyệt diễn giải chúng như là mã, không phải nội dung. Mã hóa các ký tự này thành các thực thể tránh các vấn đề về kết xuất và các lỗ hổng cross-site scripting (XSS).
Câu hỏi thường gặp
Sự khác biệt giữa các thực thể được đặt tên và số là gì?
Các thực thể được đặt tên sử dụng các tên mô tả (&, ©) trong khi các thực thể số sử dụng các điểm mã Unicode (&, ©). Cả hai hiển thị giống nhau. Các thực thể được đặt tên dễ đọc hơn; các thực thể số hoạt động cho bất kỳ ký tự Unicode nào.
Tôi có nên mã hóa tất cả các ký tự đặc biệt không?
Tối thiểu, bạn phải mã hóa &, <, > và " trong nội dung HTML và các giá trị thuộc tính. Các trình duyệt hiện đại xử lý hầu hết các ký tự khác một cách bản địa nếu tài liệu của bạn sử dụng mã hóa UTF-8.
Ba dạng, một ký tự
Một «entity» HTML, chính thức là một character reference: là một chuỗi thoát biểu diễn một ký tự bằng một chuỗi ký tự ASCII thông thường. HTML Living Standard định nghĩa ba dạng cú pháp cụ thể:
- Đặt tên:
&theo sau bởi tên từ bảng named-character-reference của WHATWG, kết thúc bằng dấu chấm phẩy. Ví dụ:©cho ©. - Số thập phân:
&#theo sau bởi code point Unicode ở cơ số 10, kết thúc bằng dấu chấm phẩy. Ví dụ:©. - Số thập lục phân:
&#xtheo sau bởi code point ở cơ số 16, kết thúc bằng dấu chấm phẩy. Ví dụ:©.
Cả ba dạng đều có thể thay thế nhau khi kết xuất: ©, ©, và © đều tạo ra ký tự hiển thị © vì tất cả đều giải quyết thành code point Unicode U+00A9. Sự lựa chọn giữa chúng là vấn đề tính dễ đọc của mã nguồn, không phải hành vi trình duyệt. Hex có xu hướng khớp với các bảng Unicode đã xuất bản (dùng ký hiệu U+XXXX), vì vậy ♥ gần với ký hiệu chính thức U+2665 hơn ♥. Tham chiếu số hoạt động với mọi ký tự Unicode, bao gồm emoji astral-plane, 😀 kết xuất thành 😀 (U+1F600 GRINNING FACE).
Tại sao entity tồn tại
Ba lý do lịch sử và thực tế riêng biệt:
- Để thoát các ký tự có ý nghĩa cú pháp trong HTML. Parser dùng một số ký tự ASCII làm ký hiệu điều khiển.
<mở thẻ,>đóng thẻ,&bắt đầu một tham chiếu, và các ký tự nháy phân tách giá trị thuộc tính. Nếu bạn muốn bất kỳ ký tự nào trong số đó xuất hiện như văn bản gốc, bạn phải thoát chúng. - Để biểu diễn các ký tự không có trong bảng mã tài liệu. Trước khi UTF-8 trở nên phổ quát trên web (chỉ vượt mức 50% vào khoảng năm 2010), hầu hết HTML được phục vụ dưới dạng US-ASCII, ISO-8859-1 hoặc Windows-1252. Trong các bảng mã một byte đó, các ký tự như ©, €, ≈, hay α đơn giản không thể được biểu diễn bằng một byte gốc. Viết
©,€, hoặc≈là cách duy nhất để tiếp cận các code point đó. - Để báo hiệu ý định tác giả cho các ký tự vô hình hoặc mơ hồ. Ngay cả trên trang UTF-8, một dấu cách không ngắt gốc (U+00A0) trông giống hệt dấu cách thông thường, viết
làm rõ ý định cho bất kỳ ai đọc nguồn.
W3C hiện khuyến nghị dùng ký tự Unicode gốc ở đâu có thể thay vì entity, «for accessibility and readability.» Entity vẫn hữu ích cho năm ký tự thoát bắt buộc, cộng thêm các ký tự thực sự vô hình hoặc mơ hồ.
Năm ký tự bắt buộc
Năm ký tự bạn nhất thiết phải thoát khi chèn nội dung không tin cậy vào HTML là <, >, &, ", và '. Cheat Sheet Ngăn chặn Cross-Site Scripting của OWASP liệt kê chúng là tập thoát tối thiểu cần thiết:
| Ký tự | Đặt tên | Thập phân | Hex |
|---|---|---|---|
| < | < | < | < |
| > | > | > | > |
| & | & | & | & |
| " | " | " | " |
| ' | ' / ' | ' | ' |
Quy tắc cơ bản: bất cứ khi nào bạn đặt văn bản không tin cậy vào đầu ra HTML, hãy thoát năm ký tự này trước. Không làm vậy là nguyên nhân gốc rễ của phần lớn các lỗ hổng XSS lưu trữ và phản chiếu.
Bẫy dấu nháy đơn
' không phải phần của HTML 4, nó ban đầu chỉ được định nghĩa bởi XML 1.0 và được kế thừa vào XHTML 1.0. Internet Explorer trước phiên bản 9 (phát hành năm 2011) từ chối kết xuất nó thành ' và sẽ hiển thị văn bản gốc '. Entity này được thêm vào HTML5 cụ thể và giờ an toàn trên mọi trình duyệt hiện đại, nhưng để tương thích tối đa giữa các trình duyệt và đặc tả, OWASP và hầu hết thư viện khử trùng doanh nghiệp vẫn khuyến nghị phát ra ' thay vì ' khi thoát dấu nháy đơn, đặc biệt trong code quan trọng bảo mật.
Khi nào cần mã hóa và khi nào không
Quyết định mã hóa phụ thuộc vào nơi văn bản sẽ đặt vào đầu ra, không phải nội dung của nó. Đây là điểm dễ hiểu nhầm nhất trong bảo mật HTML. Hướng dẫn của OWASP phân biệt các ngữ cảnh:
- Nội dung phần tử HTML: thoát
< > & "(và'cho an toàn thêm). - Giá trị thuộc tính HTML: thoát tương tự cộng thêm ký tự nháy; luôn dùng thuộc tính có nháy.
- Ngữ cảnh JavaScript: dùng JavaScript escape, không phải HTML escape:
\xHHhoặc\uHHHH. - Ngữ cảnh CSS: dùng CSS escape:
\HHtheo sau bởi một dấu cách. - Ngữ cảnh URL / tham số URI: dùng percent-encoding (
%HH), không phải HTML encoding.
Mỗi ngữ cảnh có quy tắc thoát riêng. Trộn lẫn chúng bản thân là một lỗ hổng, ví dụ, percent-encoding < thành %3C không bảo vệ chống XSS trong ngữ cảnh phần tử HTML, nơi %3C chỉ là văn bản gốc %3C.
Tránh mã hóa kép. Lỗi phổ biến là thoát dữ liệu khi đọc vào hệ thống, rồi khi lưu trữ, rồi khi đọc ra, và rồi khi kết xuất. Kết quả: người dùng nhập 5 < 10, cơ sở dữ liệu lưu 5 &lt; 10, trang hiển thị 5 < 10 thay vì bản gốc. Nguyên tắc là: lưu Unicode gốc, mã hóa một lần, vào thời điểm đầu ra, cho ngữ cảnh cụ thể.
HTML encoding so với URL encoding
Hai hệ thống thoát khác nhau cho hai ngữ cảnh khác nhau, thường xuyên bị nhầm lẫn:
| Thực thể HTML | URL / phần trăm | |
|---|---|---|
| Tiêu chuẩn | HTML Living Standard | RFC 3986 |
| Định dạng | &name; hoặc &#NN; | %HH (byte hex) |
| Ngữ cảnh | HTML markup, thân phần tử và thuộc tính | URL, query string, thân request được form-encoded |
| Dấu cách | (không ngắt), không bao giờ dấu cách thường | %20 hoặc + |
| Hàm JS | - (parser xử lý) | encodeURIComponent() / encodeURI() |
URL bên trong giá trị thuộc tính HTML được áp cả hai lớp thoát: percent-encoding cho các ký tự không hợp lệ URL trước, rồi HTML encoding cho bất kỳ & < > " nào trong URL kết quả. Đây là lý do tại sao các dấu & trong query string bên trong thuộc tính href trở thành & trong HTML serialisation.
Lịch sử ngắn
HTML 2.0 (RFC 1866, 1995) kế thừa cơ chế entity của SGML với khoảng 50 entity đặt tên cho ISO Latin 1. HTML 3.2 (W3C, tháng 1 năm 1997) bổ sung các entity toán học và ký hiệu. HTML 4.01 (W3C, tháng 12 năm 1999) hoàn thiện ba bộ entity (Latin-1, Special và Symbol) tổng cộng 252 entity đặt tên, đây là nguồn gốc của con số «252» vẫn thấy trong các hướng dẫn cũ. HTML5 / WHATWG (Living Standard, đang tiếp tục) hấp thụ và mở rộng đáng kể bảng lên hơn 2.000 mục, chủ yếu để bao gồm MathML và bộ Unicode rộng hơn. XML 1.0 (1998) định nghĩa tập tối thiểu riêng chỉ gồm Năm ký tự bắt buộc (< > & " '), tập tối thiểu đó là nguồn gốc của '.
Câu hỏi thêm
Trong code hiện đại, tôi thực sự nên dùng gì?
Code production thường không tự viết entity encoding, nó gọi thư viện. DOMPurify để khử trùng HTML phía client. html.escape() trong thư viện chuẩn Python. htmlspecialchars() trong PHP. html/template trong Go (tự động thoát theo mặc định). OWASP Java Encoder cho Java. Trong React, viết <div>{userInput}</div> tự động thoát; đường thoát dangerouslySetInnerHTML được đặt tên để không khuyến khích sử dụng bình thường. Một encoder độc lập như thế này hữu ích như công cụ kiểm tra tỉnh/debug, không phải thay thế cho các thư viện đó.
Còn các thẻ bên trong email template thì sao?
Các email client cũ hơn (đặc biệt là Outlook) diễn giải & không được mã hóa là một thuộc tính không đúng định dạng và có thể loại bỏ markup xung quanh. Nhà phát triển email HTML học cách mã hóa entity mọi ký tự đặc biệt một cách phòng thủ. Tương tự áp dụng cho các hệ thống kiểu BBCode diễn đàn viết lại nội dung trước khi lưu; round-trip có thể đưa vào entity gốc không mong muốn.
textContent so với innerHTML trong JavaScript là gì?
Quy tắc ngăn chặn XSS quan trọng nhất trong vanilla JavaScript: dùng element.textContent = userInput thay vì element.innerHTML = userInput. Đặt textContent viết chuỗi như văn bản gốc, trình duyệt xử lý tất cả thoát ký tự nội bộ. Đặt innerHTML phân tích chuỗi như HTML, thực thi bất kỳ thẻ <script> hay thuộc tính event-handler nào nó chứa. Nếu markup thực sự cần thiết, hãy dùng thư viện như DOMPurify để khử trùng trước.
Bộ mã hóa có xử lý emoji không?
Có, qua tham chiếu số. Không có entity đặt tên cho emoji, tất cả đều dùng dạng số. 😀 kết xuất thành 😀, ❤️ thành trái tim đỏ ❤️ (code point trái tim cộng emoji presentation selector). Trình duyệt xử lý chuyển đổi surrogate-pair UTF-16 ngầm nội bộ; bạn không nên viết các surrogate half thủ công.
Có dữ liệu nào được gửi lên máy chủ không?
Không. Mã hóa và giải mã là các biến đổi chuỗi hàm thuần túy chạy hoàn toàn trong trình duyệt của bạn qua JavaScript. Không có gì về đầu vào của bạn được tải lên; trang hoạt động ngoại tuyến sau khi đã tải. Điều này quan trọng vì các encoder dựa trên đám mây round-trip test payload của bạn có thể tự trở thành vector XSS nếu trang kiểm tra bị xâm phạm.