# 【juice-shop】★★ Login Admin：SQL 注入绕过登录

# 【juice-shop】★★ Login Admin：SQL 注入绕过登录

## 0x01 任务简报

<blockquote style="border-left: 5px solid #6f42c1; background-color: #f5f3ff; padding: 20px; margin: 1.5rem 0; color: #5521b5; line-height: 1.8; border-radius: 0 5px 5px 0;">
<div style="font-weight: bold; font-size: 1.1rem; margin-bottom: 10px; display: flex; align-items: center;">
<span style="margin-right: 8px;">💡</span> 核心线索 (Hints)
</div>
1. 挑战说明可能已经透露了你应该采取何种形式进行攻击。<br>
2. 若您恰巧已知晓管理员的电子邮箱地址，便可发起定向攻击。<br>
3. 即使你对管理员邮箱地址一无所知，专用的攻击模式也可能让你走运。<br>
4. 若已获取管理员密码哈希值，你当然可以尝试直接攻击该哈希值，而非使用 SQL 注入攻击。<br>
5. 或者，您也可以将此挑战与以下挑战组合解决：使用管理员用户凭据登录（无需事先更改凭据）或应用 SQL 注入挑战。
</blockquote>

---

## 0x02 前置条件

未登录

---

## 0x03 实战：复现漏洞

### 🔑 第一步：访问登录页面

通过"账户"菜单转到"登录"页面。

![登录页面](../../../../../../public/p/juice-shop-login-adminsql-注入绕过登录/assets/Pastedimage20260403162304.png)

### 🧪 第二步：探测 SQL 注入入口

为了绕过正常的登录流程，尝试使用 SQL 注入（SQLi）攻击。

对于简单的 SQL 注入而言，一个好的切入点是插入引号（如 `'` 或 `"`）。这些引号会扰乱不安全的拼接式查询的语法结构。

在电子邮件字段中输入 `'`，在密码字段中输入任意内容，点击登录按钮。

![SQL 语法错误反馈](../../../../../../public/p/juice-shop-login-adminsql-注入绕过登录/assets/Pastedimage20260403162601.png)

你会看到顶部的红色 `[objectObject]` 错误。检查浏览器网络标签页中的 500 响应，会发现包含 `SQLITE_ERROR` 错误信息以及完整 SQL 查询的提示信息。

![网络标签页中的完整错误信息](../../../../../../public/p/juice-shop-login-adminsql-注入绕过登录/assets/Pastedimage20260403163005.png)

### 💉 第三步：构造逻辑绕过语句

尝试在电子邮件字段中输入 `' OR true`，再次点击登录按钮。

![尝试 OR true 语句](../../../../../../public/p/juice-shop-login-adminsql-注入绕过登录/assets/Pastedimage20260403162820.png)

查询仍然无效。从 HTTP 响应中的新错误可以看出，语句结构仍然不完整。

![OR true 语句仍然存在语法错误](../../../../../../public/p/juice-shop-login-adminsql-注入绕过登录/assets/Pastedimage20260403162929.png)

### 🔐 第四步：使用 SQL 注释绕过

在 SQLite 数据库中，可以使用 `--` 来注释掉后续内容。

在电子邮件字段中输入 `' OR true--`

![使用 SQL 注释完成注入](../../../../../../public/p/juice-shop-login-adminsql-注入绕过登录/assets/Pastedimage20260403163051.png)

### ✅ 第五步：成功登录

按下回车后成功登录管理员账户，挑战完成。

![登录成功](../../../../../../public/p/juice-shop-login-adminsql-注入绕过登录/assets/Pastedimage20260403163105.png)

---

## 0x04 编码挑战

### 🔍 找到它（15 行）

![漏洞代码位置](../../../../../../public/p/juice-shop-login-adminsql-注入绕过登录/assets/Pastedimage20260403161005.png)

## 修复它

### 原始漏洞代码

```javascript
models.sequelize.query(`SELECT * FROM Users WHERE email = '${req.body.email || ''}' AND password = '${security.hash(req.body.password || '')}' AND deletedAt IS NULL`, { model: UserModel, plain: true })
```

### 攻击场景演示

```
用户输入：
  email = "' OR true--"
  password = "anything"

生成的 SQL：
SELECT * FROM Users 
WHERE email = '' OR true--' AND password = '...' AND deletedAt IS NULL

执行结果：
- OR true 使条件恒为真
- -- 注释掉后续的 AND password 条件
- 返回第一个用户（通常是 admin）
- 认证绕过成功
```

### 🛠️ 修复方案对比

| 方案 | 修复方式 | 安全性 | 评价 |
|------|--------|--------|------|
| fix1 | 参数化查询 + hash 在绑定 | ✅ 安全 | 正确但冗余 |
| fix2 | 参数化查询 + hash 在 SQL | ❌ 不安全 | 错误 |
| fix3 | 黑名单过滤 | ❌ 极不安全 | 错误 |
| fix4 | 参数化查询 + hash 在绑定 | ✅ 安全 | **最优** |

### ✅ 正确修复（fix4）

```javascript
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 })
```

**核心原理：**
- `$1`, `$2` 占位符告诉数据库这是数据，不是 SQL 代码
- `bind` 数组中的值被视为纯数据，不被解析为 SQL 语法
- 即使输入 `' OR true--`，也只被当作字符串值处理
- SQL 注入被彻底阻止

