DOM型XSS漏洞深度解析:从原理、手工挖掘到高级防御实践

1. 项目概述:为什么DOM型XSS值得你投入精力?

如果你是一名Web安全爱好者、渗透测试工程师,或者是一名希望写出更安全代码的前端开发者,那么“DOM型XSS”这个名词你一定不陌生。但很多时候,我们可能只是听说过它“比较隐蔽”、“绕过WAF”,对其真正的挖掘利用和防御细节却一知半解。我见过太多安全报告里,测试人员把反射型XSS的Payload往URL参数里一塞,弹个窗就完事了,但对于那些不经过服务端、纯粹在浏览器里“兴风作浪”的DOM型XSS,往往束手无策,或者干脆选择忽略。

这正是我想写这篇长文的原因。DOM型XSS(Document Object Model Cross-Site Scripting)是一种特殊类型的跨站脚本攻击,它的恶意代码从不接触服务器。攻击者精心构造的恶意数据,通过URL的片段(hash)、前端逻辑处理等方式,直接在受害者的浏览器中被解析并执行。这意味着,传统的服务端输入过滤、WAF(Web应用防火墙)很可能完全失效。对于攻击方,这是穿透防线的利器;对于防御方,这是视野之外的盲区。

从零基础到精通,这条路我走过,踩过不少坑,也积累了许多在实战中真正好用的技巧。这篇文章的目的,就是把我这些年的经验浓缩成一套可操作的方法论。我不会只给你讲几个 innerHTML eval 的案例就结束,那太浅了。我会带你深入DOM操作的核心,理解数据流如何在JavaScript中“流动”并最终变得危险,分享我手动审计和工具辅助挖掘的具体步骤,甚至聊聊那些在真实漏洞赏金(Bug Bounty)项目中,让我成功提交高危报告的“骚操作”。无论你是想系统学习前端安全,还是急需提升在渗透测试中的挖洞能力,收藏这一篇,反复实践,就够了。

2. DOM型XSS核心原理深度拆解

要精通挖掘,必须先吃透原理。很多人对DOM型XSS的理解停留在“用 location.hash 触发”,这远远不够。它的本质是: 不可信的数据源,流经了危险的JavaScript接收器(Sink),并且中间缺少了正确的编码或验证。

2.1 理解关键三要素:源、汇与数据流

这是分析任何DOM型XSS的思维框架。你可以把它想象成一条污染的河流:源头被投毒,河道没有净化处理,最终流进了居民的水库。

1. 源(Source):攻击者可控的输入点 这是恶意数据的入口。DOM型XSS的源非常多样,且常常被忽略:

  • location 对象家族 :这是最经典的源。
    • location.hash :URL中 # 后面的部分,如 https://example.com/page#<script>alert(1)</script> 。它 不会 发送到服务器。
    • location.search :URL中 ? 后面的查询字符串,如 ?id=123 。这部分通常会发送到服务器,但JavaScript可以从前端直接读取并处理,如果处理不当,同样构成DOM型XSS。
    • location.pathname :URL的路径部分。
    • document.URL / document.documentURI :完整的URL字符串。
  • document.referrer :浏览器提供的,表示当前页面来源页面的URL。攻击者可以构造一个恶意页面,链接到目标页面,从而控制 referrer 的值。
  • document.cookie :当前页面的Cookie。如果Cookie值能被攻击者部分控制(例如,通过子域名劫持、其他漏洞设置),也可能成为源。
  • Web存储 localStorage sessionStorage 。如果应用将用户输入未经处理就存入这里,并在另一个页面或同一页面后续操作中读取使用,就可能引入风险。
  • window.name :这个属性可以在页面跳转甚至跨域时保持不变,常被用于跨域通信,但也可能成为XSS的传递渠道。
  • 客户端反序列化数据 :从服务器获取的JSON数据,如果其中包含了本应由服务器净化的用户可控内容,前端直接拼接使用,也属于DOM型漏洞的范畴。

