目录

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

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

0x01 任务简报

💡 提示 (Hints)
1. 需要直接调用服务器端 API,通过 API 尝试不同的 HTTP 动作以暴露不同实体。
2. 一个包含已知数据实体及其在 API 中支持的 HTTP 操作动词的矩阵可为你提供帮助。
3. 粗心的开发人员可能暴露了客户端根本不需要的 API 方法。

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

../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406204249.png

🌐 第二步:探测目标接口

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

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

../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406204846.png

虽然题目要求不使用前端,但不能被条件完全框住。

我们使用burp的内置浏览器,访问 http://127.0.0.1:3000/api/Products 将对应的请求加入到重放器里

../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406211032.png

../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406211009.png

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

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

GET,HEAD,PUT,PATCH,POST,DELETE

../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406211301.png

../../../../../../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内置浏览器,便于捕获本地包)。

../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406212056.png

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

../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406212501.png

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

../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406212633.png

💉 第五步:构造 XSS Payload

整理关键信息:

XSS 有效负载(来自题目描述):

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

接口返回的 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 请求。

../../../../../../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

../../../../../../public/p/juice-shop-api-only-xss绕过前端直接调用-api-实施存储型-xss/assets/Pastedimage20260406221504.png

挑战完成!


吐槽

这个挑战因为一些细节粗心,花费了半天时间,我认为这个挑战是非常考验细节的,3 星有点低啊(雾

最后构建 payload 的时候注意不要少符号,容易搞坏这个 API,发现 GET 访问 /api/Products 报错的时候直接重启环境(


参考资料