Ordering API cross-user IDOR - any authenticated user can read, cancel, or ship another user's orders
- Dominant language
- C#
- Stars
- 10.9k
- Forks
- 3.8k
- Avg merge
- 1d 11h
- Merged PRs (30d)
- 4
Description
### Summary
The Ordering API exposes GET, cancel, and ship endpoints that accept an integer order ID but perform no ownership check against the authenticated user's identity. Any logged-in user can read another customer's full order (including shipping address and order contents), cancel it, or mark it as shipped by enumerating sequential integer IDs.
### Details
In `src/Ordering.API/Apis/OrdersApi.cs`, three of the four order-mutation handlers do not verify that the order belongs to the caller.
`GetOrderAsync` (line 80-89) accepts `orderId:int` from the URL path and calls `services.Queries.GetOrderAsync(orderId)`. The underlying query in `src/Ordering.API/Application/Queries/OrderQueries.cs` is:
```csharp
var order = await context.Orders
.Include(o => o.OrderItems)
.FirstOrDefaultAsync(o => o.Id == id);
```
No `Buyer.IdentityGuid == userId` predicate is applied, unlike the sibling `GetOrdersFromUserAsync` method which correctly filters by the caller's subject claim (line 95-98 of `OrdersApi.cs`).
`CancelOrderAsync` (line 22-47) and `ShipOrderAsync` (line 51-78) accept an `OrderNumber` field in the request body and dispatch it directly to `CancelOrderCommandHandler` / `ShipOrderCommandHandler`. Both handlers retrieve the order by integer ID with no ownership guard:
```csharp
// CancelOrderCommandHandler.cs
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber);
orderToUpdate.SetCancelledStatus();
// ShipOrderCommandHandler.cs
var orderToUpdate = await _orderRepository.GetAsync(command.OrderNumber);
orderToUpdate.SetShippedStatus();
```
The entire route group is protected by `.RequireAuthorization()` (`Program.cs` line 22), so a valid JWT is required, but no role or ownership check is enforced beyond that. Order IDs are auto-increment integers stored in a PostgreSQL sequence, making them trivially enumerable.
The `ShipOrderAsync` comment in the source even notes "administrator executes ship order from app," yet no admin role requirement exists on the route.
### PoC
(available on request)
### Impact
Any authenticated customer can enumerate and read the full shipping address and purchase details of all other customers' orders. They can also cancel any pending order (denial of service for other customers) or mark any order as shipped (order state corruption). The only prerequisite is a valid account on the platform, which is typically open to self-registration.
Contributor guide
Assessment
This issue has not been assessed yet.