参考手册阅读约 6 分钟

HTML 实体编码 vs URL 编码:关键区别

HTML 实体编码将 <、>、& 和 " 等字符转换为 HTML 实体(&lt;、&gt;、&amp;、&quot;),以便在网页中安全显示。URL 编码则将字符转换为 %XX 格式,以便在 URL 中安全传输。它们防范的攻击类型不同,适用的场景也各不相同。

什么是 HTML 实体编码?

HTML 实体编码(也称为 HTML 转义)会把在 HTML 中具有特殊含义的字符转换为实体引用,从而让浏览器把它们当作字面字符显示出来,而不是解析为标记。最需要编码的字符是 <、>、&、" 和 '。

如果不进行 HTML 编码,像 <script>alert('XSS')</script> 这样的字符串会被浏览器当作 JavaScript 执行。而经过编码后,它会变成 &lt;script&gt;alert(&apos;XSS&apos;)&lt;/script&gt;,并作为无害的文本显示出来。

HTML 实体可以写成两种形式:命名实体,如 &amp;、&lt; 和 &quot;;或数字实体,如 &#38;、&#60; 和 &#34;。数字实体使用字符的 Unicode 码点,可以表示任意 Unicode 字符。

<!-- HTML 编码之前 -->
<p>The formula is: x < y & y > z</p>
<!-- 浏览器会把 < 解析为一个标签的开始! -->

<!-- HTML 编码之后 -->
<p>The formula is: x &lt; y &amp; y &gt; z</p>
<!-- 浏览器显示为:x < y & y > z -->

<!-- 常见的 HTML 实体 -->
&amp;   → &   (和号 ampersand)
&lt;    → <   (小于号 less than)
&gt;    → >   (大于号 greater than)
&quot;  → "   (双引号 double quote)
&#39;   → '   (单引号 / 撇号 single quote / apostrophe)
&nbsp;  →     (不换行空格 non-breaking space)

什么是 URL 编码?

URL 编码(percent-encoding,百分号编码)会把在 URL 中不安全的字符转换为一个百分号,后面跟着两位表示字节值的十六进制数字。它由 RFC 3986 定义,确保 URL 只包含可以在互联网上安全传输的有效 ASCII 字符。

URL 编码之所以必要,是因为 URL 有严格的语法,其中某些字符充当分隔符(/、?、&、#、=)。当这些字符出现在数据中(比如查询参数的值里)时,就必须对它们进行编码,以防被解析为结构性元素。

// URL 编码示例
space     → %20
&         → %26
=         → %3D
?         → %3F
#         → %23
/         → %2F
+         → %2B

// 带有已编码查询参数的完整 URL
https://example.com/search?q=salt%20%26%20pepper
// q 的值为 "salt & pepper"

对比表

特性 HTML 实体编码 URL 编码
用途 在 HTML 页面中安全显示 在 URL 中安全传输
标准 HTML/WHATWG 规范 RFC 3986
格式 &name; 或 &#number; %XX(十六进制字节值)
空格变为 &nbsp;(不换行空格)或保持原样 %20 或 +
& 变为 &amp; %26
< 变为 &lt; %3C
防范 XSS 攻击、损坏的 HTML 损坏的 URL、注入攻击
适用场景 HTML 文档正文和属性 URL 路径、查询字符串、片段
JS 函数 无内置函数(需使用库或 DOM API) encodeURIComponent()

什么时候两者都需要?

有一些常见情况需要同时用到 HTML 编码和 URL 编码。最常见的场景是把 URL 放入 HTML 属性中,例如 href 或 src 属性。此时必须先对 URL 进行正确的 URL 编码,然后再对结果中任何特殊的 HTML 字符进行 HTML 编码。

<!-- 第 1 步:对查询参数的值进行 URL 编码 -->
<!-- 值:"Tom & Jerry" → URL 编码后:"Tom%20%26%20Jerry" -->

<!-- 第 2 步:构建 URL -->
<!-- URL:https://example.com/search?q=Tom%20%26%20Jerry -->

<!-- 第 3 步:对 URL 进行 HTML 编码,以便用在 href 属性中 -->
<a href="https://example.com/search?q=Tom%20%26%20Jerry">
  Search for Tom &amp; Jerry
</a>

<!-- 在这种情况下,% 序列不需要进行 HTML 编码, -->
<!-- 因为 % 不是特殊的 HTML 字符 -->

<!-- 但如果 URL 中包含作为分隔符的 &,则必须对其进行 HTML 编码: -->
<a href="https://example.com/search?q=cats&amp;sort=name">
  Search cats sorted by name
</a>
<!-- 如果没有 &amp;,浏览器可能会把 &sort 解析为一个 HTML 实体 -->

在 React、Vue 和 Angular 等现代框架中,当你使用模板表达式或 JSX 时,HTML 编码会被自动处理。不过,在用动态数据构建 URL 时,URL 编码仍然需要显式进行。

// React —— HTML 编码是自动的,URL 编码不是
function SearchLink({ query }) {
  // URL 编码必须显式进行
  const url = '/search?q=' + encodeURIComponent(query);

  // JSX 中的 HTML 编码是自动的
  return <a href={url}>Search for {query}</a>;
  // React 会自动对 href 和文本内容进行 HTML 编码
}

XSS 防护:在什么场景使用哪种编码

跨站脚本(XSS)防护要求根据用户输入所处的上下文,应用正确的编码。在特定上下文中使用错误类型的编码,起不到任何保护作用。

  • HTML 正文上下文: 使用 HTML 实体编码。这可以防止像 <script> 这样被注入的标签被解析为标记。
  • HTML 属性上下文: 使用 HTML 实体编码,并始终为属性值加引号。未加引号的属性即使经过 HTML 编码也可能被突破。
  • URL 上下文(href、src): 对动态部分使用 URL 编码,如果要放入 HTML 中,再对整个 URL 进行 HTML 编码。要验证 URL 以安全的协议(https:)开头,以防出现 javascript: 这类 URL。
  • JavaScript 上下文: 使用 JavaScript 字符串转义(JSON.stringify 或专门的库)。在 JavaScript 上下文中,HTML 编码和 URL 编码都不够用。
  • CSS 上下文: 使用 CSS 转义。HTML 编码和 URL 编码都无法防范 CSS 注入。

关键原则是:针对输出上下文进行编码,而不是针对输入来源。同一段用户输入,根据它在响应中出现的位置,可能需要不同的编码。切勿仅依赖输入验证来防范 XSS;一定要应用输出编码。

相关文章

试用我们的免费工具