前端编码如何防XSS

wen 网络安全 28

本文目录导读:

前端编码如何防XSS

  1. 核心原则:上下文敏感编码(Context-Aware Encoding)
  2. 具体防御措施(按优先级排序)
  3. 常见误区与避坑
  4. 总结:前端编码防 XSS 清单

前端防御 XSS(跨站脚本攻击)的核心原则是:不要信任任何用户输入,对所有输出进行编码

XSS 主要分为三种类型:反射型存储型DOM 型,前端编码防御主要针对的是将不可信数据插入到 HTML 上下文时的处理。

以下是前端编码防 XSS 的实战指南和最佳实践:

核心原则:上下文敏感编码(Context-Aware Encoding)

同样是用户数据,插入到 HTML标签体HTML属性JavaScript代码URL 中,需要的编码方式完全不同,绝对不能使用单一的转义函数。

上下文 (Context) 编码规则 示例 风险点
HTML元素内容 HTML 实体编码 < 转为 &lt;> 转为 &gt; 插入到 <div>用户数据</div>
HTML属性 属性值编码 将 转为 &quot;, 转为 &#x27; 插入到 <input value="用户数据">
JavaScript Unicode 转义 将 转为 \x27, 转为 \x22 插入到 <script>let x = '用户数据';</script>
CSS CSS 转义 非常复杂,强烈建议避免 插入到 style="background: url(用户数据)"
URL URL 百分号编码 将空格转为 %20, 转为 %22 插入到 <a href="用户数据">

具体防御措施(按优先级排序)

A. 使用成熟的框架(最推荐)

现代前端框架(如 React、Vue、Angular)在默认情况下会自动进行 HTML 编码,这是最强大的防线。

  • React: 在 JSX 中,默认使用 {变量} 渲染时,React 会自动对内容进行 HTML 实体转义。
    • 正确<div>{userInput}</div> ✅ (安全)
    • 危险<div dangerouslySetInnerHTML={{__html: userInput}} /> ❌ (必须确认内容是可信的)
  • Vue: 使用 双花括号插值时会转义。
    • 正确<div>{{ userInput }}</div>
    • 危险<div v-html="userInput"></div>
  • Angular: 使用 或 [property] 绑定时会转义。
    • 危险<div [innerHTML]="userInput"></div> ❌ (需使用 DomSanitizer 后且非常谨慎)

只要不使用 innerHTMLouterHTMLv-htmldangerouslySetInnerHTML 等方法,框架本身能解决 90% 的 XSS 问题。

B. 纯前端手动编码(无框架时)

如果必须手动处理,不要自己写转义函数,使用标准的库。

  1. 浏览器内置 API(推荐):

    • textContent 代替 innerHTML: 设置文本内容时,使用 element.textContent = userInput;,浏览器会自动处理编码。
    • setAttribute 代替字符串拼接属性: 使用 element.setAttribute('href', userInput);,但对于 URL 属性(如 hrefsrc),仅靠这个不够,需要额外校验协议。
    • createElementappendChild: 创建 DOM 节点并追加,而不是直接设置 HTML 字符串。
  2. 成熟的编码库:

    • 使用专门的库,如 DOMPurify(用于清理 HTML)、he(HTML 实体编码)。
    • 示例 (DOMPurify):
      import DOMPurify from 'dompurify';
      let clean = DOMPurify.sanitize(dirtyInput);
      document.getElementById('app').innerHTML = clean; // 仅在需要渲染富文本时使用

C. 严格的 CSP(内容安全策略)—— 纵深防御

即使代码在编码上出了 Bug,CSP 可以作为最后一道防线,通过 HTTP 响应头配置。

  • 关键指令:
    • script-src 'self': 只允许加载同源的脚本,禁止内联脚本(<script>alert(1)</script>)和 eval()
    • object-src 'none': 禁止 <object><embed><applet>
  • 示例: Content-Security-Policy: default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'

D. 特殊场景处理

  1. URL 插入 (<a href="">, <img src="">):

    • 必须进行协议验证,防止执行 javascript:alert(1) 攻击。
    • 正确做法
      function sanitizeUrl(url) {
        // 只允许 http, https, mailto 等安全协议
        const allowed = /^(https?|ftp|mailto|tel|#):/i;
        if (allowed.test(url)) {
          return url;
        }
        // 如果不匹配,返回空或安全的默认值
        return 'about:blank';
      }
  2. 富文本编辑(允许用户输入 HTML):

    • 绝对不要直接拼接字符串
    • 必须使用经过严格测试的库进行白名单过滤
    • 推荐DOMPurify(最流行,GitHub 17k+ stars)或 sanitize-html(Node.js 端)。
    • 不推荐:自己写正则过滤黑名单(如过滤 <script>),攻击者很容易绕过(如使用 <img src=x onerror=alert(1)>)。
  3. JSON 嵌入到 <script>:

    • 场景:服务端将 JSON 数据直接写在 HTML 页面中(如 SSR)。
    • 风险:JSON 字符串中包含 </script>,会提前闭合标签造成 XSS。
    • 防御:使用正规的 JSON 序列化库(如 JSON.stringify),并在字符串中正确转义 <>
  4. React dangerouslySetInnerHTML / Vue v-html / Angular [innerHTML]:

    • 核心铁律:传递给这些属性的内容,必须先用 DOMPurify 清洗。
    • // React 示例
      import DOMPurify from 'dompurify';
      const sanitizedHTML = DOMPurify.sanitize(userProvidedRichText);
      return <div dangerouslySetInnerHTML={{ __html: sanitizedHTML }} />;

常见误区与避坑

  1. 误区:在服务端用 HttpOnly Cookie 保护了 Token,前端就不用防 XSS 了。
    • 真相:XSS 不仅能偷 Cookie,还能窃取页面内容、伪造请求、键盘记录,即使 HttpOnly 防住了 Cookie,攻击者依然可以执行转账操作。
  2. 误区:使用 encodeURIencodeURIComponent 就能防住所有 XSS。
    • 真相:它们只对 URL 中的特殊字符进行百分号编码,如果直接把编码后的字符串插入到 <div> 标签体中,<> 仍然是危险的。
  3. 误区:过滤掉 <script> 标签就安全了。
    • 真相:XSS 向量远不止 <script>,还有事件处理器(<img src=x onerror=alert(1)>)、<iframe><link><svg> 等,黑名单过滤永远有漏洞。
  4. 避免使用 eval()new Function()
    • 用户输入的数据永远不要通过 eval() 执行。

前端编码防 XSS 清单

  1. 默认防御:使用 React/Vue/Angular 框架,避免使用 innerHTML 等危险 API。
  2. 手动编码:无框架时,使用 textContentsetAttribute 或调用 DOMPurify 库。
  3. URL 处理:插入 href/src 前,校验协议只能为 https?mailto
  4. 富文本:必用白名单清洗库(DOMPurify)。
  5. 纵深防御:配置严格的 CSP 响应头,禁止内联脚本。
  6. 避免风险:禁止 eval(),谨慎使用 innerHTML

抱歉,评论功能暂时关闭!