JavaScript 中预防 XSS(跨站脚本攻击)

在 JavaScript 中预防 XSS(跨站脚本攻击)是 Web 开发中最关键的安全措施之一。XSS 攻击的本质是攻击者将恶意代码(通常是 JavaScript)注入到网页中,并被其他用户的浏览器执行。

以下是预防 XSS 的主要措施,从最有效到辅助措施排列:

一、 核心原则:对不可信数据进行处理

所有预防措施都围绕一个核心原则:绝不信任用户输入。任何来自用户、第三方或数据库的数据在动态插入到页面之前,都必须被视为不可信的,并经过适当的处理。


二、 具体措施与最佳实践

1. 转义(Escaping) - 最根本的防线

转义是将数据中的危险字符转换为安全的字符实体(HTML Entities),从而避免浏览器将其解析为 HTML 或 JS 代码。

  • 针对 HTML 上下文:当你要将数据插入到 HTML 标签内部或属性中时。

    • 方法:将以下字符进行转义:

      • & -> &
      • < -> &lt;
      • > -> &gt;
      • " -> &quot;
      • ' -> &#x27; (或 ',但后者在 HTML 4 中不推荐)
      • / -> &#x2F; (有助于预防闭合标签)
    • 实践

      • 使用成熟的库,如 DOMPurify 的内部方法,或者简单的工具函数。
      • 现代前端框架(如 React, Vue, Angular)默认已经自动进行了转义 ,这是它们最大的安全优势之一。除非你使用 dangerouslySetInnerHTML (React) 或 v-html (Vue) 等故意绕过转义的 API。
  • 针对 HTML 属性上下文:同上,确保属性值用引号括起来,并转义引号。

  • 针对 JavaScript 上下文 :当你要将数据插入到 <script> 标签或事件处理程序(如 onclick)中时。

    • 方法:极其危险,应尽量避免。如果必须,确保数据被正确编码为 JSON 字符串。
    • 实践 :使用 JSON.stringify() 将数据转换为字符串,而不是用字符串拼接的方式构造 JS 代码。
  • 针对 URL 上下文 :当你要将数据作为 URL 参数(如 hrefsrc 属性)时。

    • 方法 :使用 encodeURIComponent() 对参数值进行编码。切勿使用 encodeURI(),它编码的范围更小

    • 例子

      javascript 复制代码
      // 错误做法
      let userInput = 'javascript:alert("xss")';
      aTag.href = userInput; // 危险!
      
      // 正确做法:验证协议并编码
      if (userInput.startsWith('https://') || userInput.startsWith('http://')) {
        aTag.href = userInput; // 允许,因为是安全协议
      } else {
        aTag.href = '#'; // 或者禁用
      }

2. 内容安全策略(CSP - Content Security Policy)

CSP 是一个强大的、深度防御的 HTTP 响应头。它不直接修复漏洞,而是通过白名单机制告诉浏览器允许加载和执行哪些资源。

  • 工作原理:即使攻击者成功注入了脚本,如果该脚本不在 CSP 白名单内,浏览器也会拒绝执行。

  • 常见指令

    http 复制代码
    Content-Security-Policy: default-src 'self'; script-src 'self' https://trusted.cdn.com; object-src 'none'; style-src 'self' 'unsafe-inline';
    • default-src 'self':默认只允许加载同源资源。
    • script-src 'self':只允许执行同源的脚本。可以添加可信的 CDN。
    • script-src 'unsafe-inline':允许内联脚本(如 <script>...</script>onclick 属性)。强烈不建议开启,这会大大削弱 CSP 的效果。现代开发应避免使用内联事件处理程序。
    • object-src 'none':完全禁止 <object>, <embed>, <applet> 等,减少攻击面。
    • style-src 'self' 'unsafe-inline':允许同源和内联样式(很常见,风险相对较低)。

3. 输入验证与过滤

