Negative Cart Quantity Offsets the Order Total and Inflates the Stock Counter
Summary
In newbee-mall, an authenticated member can add a cart line with a negative quantity. The cart service validates only an upper bound, so the signed value is stored verbatim. When the order is submitted, the negative line subtracts from the order total, and the stock-update SQL consumes the signed value in a way that increases the product's stock counter instead of decreasing it.
In the verified run, an order containing a positive-quantity line worth 500 and a negative line worth -415 was persisted with a total of 85, and the targeted product's stock counter rose from 998 to 1003.
Details
The cart quantity is a signed integer field bound from the request body. The cart service rejects quantities greater than 5 but performs no lower-bound or sign check, so a line such as {"goodsId":10005,"goodsCount":-5} is stored as-is. The same one-sided check exists on the cart update path.
At order submission, the service computes the total as the sum of quantity × unit price over cart lines, so a negative line subtracts from the positive lines. The only guard is that the computed total must be at least 1, which a cart mixing one negative line with any normal purchase satisfies. The signed quantity is also passed into the stock update, whose SQL computes stock_num = stock_num - quantity guarded by stock_num >= quantity; for a negative quantity both the arithmetic and the guard behave as an increment, so the stock counter rises by the absolute value.
PoC
Tested on newbee-mall at commit a069069b07027613bf0e7f571736be86f431faee (default branch) in the standard monolith deployment with the shipped demo database.
Test conditions:
- the attacker was the seeded member
13700002703with a valid session; - on-sale product 10005 (unit price 83, stock 998) and product 10006 (unit price 100, stock 1000);
- the member had a saved shipping address.
As a validation control, a cart line with goodsCount=6 was rejected with 超出单个商品的最大购买数量!, confirming the quantity check exists but is one-sided. As a second control, a cart containing only the negative line was rejected at order submission (订单价格异常), confirming the total guard exists but is satisfiable by a mixed cart.
The attacker added the two lines:
POST /shop-cart HTTP/1.1
Host: <TARGET>
Cookie: JSESSIONID=<MEMBER_SESSION>
Content-Type: application/json
{"goodsId":10005,"goodsCount":-5}Observed response: {"resultCode":200,"message":"SUCCESS","data":null}. A second request for {"goodsId":10006,"goodsCount":5} also succeeded. The member then requested GET /saveOrder directly, which created an order.
The persistent state changed as follows:
tb_newbee_mall_order(total_price): 85 (= 5 x 100 + (-5) x 83; positive-quantity value 500)
tb_newbee_mall_order_item(goods_count): -5 and 5 persisted as order lines
tb_newbee_mall_goods_info(10005).stock_num: 998 -> 1003
tb_newbee_mall_goods_info(10006).stock_num: 1000 -> 995Expected behavior: a purchase quantity is a positive integer; the server should reject negative quantities at cart-write time and re-validate the aggregated quantities before computing the total and updating stock.
Impact
Any registered member can, using only their own cart, substantially reduce the stored order total of an accompanying legitimate purchase and increase the recorded stock of an on-sale product by the absolute value of the negative quantity. Repeating the sequence can materially corrupt inventory data, subject to the database integer limit. If an affected order is later cancelled or closed through the normal inventory-recovery path, that order's stock change is reversed.
The upstream project currently uses a simulated payment flow rather than a trusted third-party payment integration. In a deployment that relies on the stored order total for payment or fulfillment, the issue can result in underpayment; independently of payment integration, it corrupts order and inventory integrity.
The attack requires an ordinary member account, tampers only with the attacker's own cart lines, and needs no race or special configuration.
Suggested remediation
Validate 1 <= goodsCount <= SHOPPING_CART_ITEM_LIMIT_NUMBER on both cart-write paths (add and update), and re-validate every cart line inside the order-submission service before computing the total or issuing the stock update. A database constraint requiring a positive goods_count can provide additional defense in depth.
Source: newbee-ltd/newbee-mall