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.

Không có dữ liệu nào rời khỏi thiết bị của bạn

Các thực thể HTML phổ biến

Ký tựThực thểSốMô tả
&&&Dấu ampersand
<&lt;&#60;Nhỏ hơn
>&gt;&#62;Lớn hơn
"&quot;&#34;Dấu ngoặc kép
'&apos;&#39;Dấu nháy đơn
 &nbsp;&#160;Khoảng trắng không ngắt
©&copy;&#169;Bản quyền
®&reg;&#174;Nhãn hiệu đã đăng ký
&trade;&#8482;Nhãn hiệu thương mại
&euro;&#8364;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ả (&amp;, &copy;) trong khi các thực thể số sử dụng các điểm mã Unicode (&#38;, &#169;). 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ể:

Cả ba dạng đều có thể thay thế nhau khi kết xuất: &copy;, &#169;, và &#xA9; đề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 &#x2665; gần với ký hiệu chính thức U+2665 hơn &#9829;. Tham chiếu số hoạt động với mọi ký tự Unicode, bao gồm emoji astral-plane, &#x1F600; 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:

  1. Để 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.
  2. Để 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 &copy;, &euro;, hoặc &#8776; là cách duy nhất để tiếp cận các code point đó.
  3. Để 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 &nbsp; 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ênThập phânHex
<&lt;&#60;&#x3C;
>&gt;&#62;&#x3E;
&&amp;&#38;&#x26;
"&quot;&#34;&#x22;
'&apos; / &#39;&#39;&#x27;

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

&apos; 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 &apos;. 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 &#39; thay vì &apos; 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:

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 &amp;lt; 10, trang hiển thị 5 &lt; 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ể HTMLURL / phần trăm
Tiêu chuẩnHTML Living StandardRFC 3986
Định dạng&name; hoặc &#NN;%HH (byte hex)
Ngữ cảnhHTML markup, thân phần tử và thuộc tínhURL, query string, thân request được form-encoded
Dấu cách&nbsp; (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 &amp; 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 (&lt; &gt; &amp; &quot; &apos;), tập tối thiểu đó là nguồn gốc của &apos;.

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ố. &#x1F600; kết xuất thành 😀, &#x2764;&#xFE0F; 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.

Công cụ liên quan