在数据入库或进行处理之前,对其进行严格的验证。

  • 类型验证:确保数据符合预期的格式(如邮箱、电话号码、数字等)。
  • 长度限制:防止过长的输入。
  • 白名单原则:只允许已知安全的字符通过,比黑名单(试图过滤危险字符)要有效得多。例如,用户名字段可以只允许字母、数字和特定符号。
  • 注意 :输入验证不能替代输出转义。因为数据的用途可能会改变(比如原本在文本上下文的数据后来被用到了 HTML 上下文)。

4. 使用安全的 DOM API

避免使用那些容易引发 XSS 的旧 API,转而使用更安全的现代 API。

  • 避免使用

    • element.innerHTML = userData; // 极其危险!
    • document.write(userData); // 极其危险!
    • eval(userData); // 绝对禁止!
    • setTimeout(userData, 100); // 危险!
  • 推荐使用

    • element.textContent = userData; // 安全!它会将内容作为纯文本插入,不会被解析为 HTML。
    • element.setAttribute('alt', userData); // 相对安全,但最好还是对 userData 进行转义。
    • 操作 innerHTML 时,务必先对 userData 进行净化(见下一条)。

5. 净化(Sanitization) - 当需要富文本时

有时你的应用需要用户输入 HTML(如博客评论、富文本编辑器)。这时转义会破坏所需的格式,你需要的是"净化"。

  • 方法:使用一个强大的、专门的白名单净化库来过滤用户输入的 HTML。

    • 首选库DOMPurify 。它是一个轻量、快速且经过严格测试的库。它会移除所有危险的标签和属性,只保留安全的子集(如 <b>, <i>, <a href="...">)。
  • 实践

    javascript 复制代码
    // 使用 DOMPurify
    const cleanHTML = DOMPurify.sanitize(dirtyHTML);
    div.innerHTML = cleanHTML; // 现在安全了

    切勿尝试自己用正则表达式写一个 HTML 净化器! HTML 的语法非常复杂,很容易被绕过。

防止攻击者通过 XSS 窃取用户的 Cookie(特别是 Session ID)。

  • HttpOnly:这是最重要的属性。设置了 HttpOnly 的 Cookie 无法通过 JavaScript 的 document.cookie API 访问,从而有效防止被窃取。
  • Secure:确保 Cookie 只通过 HTTPS 协议传输。
  • SameSite:可以设置为 StrictLax,防止 CSRF 攻击,并在一定程度上增加 XSS 的利用难度。

示例 Set-Cookie 响应头:

http 复制代码
Set-Cookie: sessionId=abc123; Secure; HttpOnly; SameSite=Strict

总结

措施 描述 适用场景
转义 (Escaping) 将危险字符转换为实体 所有动态内容输出到 HTML 时
CSP HTTP 头,定义资源白名单 所有生产环境应用,深度防御
输入验证 检查数据格式和长度 数据录入阶段,减少无效数据
安全 DOM API 使用 textContent 而非 innerHTML 插入纯文本内容时
净化 (Sanitization) 使用 DOMPurify 过滤 HTML 必需处理富文本内容时
HttpOnly Cookie 防止 JS 读取敏感 Cookie 所有身份验证相关的 Cookie

最有效的策略是 "默认转义" + "CSP" 的组合。现代前端框架帮你处理了大部分转义工作,而 CSP 则作为最后一道坚固的防线。对于富文本等特殊情况,则毫不犹豫地使用 DOMPurify

相关推荐
我是天龙_绍2 小时前
🐴 记住了,节流(throttle)与防抖(debounce)
前端
凡二人2 小时前
Flip-js 优雅的处理元素结构变化的动画(解读)
前端·typescript
争当第一摸鱼前端2 小时前
Electron中的下载操作
前端
sjin2 小时前
React 源码 - Commit Phase 的工作细节
前端
FisherYu2 小时前
AI环境搭建pytorch+yolo8搭建
前端·计算机视觉
学前端搞口饭吃3 小时前
react reducx的使用
前端·react.js·前端框架
aidingni8883 小时前
掌握 JavaScript 中的 Map 和 Set
前端·javascript
开心不就得了3 小时前
React 进阶
前端·javascript·react.js
Olrookie3 小时前
ruoyi-vue(十四)——前端框架及package.json,vite.config.js, main.js文件介绍
前端·笔记