网安小萌新头像
关注

基于OWASP Juice Shop的业务逻辑漏洞实战:从负数商品数量到异常订单金额

本文实验均在个人本地部署、合法授权的 OWASP Juice Shop 靶场环境中完成,仅用于安全学习、漏洞分析与防御研究。
本次实验针对购物车数量修改和订单结算流程进行业务逻辑测试,验证服务端是否会对商品数量和订单金额进行有效校验。

一 漏洞发现与验证

1. 正常添加商品请求基线

首先登录普通用户账号,选择一个商品加入购物车,并使用 Burp Suite 捕获添加购物车请求。

正常添加商品时,请求如下:

POST /api/BasketItems/ HTTP/1.1
Host: localhost:3000
Authorization: Bearer <JWT_TOKEN>
Content-Type: application/json
Cookie: language=zh_CN; token=<JWT_TOKEN>

请求体如下:

{
  "ProductId": 25,
  "BasketId": "9",
  "quantity": 1
}

从请求体可以看到,客户端向服务端提交了三个关键字段:

字段说明当前值
ProductId商品 ID25
BasketId当前用户购物车 ID9
quantity添加商品数量1

其中,quantity 表示本次添加到购物车的商品数量。正常情况下,该字段应为正整数。

服务端响应如下:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 158

响应体如下:

{
  "status": "success",
  "data": {
    "id": 31,
    "ProductId": 25,
    "BasketId": "9",
    "quantity": 1,
    "updatedAt": "2026-09-30T08:28:44.081Z",
    "createdAt": "2026-09-30T08:28:44.081Z"
  }
}

从响应结果可以看到,服务端返回 200 OK,并且响应体中的 status 为 success,说明本次添加商品请求被服务端成功处理。

响应体中的关键字段如下:

字段说明当前值
id购物车条目 ID,也就是 BasketItem ID31
ProductId商品 ID25
BasketId当前用户购物车 ID9
quantity服务端记录的商品数量1

其中,id: 31 表示本次创建的购物车条目 ID,后续如果需要修改或删除该购物车条目,通常会用到这个 ID。

本步骤可以得到如下结论:

1. 客户端可以通过 POST /api/BasketItems/ 向购物车添加商品。
2. 请求体中包含 ProductId、BasketId 和 quantity 三个关键业务参数。
3. quantity 字段由客户端提交,属于后续需要重点验证的可控参数。
4. 服务端在正常 quantity=1 的情况下返回 200 OK,并成功创建购物车条目。
2. 正常数量修改请求验证

在完成商品添加后,购物车中已经存在一条商品记录。前一步响应中的 id 字段为 31,表示当前购物车条目的 BasketItem ID。

为了验证数量修改接口,进入购物车页面,将商品数量从 1 修改为 2,并使用 Burp Suite 捕获请求发送至Repeater模块。
image
请求如下:

PUT /api/BasketItems/31 HTTP/1.1
Host: localhost:3000
Authorization: Bearer <JWT_TOKEN>
Content-Type: application/json
Cookie: language=zh_CN; token=<JWT_TOKEN>

请求体如下:

{
  "quantity": 2
}

其中:

字段说明当前值
请求方法修改已有购物车条目PUT
请求路径当前购物车条目接口/api/BasketItems/31
BasketItem ID被修改的购物车条目 ID31
quantity修改后的商品数量2

服务端响应如下:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 156

响应体如下:

{
  "status": "success",
  "data": {
    "ProductId": 25,
    "BasketId": 9,
    "id": 31,
    "quantity": 2,
    "createdAt": "2026-09-30T08:28:44.081Z",
    "updatedAt": "2026-09-30T10:58:25.525Z"
  }
}

从响应结果可以看到,服务端返回 200 OK,并且响应体中的 quantity 已经变为 2。

这说明:

1. /api/BasketItems/31 是修改已有购物车条目数量的接口。
2. quantity 字段可以由客户端提交。
3. 当 quantity 为正常正整数时,服务端接受该修改。
4. 商品数量从 1 成功修改为 2。

image

3. 异常数量修改验证

在确认正常数量 quantity=2 可以被服务端接受后,继续保持请求路径、当前用户身份和 BasketItem ID 不变,仅将 quantity 修改为负数。

请求如下:

PUT /api/BasketItems/31 HTTP/1.1
Host: localhost:3000
Authorization: Bearer <JWT_TOKEN>
Content-Type: application/json
Cookie: language=zh_CN; token=<JWT_TOKEN>

请求体如下:

{
  "quantity": -1
}

其中,31 为前面正常添加商品时返回的 BasketItem ID。本次测试只修改 quantity 字段,其余关键参数保持不变。

