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

IDOR in GET /rest/basket/:id allows viewing any user's basket

The authenticated /rest/basket/:id handler fetches a basket purely by the URL id with no scoping to the authenticated user, letting any logged-in user read any other user's basket contents.

Fileroutes/basket.ts
Lines1536
Confidence
98%
File statusvalidated
Details

server.ts mounts app.use('/rest/basket/:id', security.isAuthorized()) (authentication only) and routes GET to retrieveBasket().

routes/basket.ts lines 15-36:

export function retrieveBasket () {
  return async (req: Request, res: Response, next: NextFunction) => {
    try {
      const id = req.params.id
      const basket = await BasketModel.findOne({ where: { id }, include: [{ model: ProductModel, paranoid: false, as: 'Products' }] })
      ...
      res.json(utils.queryResultToJson(basket))

The query BasketModel.findOne({ where: { id } }) scopes only by the URL id. The bid claim in the JWT (security.authenticatedUsers.from(req).bid) is only consulted to award a CTF challenge (solveIf), not to actually reject the response. So an authenticated user can replace the URL id with any other user's basket id and receive that basket's products.

Proof of concept
  1. Register/log in as attacker → obtain Authorization: Bearer <JWT> (and bid cookie pointing at attacker's own basket).
  2. Iterate basket ids:
GET /rest/basket/2 HTTP/1.1
Host: target
Authorization: Bearer <attacker-JWT>

Response contains the victim basket (basket id 2) including its Products array, even though the JWT belongs to a different user.

Impact

Any authenticated user can enumerate basket ids and read every other user's basket, exposing product selections and quantities. No special role required — a normal customer session is enough.

Validation
confirmed

The handler reads req.params.id and calls BasketModel.findOne({ where: { id } }) with no comparison against security.authenticatedUsers.from(req).bid. The only place the user's bid is referenced is inside challengeUtils.solveIf(...), which merely awards a CTF challenge and does not gate the res.json(utils.queryResultToJson(basket)) response. Any authenticated user can substitute another user's basket id in the URL and receive that basket's Products, matching the PoC exactly.

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

The endpoint is reachable over HTTP (AV:N) and security.isAuthorized() mounted in server.ts requires any valid JWT but no specific role (PR:L). Exploitation is a single authenticated GET with an incremented :id — no race or special preconditions (AC:L, UI:N), and BasketModel.findOne({ where: { id } }) returns data within the same app's authority (S:U). Impact is limited to disclosing other users' basket contents (product names/quantities) — no write path, no deletion, and the leaked data class is restricted to baskets rather than all secrets, so C:L with I:N/A:N.

References