实操心得 :在审计时,不要只盯着URL参数。用浏览器的开发者工具,在Console里输入 document.cookie document.referrer localStorage.getItem(‘key’) 看看,说不定有惊喜。我曾在一个电商网站发现,它将用户搜索词用Base64编码后存到 sessionStorage ,然后在推荐模块直接 innerHTML 出来,成功构造了存储型DOM XSS。

2. 汇(Sink):危险的JavaScript函数或属性 这是执行恶意代码的地方。数据流到这里时,如果内容是HTML或JavaScript字符串,就会被浏览器解析执行。

  • HTML注入汇点
    • element.innerHTML
    • element.outerHTML
    • document.write()
    • document.writeln()
  • JavaScript代码执行汇点
    • eval() :万恶之源,直接执行字符串作为代码。
    • setTimeout() / setInterval() :第一个参数可以是字符串,会被当作代码执行。例如 setTimeout(“alert(‘xss’)”, 1000)
    • Function() 构造函数: new Function(‘alert(1)’)
    • location 相关: location.href location.assign() location.replace() ,当赋值为 javascript: 伪协议时。
  • 其他常见汇点
    • element.setAttribute(name, value) :当 name 参数可控且为 onerror onclick 等事件处理器时。
    • element.appendChild() / insertBefore() 等:如果插入的节点内容可控。
    • jQuery的 $() / html() / append() 等方法:如果未正确配置或使用旧版本。

3. 数据流(Data Flow):从源到汇的路径 这是最考验分析能力的一环。源代码往往不是 source -> sink 这么直接。它可能经过多个函数的传递、字符串的拼接、条件判断的分支。

// 一个简单的例子
function getSearchParam(key) {
    const urlParams = new URLSearchParams(location.search);
    return urlParams.get(key);
}
const userId = getSearchParam(‘id’); // 源:location.search
document.getElementById(‘user-info’).innerHTML = `用户ID: ${userId}`; // 汇:innerHTML
// 攻击:?id=<img src=x onerror=alert(1)>

在这个例子里,数据流是: location.search -> URLSearchParams.get() -> userId 变量 -> 模板字符串 -> innerHTML 。我们需要在脑海中或通过工具追踪这条路径。

2.2 与反射型、存储型XSS的本质区别

很多人会混淆,这里必须彻底厘清。假设攻击Payload是 <script>alert(‘xss’)</script>

  • 反射型XSS :Payload作为请求的一部分(如URL参数 ?q=<script>...</script> )发送到 服务器 ,服务器在响应页面中 原样反射 回这个Payload,浏览器执行。
    • 关键 :Payload出现在服务器响应体(HTML)中。你在页面源代码(View Source)里能看到它。
  • 存储型XSS :Payload被发送到服务器后, 存储 在数据库、文件等地方(如评论内容)。当其他用户访问某个页面时,服务器从存储中取出Payload并放入响应中,浏览器执行。
    • 关键 :Payload被持久化,受害者无需点击特定链接,访问正常页面即可中招。
  • DOM型XSS :Payload作为客户端JavaScript的 输入数据 (如 location.hash )。前端的JavaScript代码(可能是服务器下发的静态脚本) 读取这个输入,并用自己的逻辑 将其写入DOM的某个危险位置(汇点),导致执行。
    • 关键 :Payload 可能从未出现在服务器响应或页面源代码中 。它只存在于客户端的JavaScript执行上下文里。你必须使用浏览器的“开发者工具” -> “元素”面板,查看 实时DOM树 ,或者使用“调试器”跟踪JavaScript执行,才能看到Payload是如何被动态添加进去的。

