agentggagentgg
Back to all findings
MEDIUMconfirmedmissing-access-controlidor-basket-coupon0d37ee02b9b1

IDOR in PUT /rest/basket/:id/coupon/:coupon allows applying coupons to any basket

The authenticated apply-coupon endpoint updates the basket identified solely by URL id without verifying the basket belongs to the caller, letting a user attach (or clear) coupons on other users' baskets.

Fileroutes/coupon.ts
Lines1034
Confidence
95%
File statusvalidated
Details

server.ts:

app.put('/rest/basket/:id/coupon/:coupon', utils.asyncHandler(applyCoupon()))

Guarded by app.use('/rest/basket/:id', security.isAuthorized()) for auth only.

routes/coupon.ts:

const id = params.id
let coupon = params.coupon ? decodeURIComponent(params.coupon) : undefined
...
const basket = await BasketModel.findByPk(id)
if (!basket) { ... }
await basket.update({ coupon: coupon?.toString() })

The basket is fetched by findByPk(id) and mutated with no where: { UserId: ... } clause and no check against security.authenticatedUsers.from(req).bid.

Proof of concept
PUT /rest/basket/2/coupon/n1y4tlavupr HTTP/1.1
Host: target
Authorization: Bearer <attacker-JWT>

The coupon (a valid coupon code) is now persisted on basket 2 (the victim's basket), even though the attacker is authenticated as a different user. Conversely, sending an invalid coupon null-s out the victim's previously applied coupon.

Impact

Any authenticated user can manipulate the coupon field on any basket — granting discounts to others' carts or wiping coupons they had legitimately applied. Trivially exploitable by sequentially trying basket ids.

Validation
confirmed

The applyCoupon handler reads params.id, calls BasketModel.findByPk(id), and then basket.update({ coupon }) with no ownership check against req.user/authenticatedUsers.from(req).bid. The security.isAuthorized() middleware only verifies the JWT but does not bind :id to the caller's basket, so any authenticated user can set/clear the coupon on another user's basket by iterating numeric ids. The exploit chain (authenticated request → arbitrary basket id → persisted mutation) is fully reachable from untrusted input, matching the reported PoC.

CVSS 3.1
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:L/A:N
Base score: 4.3 · MEDIUM

The endpoint is reachable over HTTP (AV:N) and only requires any authenticated user thanks to security.isAuthorized() (PR:L); no victim action is needed (UI:N) and exploitation is just iterating basket ids (AC:L). The route calls BasketModel.findByPk(id) then basket.update({ coupon }) without checking ownership against security.authenticatedUsers.from(req).bid, so the attacker can write/clear the coupon field on any basket — a bounded integrity impact limited to one column, hence I:L. No basket contents are returned in the response (C:N) and clearing a coupon doesn’t deny use of the application (A:N); impact stays within the Juice Shop component (S:U).

References