IDOR Testing Checklist for QA Engineers

An insecure direct object reference (IDOR) happens when an application uses an ID from the request to fetch an object and never checks that the caller is allowed to have it. Change /api/invoices/1001 to /api/invoices/1002 and you're reading someone else's invoice. It's one of the most common serious bugs in web and mobile APIs, and QA engineers are well placed to find it.

In OWASP terms, IDOR is part of A01:2021 Broken Access Control, which is first on the OWASP Top 10. In the OWASP API Security Top 10 it's called API1:2023 Broken Object Level Authorization (BOLA).

Only test applications you're authorized to test: your own product, a staging environment you have permission for, or a bug bounty programme whose scope includes the target.

1. Set up two accounts (and roles)

2. Map every object reference

Use the application as User A with Burp Suite proxying the traffic. Then look through the HTTP history for anything that identifies an object:

3. Swap the IDs

Send each request to Repeater. Keep User A's session, replace the ID with one of User B's, and compare the response.

A 200 response isn't proof on its own, so check the body. And a 403 on the read doesn't prove the write is safe.

4. Don't be fooled by "unguessable" IDs

UUIDs make IDs hard to guess, but they don't fix access control. Real applications leak IDs all the time: in other API responses, shared links, email notifications, exports, logs and URLs. If the server doesn't check ownership, any leaked UUID is enough to get in.

5. Scale it up with Autorize

The Autorize extension for Burp Suite replays every request you make with a second user's session (and with no session at all). It flags the ones that still succeed. Browse the whole application as the high-privilege user and let Autorize test it with the low-privilege session. It's the fastest way to cover a large app, but confirm every flagged request by hand before you report it.

6. Report it so it gets fixed

7. Turn every finding into a regression test

This is where QA adds most value. Once a developer fixes the bug, add an automated test so it can't come back:

test("user A cannot read user B's invoice", async ({ request }) => {
  const res = await request.get(`/api/invoices/${fixtures.userB.invoiceId}`, {
    headers: { Authorization: `Bearer ${fixtures.userA.token}` },
  });
  expect([403, 404]).toContain(res.status());
});

Run these in CI with the rest of your suite. I describe the full setup in QA automation and security testing in one CI pipeline.

The checklist

  1. Two users per role, each with their own data.
  2. Every object reference mapped: path, query, body, headers, GraphQL and exports.
  3. Read, write, delete and create-as-another-user tested for each one.
  4. Other HTTP methods and older API versions tried.
  5. UUID-based endpoints tested with IDs leaked from elsewhere.
  6. The whole app replayed with Autorize as a low-privilege and an unauthenticated user.
  7. Every confirmed finding reported with impact and a clear fix.
  8. Every fix covered by an automated regression test.