Unauthenticated IDOR Across Multiple API Endpoints
An assessment of a customer-facing web application surfaced five API endpoints that return customer profile data, order documents, and account records, none requiring a valid session ownership check. Each endpoint accepts a numeric customer or order identifier and returns the associated record without verifying that the requesting session owns it. A single authenticated account was enough to enumerate data belonging to every other customer in the system.
How the endpoints were found
Walking the checkout and account area by hand and reading the proxied traffic, a cluster
of API calls shared a common pattern: a userId or referenceId parameter
controls which record is returned. Each candidate was tested manually in Burp Repeater, replacing
the ID value with one belonging to a different account. Every one produced a full response with
no authorization error. No session scoping, no ownership assertion. Any value that resolves to a
real record is returned.
The five endpoints
# all five return customer data without verifying session ownership
# IDs shown are placeholders: <user-a> and <user-b> are two distinct accounts
GET /api/account/getProfileData?userId=<user-a>
GET /api/account/getAccountRecord?userId=<user-a>
GET /api/docs/getOrderDocuments?referenceId=<order-ref>
GET /api/docs/getDocument?documentId=<document-uuid>
GET /api/account/getAccountStatus?userId=<user-b>
# companion: email lookup, no session required at all
POST /api/account/lookup
Content-Type: application/json
{"email":"<any-registered-email>","cmd":"<lookup-command>"}
# returns: account metadata for any registered email address
What the data covers
Together the endpoints cover nearly every dimension of a customer's account relationship with the platform: profile and extended attributes, purchase history and order documentation, and account records. The document endpoints are particularly sensitive. One accepts an integer order reference to return a list of associated documents, and a second fetches a specific document by UUID. The UUID looks non-enumerable, but the first endpoint hands it over freely, turning enumeration into a two-step chain: fetch the order document list, then retrieve each document directly.
A separate, unauthenticated companion endpoint accepts an email address with no session token and returns account metadata for any registered email. This enables account existence confirmation, feeding credential-stuffing and social engineering attacks independently of the IDOR chain.
Checkout and account API calls mapped manually, one by one. Numeric IDs in userId and referenceId parameters flagged by hand as IDOR candidates from the proxied traffic.
Cross-account request replayed in Burp Repeater returned full data. userId from Account A substituted with a value from Account B. Complete response, no authorization error.
All five variants confirmed. Profile data, order documents, and account records, all accessible without ownership validation.
Unauthenticated email lookup confirmed. No session token required. Returns account metadata for any registered email address submitted to the endpoint.
Full finding cluster documented. Five IDOR endpoints plus the companion email disclosure raised as a single high-severity finding with individual evidence per endpoint.
Fixing the authorization gap
- Add server-side ownership validation to every endpoint that returns customer data: verify the session's account identity matches the requested identifier before returning any record. This check must happen in the API handler, not in client-side code or middleware that can be bypassed.
- Replace sequential numeric identifiers with non-enumerable UUIDs on all API paths serving customer-specific data. Opaque IDs are not a substitute for authorization checks, but they eliminate the trivial enumeration path that makes mass data extraction practical.
- Require authentication on the email-lookup endpoint and return a generic response regardless of whether the submitted address exists in the system. Never confirm or deny account registration to an unauthenticated caller.
- Audit all customer-facing API routes for the same pattern: any endpoint that accepts a user or record identifier as a query parameter is a candidate. Automated DAST tooling misses this class of bug because the response is a valid 200. Authorization correctness requires a test with two distinct accounts.
The takeaway
Five endpoints, zero ownership checks. The access control gap wasn't in authentication. A valid session was present. It was in authorization: the server returned any record for any ID with no verification that the caller had the right to see it. IDOR at this scale, across multiple endpoint families, typically indicates the authorization layer was added per-feature rather than enforced centrally, and the fix needs to match that scope.