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

相关推荐
知识分享小能手5 小时前
微信小程序入门学习教程,从入门到精通,微信小程序常用API(上)——知识点详解 + 案例实战(4)
前端·javascript·学习·微信小程序·小程序·html5·微信开放平台
清灵xmf6 小时前
CSS field-sizing 让表单「活」起来
前端·css·field-sizing
文火冰糖的硅基工坊6 小时前
[光学原理与应用-480]:《国产检测设备对比表》
前端·人工智能·系统架构·制造·半导体·产业链
excel6 小时前
Qiankun 子应用生命周期及使用场景解析
前端
weixin_446260856 小时前
Django - 让开发变得简单高效的Web框架
前端·数据库·django
ObjectX前端实验室7 小时前
【react18原理探究实践】异步可中断 & 时间分片
前端·react.js
SoaringHeart7 小时前
Flutter进阶:自定义一个 json 转 model 工具
前端·flutter·dart
努力打怪升级7 小时前
Rocky Linux 8 远程管理配置指南(宿主机 VNC + KVM 虚拟机 VNC)
前端·chrome
brzhang8 小时前
AI Agent 干不好活,不是它笨,告诉你一个残忍的现实,是你给他的工具太难用了
前端·后端·架构
brzhang8 小时前
一文说明白为什么现在 AI Agent 都把重点放在上下文工程(context engineering)上?
前端·后端·架构