Cross-Tenant IDOR on a Transaction Endpoint
A grey-box assessment of a web application surfaced a transaction IDOR that was not a finding against the assessed tenant — it was a finding against the SaaS platform the application was built on. Any authenticated token from any tenant could retrieve transaction records belonging to customers on every other tenant globally. The blast radius was not limited to the assessed organization. It extended to every tenant on the platform, past and present.
Cross-tenant access confirmed
The transaction history endpoint accepted a sequential integer ID in the URL path with no ownership check. Any authenticated user — from any tenant on the platform — could supply any ID and receive the full transaction record.A scan of consecutive IDs returned records belonging to other tenants on the platform. None had knowledge a test was happening.
GET /api/account/transactions/2300001 HTTP/1.1
Host: member.acme-lab.test
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
HTTP/1.1 200 OK
{
"id": 2300001,
"customer_id": 40821,
"full_name": "Lab User A",
"email": "lab.user.a@example.test",
"company": "Example Org Ltd",
"amount": 501.40,
"currency": "GBP",
"status": "completed",
"card": {
"brand": "Visa",
"last4": "0000",
"stripe_payment_id": "pi_lab_001_example"
}
# no tenant isolation check — any authenticated token, any tenant
The entire range was accessible with a single valid token. Each record returned full name, email, phone, transaction amount, date, and — for a significant proportion of sampled records — payment card brand, last four digits, and internal payment references.
Why multi-tenant IDOR has a different risk profile
A standard IDOR lets user A read user B's data within the same application. A multi-tenant IDOR does the same across organizational boundaries: the other organizations whose data was accessible had no knowledge a test was happening, were not party to the assessment agreement, and had no way to know their data was reachable.
The test timeline
API surface enumeration. JS bundle analysis and authenticated crawl. Transaction endpoint identified with sequential integer path parameter. Initial access check shows no tenant isolation.
Consecutive-ID scan returns cross-tenant data. Records from other tenants confirmed. Payment card metadata present in a portion of sampled records. Platform-level scope established.
Exploitation stopped. Platform vendor notified via assessed client.No mass enumeration beyond a limited proof-of-concept. Platform-level disclosure path initiated.
Closing the isolation gap
- Enforce tenant-scoped ownership checks on every record-level endpoint. The transaction endpoint must verify three things in sequence: is the caller authenticated? is the ID valid? does the caller's tenant own this record? All three must pass. Step 3 was absent entirely.
- Replace sequential integers with UUIDs for all cross-tenant record identifiers. Sequential integers from a shared database sequence signal that tenant isolation was not considered at the schema level. UUIDs remove the predictability that makes scanning feasible.
- Apply rate limiting and anomaly detection on sequential ID enumeration. A scan across hundreds of consecutive IDs in a short window should trigger alerts or temporary blocks. Without this control, an adversary can silently enumerate the platform's full transaction history using a single valid token.
- Treat platform-level findings as platform-level disclosures. An IDOR that affects all tenants must be disclosed to the platform vendor, not only to the assessed tenant. The assessed organization cannot deploy a fix for a SaaS platform they do not control.
The takeaway
When you buy a SaaS platform, you buy its security model. Including the parts that were never tested.