服务端响应如下:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 157

响应体如下:

{
  "status": "success",
  "data": {
    "ProductId": 25,
    "BasketId": 9,
    "id": 31,
    "quantity": -1,
    "createdAt": "2026-09-30T08:28:44.081Z",
    "updatedAt": "2026-09-30T11:01:37.481Z"
  }
}

从响应结果可以看到,服务端返回 200 OK,并且响应体中的 quantity 被更新为 -1。

这说明服务端在处理购物车数量修改请求时,没有拒绝负数商品数量。

两次数量修改请求对比如下:

测试项请求体响应结果说明
正常数量修改{"quantity": 2}200 OK服务端接受正常正整数数量
异常数量修改{"quantity": -1}200 OK服务端接受负数数量

因此,本步骤可以得到结论:

购物车数量修改接口缺少对 quantity 取值范围的严格校验。

正常业务中,商品数量应当是正整数,不应允许出现负数。服务端接受 quantity=-1,说明该接口存在业务参数校验不足的问题。

4. 购物车金额变化验证

在上一节中,服务端接受了如下异常数量修改请求:

{
  "quantity": -1
}

为了确认该异常数量是否真正影响购物车业务状态,返回购物车页面进行观察。

页面显示结果如下:

检查项修改后结果
商品名称榨汁机
商品数量-1
商品单价89.99¤
购物车总价-89.99¤
奖励积分-9

image
从页面结果可以看到,购物车中的商品数量已经变为 -1,并且总价格也变为:

-89.99¤

这说明异常数量不仅被服务端接口接受,而且已经进入购物车金额计算流程。

正常情况下,商品数量应当是正整数,购物车总价也不应出现负数。但当前页面中出现了:

quantity = -1
总价格 = -89.99¤

说明服务端在购物车数量修改接口中没有正确限制 quantity 的取值范围,导致负数数量参与了金额计算。

本步骤可以得到如下结论:

1. 服务端接受了 quantity=-1。
2. 购物车页面成功显示负数数量。
3. 购物车总价受到负数数量影响,变为负数金额。
4. 异常业务参数已经进入后端金额计算流程。

因此,该问题可以归纳为:

商品数量参数校验不足
+
购物车金额计算逻辑受异常数量影响
5. 订单结算验证

在购物车金额已经出现异常的情况下,继续进入结算流程,观察异常金额是否会进入订单结算阶段。

结算页面中显示的订单摘要如下:

检查项结果
商品金额-89.99¤
配送费用0.50¤
促销抵扣0.00¤
最终总价-89.49¤
奖励积分-9

image

从结算页面可以看到,负数商品金额继续进入了订单摘要计算:

最终总价 = 商品金额 + 配送费用 - 促销抵扣
        = -89.99 + 0.50 - 0.00
        = -89.49¤

随后点击“下单并支付”,订单被成功提交。

提交成功后,页面显示完成提示,并触发官方 challenge:

Payback Time
Place an order that makes you rich.

image

订单结果页面中仍然可以看到异常订单数据:

检查项结果
商品名称榨汁机
商品单价89.99¤
商品数量-1
商品金额-89.99¤
配送费用0.50¤
最终总价-89.49¤
订单是否提交成功是
是否完成官方 Challenge是,Payback Time

这说明异常商品数量不仅影响了购物车金额,还进一步进入了订单结算流程,并最终形成了负数金额订单。

该步骤可以得到如下结论:

1. 负数商品数量进入结算页面。
2. 订单总价被计算为负数。
3. 服务端允许提交该异常订单。
4. 官方 Payback Time challenge 被触发。

因此,本案例可以归纳为:

商品数量校验不足
+
订单金额计算缺少边界校验
+
异常金额进入结算流程

二 原理分析

1. 正常业务规则

在购物车与订单结算流程中,商品数量和订单金额都属于核心业务数据。

正常情况下,商品数量应满足:

Quantity 必须是正整数
Quantity 不能为 0
Quantity 不能为负数

订单金额应由服务端根据可信数据计算,而不是直接信任客户端提交的结果:

商品小计 = 商品单价 × 商品数量
订单总价 = 商品小计 + 配送费用 - 促销抵扣
最终支付金额不应为负数

因此,服务端至少需要在两个位置进行校验:

1. 修改购物车数量时,校验 quantity 是否为合法正整数;
2. 订单结算时,重新校验购物车状态并计算最终金额。

2. 本案例中的异常链路

在本次实验中,正常修改商品数量时,服务端可以接受:

{
  "quantity": 2
}

