Skip to content

Ordering API cross-user IDOR - any authenticated user can read, cancel, or ship another user's orders #996

@geo-chen

Description

@geo-chen

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:

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:

// 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions