# 【juice-shop】★★★ API-only XSS：绕过前端直接调用 API 实施存储型 XSS


# 【juice-shop】★★★ API-only XSS：绕过前端直接调用 API 实施存储型 XSS

## 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. 需要直接调用服务器端 API，通过 API 尝试不同的 HTTP 动作以暴露不同实体。<br>
2. 一个包含已知数据实体及其在 API 中支持的 HTTP 操作动词的矩阵可为你提供帮助。<br>
3. 粗心的开发人员可能暴露了客户端根本不需要的 API 方法。
</blockquote>

---

## 0x02 实战：复现漏洞

### 🔍 第一步：API 枚举

这个挑战需要寻找前端没有暴露的 API 接口，使用 FindSomething 插件收集到以下接口：

```
/api/Addresss
/api/BasketItems
/api/Cards
/api/Challenges
/api/Challenges/?key=nftMintChallenge
/api/Complaints
/api/Deliverys
/api/Feedbacks
/api/Hints
/api/Products
/api/Quantitys
/api/Recycles
/api/SecurityAnswers
/api/SecurityQuestions
/api/Users
```

![API 枚举结果](../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406204249.png)

### 🌐 第二步：探测目标接口

使用 curl 逐个访问接口（默认 GET 方法），发现 `/api/Products` 返回了所有商品列表，将其作为目标。

```bash
❯ curl 127.0.0.1:3000/api/Products | python -m json.tool
```

![Products 接口响应](../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406204846.png)

> 虽然题目要求不使用前端，但不能被条件完全框住。
> 
> 我们使用burp的内置浏览器，访问 http://127.0.0.1:3000/api/Products 将对应的请求加入到重放器里

![加入 Repeater](../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406211032.png)

![Repeater 界面](../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406211009.png)

### ⚙️ 第三步：探测支持的 HTTP 方法

发送 OPTIONS 请求，获取该接口支持的所有请求方法：

```
GET,HEAD,PUT,PATCH,POST,DELETE
```

![OPTIONS 请求](../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406211301.png)

![OPTIONS 响应](../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406211322.png)

逐一测试各方法：

- **PUT** — 报错 `Error: Unexpected path: /api/Products`，无法使用
- **POST** — 报错 `UnauthorizedError: No Authorization header was found`，缺少鉴权

### 🔑 第四步：获取管理员 Authorization

我们使用之前的Login Admin挑战获取一下 Authorization ，直接用admin防止权限不足（这里使用burp内置浏览器，便于捕获本地包）。

![获取 Authorization](../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406212056.png)

将 `Authorization` 添加到 Burp Repeater 的请求头中，重新发送 POST 请求。

![添加 Authorization](../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406212501.png)

发送成功，响应中出现了新商品，确认该接口为商品添加接口。

![POST 成功](../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406212633.png)

### 💉 第五步：构造 XSS Payload

整理关键信息：

**XSS 有效负载（来自题目描述）：**

```html
<iframe src='javascript:alert(`xss`)'>
```

**接口返回的 JSON 结构：**

```json
{
    "status": "success",
    "data": {
        "id": 49,
        "updatedAt": "2026-04-06T13:25:59.899Z",
        "createdAt": "2026-04-06T13:25:59.899Z",
        "name": null,
        "description": null,
        "price": null,
        "deluxePrice": null,
        "image": null,
        "deletedAt": null
    }
}
```

**关键请求头（上传 JSON 数据时必须添加 Content-Type）：**

```
Authorization: Bearer <admin_token>
Content-Type: application/json
```

将 XSS 有效负载注入商品的 `description` 字段，构造并发送 POST 请求。

![构造 Payload](../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406221314.png)

### ✅ 第六步：完成挑战

访问 `http://127.0.0.1:3000/#/search`，翻到最后一页，找到通过 API 添加的商品，点击后 XSS 弹窗触发。

![商品列表](../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406221411.png)

![XSS 弹窗触发](../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406221504.png)

挑战完成！

---

## 吐槽

> 这个挑战因为一些细节粗心，花费了半天时间，我认为这个挑战是非常考验细节的，3 星有点低啊（雾
>
> 最后构建 payload 的时候注意不要少符号，容易搞坏这个 API，发现 GET 访问 `/api/Products` 报错的时候直接重启环境（

---

## 参考资料

- [OWASP Juice Shop 官方攻略 - API-only XSS](https://pwning.owasp-juice.shop/companion-guide/latest/appendix/solutions.html#_perform_a_persisted_xss_attack_without_using_the_frontend_application_at_all)

