DOM型XSS如何规避

wen 网络安全 28

本文目录导读:

DOM型XSS如何规避

  1. 核心原则:永远不要信任不可控的输入
  2. 输入验证与净化(白名单优先)
  3. 输出编码(关键防御点)
  4. 使用安全的API和框架
  5. 内容安全策略(CSP,Content Security Policy)【纵深防御】
  6. 特定场景防护
  7. 禁止的危险模式(勿踩雷区)
  8. 一个可操作的检查清单

DOM型XSS(Cross-Site Scripting)是一种前端安全漏洞,攻击者通过操纵页面中的DOM元素(如URL参数、Fragment、本地存储、表单输入等),将恶意脚本注入到页面中并执行,要有效规避DOM型XSS,需要从输入来源输出点执行环境三个关键环节进行防护。

以下是具体的规避策略和最佳实践:

核心原则:永远不要信任不可控的输入

任何来自用户、URL、window.namelocalStoragesessionStoragepostMessageRefererCookie 等客户端来源的数据,都应被视为不可信。

输入验证与净化(白名单优先)

  • 白名单验证:只允许特定格式或字符,如果输入应该是数字或邮箱,就严格校验其格式。
  • 黑名单过滤:剔除 <script>onerrorjavascript: 等危险关键字,但黑名单容易被绕过(如大小写、编码、换行符等),所以不推荐作为唯一手段

输出编码(关键防御点)

这是最直接的防护手段,根据数据将要插入的HTML上下文,使用不同的编码方式。

  • HTML上下文:插入到 <div><p> 等标签内容中。

  • &, <, >, , 进行HTML实体编码。

  • 示例:textContent 属性自动进行HTML编码,而 innerHTML 不会。优先使用 textContent

  • 属性上下文:插入到HTML标签的属性中,如 <img src="[USER_INPUT]">

  • 除了对&, <, >, , 编码外,还需要对属性分隔符(空格、 等)进行编码。

  • 强烈建议:如果必须在属性中使用用户输入,最好使用 setAttribute() 设置安全属性(如 srchref),并确保其值不包含 javascript: 协议。避免使用 innerHTML 直接拼接属性

  • URL上下文:插入到 <a href="...">window.location 中。

  • 禁止使用 javascript: 协议,如果允许用户输入URL,必须验证协议只能是 http:https:

  • 对URL参数值进行百分比编码(encodeURIComponent)。

  • 使用 URL 构造函数或正则表达式解析并验证协议。

  • JavaScript上下文:插入到 <script> 标签或事件处理器(如 onclick)中。

  • 禁止直接拼接:永远不要像 "<script>var x = '" + userInput + "';</script>" 这样写。

  • 序列化:如果必须将数据传递给JS,使用 JSON.parse()JSON.stringify() 处理对象,或使用 encodeURIComponent() 处理简单字符串。

使用安全的API和框架

  • 避免使用 innerHTMLouterHTMLdocument.write()document.writeln(),这些方法直接解析HTML字符串。

  • 替代方案:使用 textContent(纯文本)、createElement + appendChild(动态创建节点)或模板引擎(如Handlebars、Vue、React,但需确保它们默认开启了XSS防护)。

  • 对于动态执行:避免使用 eval()setTimeout(string)Function()script.src 动态加载不可信代码。

  • 现代框架的防护:React、Vue 3、Angular 等框架默认会对模板中的变量进行HTML实体编码(除非你使用 v-htmldangerouslySetInnerHTML 等特殊指令),要避免使用这些不安全的绑定,除非你手动进行了严格净化。

内容安全策略(CSP,Content Security Policy)【纵深防御】

CSP是浏览器层面的最后一道防线,即使代码中存在缺陷,CSP也能阻止恶意脚本的执行(如禁止内联脚本、限制可加载的脚本源)。

  • 推荐策略default-src 'self'; script-src 'self' 'nonce-随机值';(禁止内联脚本,允许同源脚本,并配合Nonce机制)。
  • 如果必须允许内联脚本(如第三方统计代码),可使用 'strict-dynamic''unsafe-hash',但不推荐 'unsafe-inline'

特定场景防护

  • URL Fragment/Query参数

  • 不要从URL中提取数据后直接插入到 document.writeinnerHTML

  • 使用 URLSearchParamswindow.location.hash 获取参数后,必须先执行输出编码再使用。

  • postMessage 事件

  • message 事件处理函数中,必须验证 event.origin 是否为可信任域名

  • 对接收到的 event.data 进行严格类型检查(例如期望是JSON对象,就先用 JSON.parse 尝试解析,并捕获异常)。

  • jsonp 回调

  • 回调函数名称应严格限定为白名单中的合法函数名,不要直接拼接用户输入的参数作为回调名。

  • 存储型XSS的DOM变体

  • localStoragesessionStorage 中读取数据时,同样视为不安全的输入,输出时仍需编码。

禁止的危险模式(勿踩雷区)

  • 通过字符串拼接生成HTML代码element.innerHTML = '<img src="' + userInput + '">'
  • 通过字符串拼接生成JS代码并执行eval('var x = ' + userInput)
  • 直接使用 setTimeoutsetIntervalFunction 构造函数执行用户字符串
  • 使用 location.href 跳转到用户可控的URL(需验证协议)。

一个可操作的检查清单

  1. 识别所有输入来源:URL参数、hash、window.namelocalStoragepostMessage、表单输入、Cookie等。
  2. 选择输出上下文:数据要写入 HTML内容 / HTML属性 / URL / CSS / JS代码?
  3. 执行编码
    • HTML内容 → textContent 或 HTML实体编码器。
    • 属性值 → 属性编码 + 协议验证。
    • URL → 协议白名单 + encodeURIComponent
    • JS代码 → 禁止拼接,使用 JSON.stringifyencodeURIComponent 后再传递。
  4. 使用安全APItextContent 优先于 innerHTML;动态创建节点而非字符串拼接。
  5. 启用CSP:配置严格CSP,阻止内联脚本(除非使用Nonce)。
  6. 定期扫描:使用静态分析工具(如ESLint的 eslint-plugin-no-unsanitized 插件)或自动化安全扫描工具检测DOM XSS。

一句话原则绝不要让用户输入的数据在未经严格编码或净化的情况下,被浏览器解析为可执行的HTML代码或JavaScript代码。

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