eWPTX study materials
eWPTX study resources

IDOR That Isn't an IDOR

eWPTX · Put your knowledge to work in context.

All case studies

IDOR That Isn't an IDOR

A SaaS app exposes GET /api/v2/documents/{id} returning a document's metadata and a short-lived signed download URL. IDs are UUIDv4. You are authenticated as tenant A user u1. You request a document ID belonging to tenant B (obtained from a shared link in a support ticket) and get 403 {"error":"forbidden"}. You request a random UUID and get 404. You request your own document and get 200. The app uses a shared Postgres with a tenant_id column; the API layer is a thin Express router. You have 60 minutes and the client wants to know if tenant isolation holds.

1. The 403 vs 404 difference is the key signal. Explain what it tells you about the authorization check and design the test that distinguishes 'correctly isolated' from 'check is in the wrong layer.'

Read the worked answer

The 403 means the app found the row and then decided you can't see it - so the query ran without a tenant filter, and authorization is enforced in application code after the fetch. A correctly isolated design would scope the query (WHERE id=? AND tenant_id=?) and return 404 for both 'not found' and 'not yours,' leaking nothing. Here the 404-vs-403 split is an existence oracle: I can enumerate which UUIDs are real by status code, even though I can't read them.

To distinguish 'isolated' from 'check in wrong layer,' I test every route that touches documents, not just this one: the download-URL endpoint, the share endpoint, the version-history endpoint, the search endpoint, and any bulk/export endpoint. A post-fetch check in one handler is routinely missing in a sibling. I also test method and parameter confusion: GET /api/v2/documents/{id}?tenant_id=B, header overrides (X-Tenant-Id), and POST /api/v2/documents/{id}/share - if any of these bypass the post-fetch check, isolation is broken despite the 403 on the primary route.

2. You find `GET /api/v2/documents/{id}/download-url` returns a signed URL for tenant B's document to tenant A. Write the finding with severity and the specific remediation, and explain why 'add a tenant check' is insufficient.

Read the worked answer

Finding: Broken object-level authorization on download-url endpoint (IDOR). Authenticated tenant-A user can obtain a signed download URL for any tenant-B document by supplying its UUID. The primary metadata route returns 403, but the download-url route performs no tenant scoping, so the signed URL - which is the actual data-access primitive - is issued cross-tenant. Severity: High (cross-tenant data disclosure; the signed URL bypasses all further app-layer checks and is shareable). Evidence: request/response pair showing tenant-A token + tenant-B UUID → 200 with a valid signed URL, plus the 403 on the sibling route as the contrast.

Why 'add a tenant check' is insufficient: the check that exists on the metadata route is a post-fetch comparison, and it's missing on the sibling route - so patching one handler leaves the class of bug alive on the next endpoint (share, versions, export, search). The signed URL also means the authorization decision happens before the data is served, so any future route that mints URLs inherits the same flaw. Remediation: push tenant scoping into the data layer - every document query must include tenant_id from the session, returning 404 for non-matches (removing the existence oracle); centralize this in the repository/ORM so no handler can forget it; and make the signed-URL minter re-verify tenant ownership at issuance. Add a regression test that asserts 404 (not 403) for cross-tenant IDs on every document route.