这说明 /api/BasketItems/31 接口确实用于修改已有购物车条目的数量。

随后将数量修改为负数:

{
  "quantity": -1
}

服务端仍然返回 200 OK,并在响应中保存了:

{
  "quantity": -1
}

这说明服务端在购物车数量修改阶段没有拒绝负数数量。

之后返回购物车页面,可以看到商品数量变为 -1,购物车总价变为负数:

商品数量:-1
购物车总价:-89.99¤

继续进入结算页面后,负数金额仍然进入订单摘要:

商品金额:-89.99¤
配送费用:0.50¤
最终总价:-89.49¤

最终订单可以提交成功,并触发 Payback Time challenge。

因此,本案例中的异常链路可以概括为:

客户端提交 quantity = -1
        ↓
服务端接受并保存负数数量
        ↓
购物车金额被计算为负数
        ↓
负数金额进入订单结算流程
        ↓
异常订单提交成功

3. 漏洞本质

本问题的核心不是 SQL 注入、XSS、JWT 伪造或越权访问,而是服务端没有正确执行业务规则。

具体来说,问题出在两个位置:

1. 购物车数量修改接口没有校验 quantity 必须为正整数;
2. 订单结算流程没有拦截负数数量和负数订单金额。

前端页面中的数量按钮、输入限制或显示逻辑不能作为安全边界。只要客户端请求可以被 Burp Suite 修改,服务端就必须重新校验关键业务参数。

因此,该漏洞本质可以概括为:

服务端信任了客户端提交的商品数量,
导致负数 quantity 参与金额计算,
最终形成负数金额订单。

这类问题属于业务逻辑漏洞,关键不在于 payload 复杂,而在于服务端没有在关键业务流程中校验业务规则。

三 修复建议

本案例的核心问题是:服务端接受了 quantity=-1,并让负数数量参与购物车金额和订单金额计算。

因此,修复重点不是在前端限制按钮或输入框,而是要在服务端关键流程中做校验。

1. 限制商品数量必须为正整数

在修改购物车数量时,服务端应校验 quantity 的合法性。

本次问题请求为:

PUT /api/BasketItems/31 HTTP/1.1

异常请求体为:

{
  "quantity": -1
}

服务端不应返回 200 OK,而应拒绝该请求。

建议校验规则如下:

quantity 必须是正整数
quantity 不能为 0
quantity 不能为负数
quantity 不能为小数
quantity 不能为字符串
quantity 不能超过库存或业务限制

修复后预期效果:

测试值预期结果
1允许
2允许
0拒绝
-1拒绝
1.5拒绝
"test"拒绝

如果数量不合法,服务端应返回类似错误:

{
  "error": "Quantity must be a positive integer"
}

2. 结算前重新校验购物车数据

本实验中,负数数量不仅进入了购物车,还进入了订单结算流程:

商品数量:-1
商品金额:-89.99¤
最终总价:-89.49¤

这说明结算阶段没有再次拦截异常数据。

因此,在提交订单前,服务端应重新检查购物车中的每一项商品:

商品数量是否合法
商品价格是否来自服务端
商品小计是否正常
订单总价是否小于 0

如果购物车中存在负数数量或负数金额,应直接拒绝下单。


3. 订单金额必须由服务端计算

订单金额不能依赖客户端传来的数据,也不能只相信前端页面显示的金额。

正确逻辑应是:

服务端读取商品单价
        ↓
服务端读取并校验商品数量
        ↓
服务端计算商品小计
        ↓
服务端计算订单总价
        ↓
确认最终金额合法
        ↓
创建订单

也就是说,订单金额应该始终由服务端根据可信数据重新计算。

正常公式应为:

商品小计 = 商品单价 × 商品数量
订单总价 = 商品小计 + 配送费用 - 促销抵扣
最终支付金额 >= 0

不能出现本次实验中的情况:

quantity = -1
最终总价 = -89.49¤
订单仍然提交成功

4. 修复后复测

修复完成后,需要重新验证购物车修改和订单结算两个阶段。

复测项预期结果
quantity=1修改成功
quantity=2修改成功
quantity=-1请求被拒绝
负数数量进入购物车不允许
负数金额进入结算页不允许
负数金额订单提交不允许

5. 修复总结

本漏洞的修复重点可以概括为:

服务端不能信任客户端提交的 quantity。
购物车修改阶段要校验数量。
订单结算阶段要重新校验购物车状态。
订单金额必须由服务端重新计算。
最终支付金额不能为负数。

前端限制只能提升用户体验,不能作为安全防护。真正的业务安全边界必须放在服务端。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2401_83721761/article/details/166902475

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--