这个区别导致了根本性的影响:

  1. 绕过服务端防护 :WAF、输入过滤规则可能完全检测不到,因为恶意请求参数(如 # 后的内容)根本不发往服务器。
  2. 检测难度大 :自动化扫描器通常通过分析HTTP请求和响应来发现漏洞。对于DOM型XSS,扫描器需要能够执行并分析JavaScript,模拟浏览器环境,技术门槛高很多。
  3. 修复责任转移 :修复漏洞的关键在于前端JavaScript代码,需要确保从“源”到“汇”的数据流经过了正确的编码或验证。

3. 手工挖掘与审计实战指南

知道了原理,我们开始动手挖。我强烈建议从手工审计开始,这能帮你建立对代码和数据流的“直觉”。

3.1 第一步:识别潜在的“源”和“汇”

打开目标网站,按下F12开启开发者工具。

1. 全局搜索“危险函数”(汇点): 在“源代码”(Sources)标签页,或者在整个工作区使用 Ctrl+Shift+F (Windows) / Cmd+Opt+F (Mac) 进行全局搜索。关键词列表如下,建议从最危险的开始:

innerHTML
outerHTML
document.write
document.writeln
eval
setTimeout
setInterval
Function
location.href
location.assign
.insertAdjacentHTML
jQuery.html
jQuery.append
jQuery.before
jQuery.after
React dangerouslySetInnerHTML
Vue v-html

每找到一个,就点击过去,看看它操作的数据是什么。

2. 回溯数据来源(找源): 当你定位到一个 innerHTML = something 时,问自己: something 这个值从哪里来?

  • 它是一个变量吗?双击变量名,查找它的定义和赋值位置。
  • 它是函数调用的结果吗?去查看那个函数的返回值。
  • 它是否来自 location.search location.hash document.referrer document.cookie
  • 它是否来自 window.name localStorage sessionStorage
  • 它是否来自 fetch() / XMLHttpRequest 从服务器获取的数据?如果是,尝试篡改API响应(通过代理工具如Burp Suite)。

3. 使用Console进行动态探测: 有时静态搜索不直观。你可以直接在Console里进行测试。

// 测试 location.hash 是否被使用
location.hash = ‘#test’;
setTimeout(() => { console.log(‘检查页面元素或网络请求,看是否有地方处理了#test’); }, 500);

// 监控DOM修改(非常有用!)
// 保存原始方法
const originalInnerHTML = Object.getOwnPropertyDescriptor(Element.prototype, ‘innerHTML’).set;
// 重写 setter,加入日志
Object.defineProperty(Element.prototype, ‘innerHTML’, {
  set: function(value) {
    console.log(‘innerHTML被设置到:’, this, ‘值:’, value);
    // 可以在这里加入debugger语句进行断点调试
    // if (value.includes(‘<script>’)) { debugger; }
    return originalInnerHTML.call(this, value);
  }
});

运行上面的代码后,操作页面,任何对 innerHTML 的赋值都会在控制台打印出来,清晰看到数据和目标元素。

3.2 第二步:构造与测试Payload

找到可疑的源-汇路径后,就需要构造Payload来验证漏洞是否存在。Payload的构造需要根据上下文(Context)来决定。

1. 确定上下文(Context): 数据最终被放入哪里?这决定了你需要闭合什么标签,或者需要如何构造JavaScript。

  • HTML上下文 :数据被放入 innerHTML outerHTML document.write 等。你需要构造一个完整的HTML标签来执行脚本。
    • 最简单: <img src=x onerror=alert(document.domain)>
    • 需要闭合前标签:如果插入点是 <div> 用户输入 </div> ,你可能需要先闭合 </div> 再开始攻击。
  • 属性上下文 :数据被放入某个HTML标签的属性值里,如 <input value=”用户输入”>
    • 你需要先闭合引号,然后添加事件处理器: ” onmouseover=”alert(1)
    • 最终效果: <input value=”” onmouseover=”alert(1)”>
  • JavaScript上下文 :数据被直接拼接进JavaScript代码字符串中,然后被 eval 或作为 setTimeout 参数执行。
    • 你需要闭合字符串和语句:假设代码是 var data = ‘用户输入’; ,Payload可以是 ’; alert(1); //
    • 最终效果: var data = ‘’; alert(1); //’;

2. 使用编码绕过简单过滤: 如果直接输入被拦截或转义了,尝试编码。

  • HTML实体编码 < 变成 &lt; > 变成 &gt; 。但如果在JS上下文中,这些编码会被解码后拼接,可能仍然有效。或者,如果网站错误地先解码再使用,就可能造成漏洞。
  • JavaScript Unicode转义 alert(1) 可以写成 \u0061\u006c\u0065\u0072\u0074(1) 。这在JS字符串中会被解释为原字符。
  • 利用浏览器解析差异 :有时标签名、属性名大小写混用或省略引号可以绕过简单的正则匹配。例如 <ScRiPt>alert(1)</sCrIpT> <img src=x onerror=alert(1)> (注意 onerror 没有引号)。

3. 利用伪协议 javascript: 常用于 location.href iframe.src a.href 等需要URL的地方。

#javascript:alert(document.domain)
?next=javascript:alert(1)

注意事项 :现代浏览器对 javascript: 伪协议在直接地址栏输入或某些上下文中的执行限制越来越严格,但在某些动态赋值场景下依然有效。

3.3 第三步:利用工具提升效率

纯手工效率低,我们需要工具辅助。但记住,工具是辅助,核心判断还得靠人。

1. 浏览器扩展:

  • DOM Invader (Burp Suite内置浏览器) :这是目前我心中最强的DOM XSS辅助工具,没有之一。它集成在Burp的浏览器中,能自动识别源和汇,并尝试自动构造Payload,可视化展示数据流,极大提升了审计效率。
  • PPScan :一款国产的浏览器插件,能被动扫描当前页面,提示潜在的DOM XSS风险点,适合在浏览网站时进行初步感知。

2. 静态代码分析工具(SAST):

  • Semgrep :可以编写自定义规则来搜索JavaScript代码中的源-汇模式。例如,一条规则可以查找所有 innerHTML 的赋值,并检查其右侧表达式是否包含来自 location 的属性。
  • CodeQL :更强大也更复杂。它可以为整个代码库建立数据流图,精准地追踪从用户输入点到危险函数的完整路径。适合对大型、重要的前端项目进行深度安全审计。

3. 动态分析工具(DAST/IAST):

  • Burp Suite Active Scan :新版Burp的主动扫描引擎已经具备了一定的DOM XSS检测能力,它会使用内置浏览器执行JavaScript并分析DOM变化。
  • Arachni、ZAP :这些开源扫描器也逐步在集成基于浏览器的扫描插件,可以尝试。

实操心得 :不要迷信工具的“未发现漏洞”报告。工具擅长找模式,但复杂的业务逻辑、需要多步交互触发的DOM XSS,还是得靠人工分析。我通常的工作流是:先用工具(如DOM Invader)快速扫一遍,标记出高风险点,然后针对这些点,结合业务逻辑进行深度手工测试。

4. 高级利用技巧与绕过手段

当你掌握了基础挖掘方法后,这些高级技巧能让你在漏洞利用上更上一层楼,特别是在面对有一定防护的网站时。

4.1 利用 AngularJS 等框架的客户端模板注入

如果网站使用旧版本的AngularJS(1.x)且未使用 ng-app 指令的 strict 模式,就可能存在客户端模板注入,这本质上是另一种形式的DOM型XSS。

原理 :AngularJS的模板语法 {{ expression }} 会在客户端被解析执行。如果用户输入被直接拼接进模板,且表达式可控,就能执行任意JavaScript。

<!-- 假设搜索框内容可控,并直接用于渲染 -->
<div> 搜索结果显示: {{ userInput }} </div>

Payload {{ constructor.constructor(‘alert(1)’ )() }} 这个Payload利用了AngularJS沙箱逃逸的技术(旧版本沙箱存在漏洞),直接调用JavaScript的 Function 构造函数执行代码。

检测方法

  1. 寻找使用了AngularJS的页面。
  2. 测试所有输入点,输入 {{ 7*7 }} 。如果页面显示 49 ,说明存在模板注入。
  3. 尝试更复杂的Payload进行利用。可以使用现成的工具如 AngularJS-sandbox-escape-cheatsheet 中的Payload。

4.2 基于 postMessage 的跨域XSS

window.postMessage() 是HTML5用于跨窗口/跨域通信的API。如果消息接收方( message 事件监听器)对消息来源( event.origin )验证不严,且直接将消息内容用于危险汇点,就可能造成XSS。

攻击场景

  1. 网站A( https://vulnerable.com )内嵌了一个来自可信域 https://trusted.com 的iframe B。
  2. B页面包含以下代码:
    window.addEventListener(‘message’, function(event) {
        // 漏洞:没有严格验证 origin!
        // if (event.origin !== ‘https://trusted.com’) return;
        document.getElementById(‘display’).innerHTML = event.data;
    });
    
  3. 攻击者可以创建一个恶意页面C,通过 iframe.contentWindow.postMessage() 向A页面中的B iframe发送消息。由于A和C同源(或B未验证origin),消息会被接收。
  4. 攻击者发送的消息 event.data 包含恶意HTML/JS,被B页面直接 innerHTML ,导致XSS在 vulnerable.com 的上下文中执行。

挖掘方法 : 在目标页面中搜索 addEventListener(‘message’) window.onmessage 。检查其回调函数中:

  1. 是否对 event.origin 进行了严格校验?(应使用白名单比对,而非黑名单或简单的 indexOf 检查)。
  2. 是否将 event.data 直接用于 innerHTML eval 等危险操作?

4.3 绕过常见的防御措施

现代前端框架和库内置了一些防护,但配置不当或版本过旧仍可绕过。

1. 绕过HTML编码: 如果服务器对输入进行了HTML实体编码(如 < 变成 &lt; ),但在前端,JavaScript又错误地进行了解码(例如使用了 innerHTML 赋值,而浏览器会自动解码实体),那么编码就失效了。关键是要找到这种“编码-解码”不一致的点。

2. 利用 <svg> <math> 等标签: 在某些防护规则或WAF中,可能只过滤了 <script> <img> 等常见标签,但忽略了 <svg> 或HTML5的 <math> 标签。这些标签内部可以包含事件处理器或 <script> 标签。

<svg onload=alert(1)>
<svg><script>alert(2)</script></svg>

3. 利用JavaScript协议和动态属性: 对于属性上下文,如果过滤了空格和引号,可以尝试利用Tab符( %09 )、换行符( %0a )进行分隔,或者利用HTML属性可以不用引号的特性(在严格模式下不推荐,但浏览器可能支持)。 ”onmouseover=alert(1) (无空格,依赖浏览器自动解析)

4. 利用 document.domain 降级与跨域: 这是一个相对古老的技巧,但在某些特定子域名配置下可能有效。如果两个页面将 document.domain 设置为相同的父域,它们就可以绕过同源策略进行通信。如果其中一个页面存在XSS,可能会影响到另一个。

5. 防御策略与安全编码实践

知道了如何攻击,才能更好地防御。如果你是开发者,这部分是你的必修课。

5.1 原则:严格实施输入验证与输出编码

这是防御所有XSS的黄金法则,对DOM型XSS同样至关重要,只是执行侧重点在客户端。

1. 输入验证(白名单原则): 在数据从“源”取出后,立即根据其预期用途进行严格验证。

  • 预期是数字 :使用 parseInt() Number() 转换,并检查 isNaN()
  • 预期是有限的枚举值 (如排序方式 asc / desc ):与白名单数组比对。
  • 预期是简单文本 :使用正则表达式移除所有非预期字符(如只保留字母、数字、空格)。
// 不好的做法:直接使用
const userInput = location.hash.substring(1);
// 好的做法:验证
const userInput = location.hash.substring(1);
if (!/^[a-zA-Z0-9\s-]+$/.test(userInput)) {
    userInput = ‘’;
}

2. 输出编码(根据上下文!): 在数据最终被写入“汇”之前,根据其所在的 上下文 进行编码。这是最关键的一步,用错编码等于没防。

  • HTML上下文 :对 < , > , & , , 进行HTML实体编码。
    • 可以使用 textContent innerText 属性代替 innerHTML ,它们会自动进行文本编码。
    • 如果必须使用 innerHTML ,务必对动态内容进行编码。可以使用 document.createTextNode() 创建文本节点,或使用可靠的库如 DOMPurify
  • 属性上下文 :对属性值中的引号进行编码。通常使用HTML实体编码即可,但要注意属性值外的引号。
  • JavaScript上下文 :这非常危险,应尽量避免将动态数据拼接进JS代码。如果必须,需进行JavaScript Unicode转义。
    • 绝对不要使用 eval()
    • 使用 JSON.parse() 解析数据,而非 eval()
    • 使用 addEventListener 绑定事件,而非 onclick=”…” 属性。

5.2 使用安全的API和框架

1. 选择安全的API:

  • element.textContent / innerText > innerHTML / outerHTML
  • element.setAttribute(‘data-xxx’, value) > 拼接字符串后设置 innerHTML
  • document.createElement() + appendChild() > innerHTML
  • addEventListener > onclick 等HTML属性

2. 利用现代前端框架的安全特性:

  • React :默认会对所有在JSX中渲染的变量进行转义。只有使用 dangerouslySetInnerHTML 时才有风险,务必对其内容进行净化。
  • Vue :默认的模板语法( {{ }} )也会进行HTML转义。只有使用 v-html 指令时才有风险,务必对其内容进行净化。
  • Angular :默认对所有插值表达式和属性绑定进行净化。使用 bypassSecurityTrust 系列方法时要极度谨慎。

3. 使用专业的净化库: 当业务确实需要渲染富文本HTML时(如博客评论、CMS后台),必须使用净化库。

  • DOMPurify :目前业界最推荐、最安全的HTML净化库。它只允许安全的HTML元素和属性通过,能有效防御XSS。
const cleanHTML = DOMPurify.sanitize(userControlledHTML);
document.getElementById(‘output’).innerHTML = cleanHTML;

5.3 部署内容安全策略

CSP (Content Security Policy) 是防御XSS的终极武器之一。它通过HTTP头告诉浏览器,哪些来源的资源(脚本、样式、图片等)是可以加载和执行的。

一个严格的CSP能极大地限制XSS的影响,甚至完全阻止它。

Content-Security-Policy: default-src ‘self’; script-src ‘self’ https://trusted.cdn.com; object-src ‘none’;

这个策略意味着:

  • default-src ‘self’ :默认只允许加载同源资源。
  • script-src ‘self’ https://trusted.cdn.com :脚本只能从同源或指定的CDN加载。 内联脚本(如 <script>alert(1)</script> )和 javascript: 伪协议都将被阻止
  • object-src ‘none’ :禁止加载 <object> , <embed> , <applet> 等,封堵一些老的攻击向量。

CSP对DOM型XSS的防御 :即使攻击者成功注入了 <script> 标签或事件处理器,如果CSP禁止了内联脚本执行,这些代码也不会被浏览器执行。但请注意,CSP配置复杂,需要仔细测试,否则可能影响网站正常功能。可以使用 Content-Security-Policy-Report-Only 头在只报告不拦截的模式下先运行一段时间。

6. 常见问题与排查技巧实录

在实际挖掘和修复过程中,你会遇到各种奇怪的问题。这里记录一些我踩过的坑和解决方法。

6.1 为什么我的Payload不弹窗?

这是新手最常见的问题。别慌,按步骤排查:

  1. 检查控制台(Console) :首先看有没有JavaScript报错。可能是语法错误,或者因为CSP被拦截。CSP违规信息通常会在控制台显示。
  2. 检查网络请求 :打开开发者工具的“网络”(Network)标签页,看看Payload是否真的被发送到了你期望的端点?对于DOM型XSS,可能根本不发送请求。
  3. 使用 alert(1) 还是 alert(document.domain) :优先使用 alert(document.domain) 。这能证明脚本是在目标网站的源下执行的,而不仅仅是触发了浏览器扩展或本地脚本。如果 alert(1) 弹窗但 alert(document.domain) 不弹,可能触发了同源策略限制或脚本执行环境有问题。
  4. 查看实时DOM :在“元素”(Elements)面板,检查你的Payload是否被正确插入到DOM树中?是否被HTML编码了(显示为 &lt; )?右键点击可疑元素,选择“编辑为HTML”,看看原始代码。
  5. 调试JavaScript :在“源代码”(Sources)面板,在你怀疑的“汇”函数(如 innerHTML 赋值行)上打上断点。重新触发漏洞,看看程序是否执行到这里?赋值的数据是什么?是否被修改或截断?

6.2 如何处理复杂的JavaScript代码流?

当代码经过压缩、混淆,或者调用链路很长时,手动追踪非常困难。

  1. 使用浏览器的调试器“调用堆栈”(Call Stack) :当你在汇点(如 innerHTML 赋值)打上断点并触发后,查看调用堆栈。它能显示是哪个函数调用了当前函数,一层层回溯,帮你理清数据流的来龙去脉。
  2. 使用 console.trace() :如果你能修改代码(比如在测试环境),可以在关键的变量赋值处插入 console.trace() ,它会在控制台打印出当前的调用栈轨迹。
  3. 关注事件监听器 :很多DOM操作是由事件(点击、输入、加载)触发的。在“源代码”面板的“事件监听器断点”中,勾选“脚本” -> “事件监听器”,然后触发页面动作,调试器会在事件处理函数开始时暂停。

6.3 漏洞修复后如何验证?

修复DOM型XSS后,不能简单测试一下原来的Payload不弹窗就完事。

  1. 回归测试 :确保原来的攻击向量确实被阻断。使用之前所有成功的Payload进行测试。
  2. 测试边界情况
    • 输入超长字符串。
    • 输入包含各种空白字符(Tab, 换行)。
    • 输入Unicode字符、emoji。
    • 尝试不同的编码方式。
  3. 审查修复代码 :确认修复是采用了“输出编码”还是“输入验证”?编码是否匹配上下文?白名单验证是否足够严格?有没有可能通过其他源(如 document.referrer )绕过?
  4. 使用自动化工具扫描 :用之前提到的DOM Invader或CodeQL对修复后的代码再次进行扫描,看是否引入了新的问题或遗漏了其他路径。

6.4 速查表:常见漏洞模式与Payload

下表汇总了常见的危险模式、测试Payload和修复建议,方便你在审计时快速对照。

漏洞模式 危险代码示例 测试Payload (示例) 修复建议
innerHTML / outerHTML div.innerHTML = userInput; <img src=x onerror=alert(document.domain)> 使用 textContent 或净化库(如DOMPurify)
document.write document.write(‘Welcome ‘ + name); </script><script>alert(1)</script> 避免使用,改用DOM操作方法
eval eval(‘var data = “‘ + input + ‘“;’); ”; alert(1); // 绝对禁止使用 ,用 JSON.parse() 等替代
setTimeout / setInterval (字符串参数) setTimeout(input, 1000); alert(1); 传入函数引用,而非字符串: setTimeout(function(){…}, 1000)
location.href 跳转 location.href = redirectUrl; javascript:alert(1) 验证URL协议,只允许 http:// https://
location.hash 直接使用 eval(location.hash.substr(1)); #alert(1) 避免直接执行,对hash值进行严格验证
jQuery .html() / .append() $(‘#div’).html(userContent); innerHTML 使用 .text() 方法,或确保传入内容是安全的
AngularJS 模板注入 <div>{{ userContent }}</div> {{ constructor.constructor(‘alert(1)’)() }} 升级AngularJS,使用 $sce 服务严格过滤,或迁移到新版Angular

这条路从理解原理到熟练挖掘,需要大量的练习和代码阅读。我最开始也是对着 alert(1) 弹窗兴奋不已,后来才慢慢学会追踪复杂的代码流,理解框架特性,甚至写出自己的模糊测试脚本。安全是一个攻防对抗的领域,DOM型XSS因其客户端特性,在这场对抗中占据着一个独特而重要的位置。希望这篇长文能成为你手边一份可靠的指南,下次再遇到看似“干净”的页面时,你会知道从哪里入手,去发现那些隐藏在水面之下的风险。记住,最重要的不是记住所有Payload,而是培养那种追踪数据流、理解代码意图的“侦探”思维。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值