Back to Blog
On this page6 sections

// deep dive

IDOR explained for QA engineers

qa.codesqa.codes · 13 June 2026 · 8 min read
IntermediateManual QAQA engineers
security-testingauthidorbugs

IDOR is the most common serious web vulnerability and the easiest for QA to catch — if you know what you're looking at. Here's the concept, in tester's terms.

part ofSecurity testing for QA

IDOR — Insecure Direct Object Reference — sounds like a security-conference acronym, and that framing is exactly why QA teams leave it to "the security people." But strip the jargon and it's the simplest bug there is: the app lets you ask for a thing by its ID, and forgets to check whether the thing is yours. You don't need tooling, payloads, or a security background to find it. You need two accounts and the habit of changing a number. This is the deeper look at the bug introduced in auth bugs QA can catch without being a pentester.

The concept in one sentence

An object reference is just an identifier the app uses to fetch something — /orders/1024, ?userId=88, invoice_id=5567. It's "direct" when it points straight at the underlying record. It's "insecure" when the server returns that record based on the ID alone, without checking that the logged-in user is allowed to see it. That missing check is the entire vulnerability.

Why it's so common

Developers build the happy path first: a logged-in user views their own order, so the code fetches orders/:id and renders it. It works perfectly in every demo, because in every demo the ID being requested is the user's. The authorization check — "is this order owned by the person asking?" — is easy to forget precisely because the feature works without it. Tests written against the happy path never catch it, because they only ever request the user's own data.

How to find it (two accounts, one changed number)

The whole technique:

  1. Log in as User A. Open one of their records and note the identifier — in the URL (/orders/1024), a query param, a request body field, or an API path.
  2. Log in as User B in a separate browser profile.
  3. As User B, request A's identifier directly.

If you get A's data, that's IDOR. Expected behaviour is a 403 (or a 404 that doesn't confirm the record exists). You're not "hacking" — you're requesting a URL while logged in as the wrong person.

Where it hides beyond the obvious URL

The ID isn't always in the address bar. Check every place an object reference travels:

  • Query parameters: ?accountId=88 — change the number.
  • Request bodies: a PATCH that includes "ownerId": 88 — does the server trust that field or the session?
  • Nested and related resources: /orders/1024/invoice — the order check might exist but the invoice sub-resource might not.
  • Predictable IDs: sequential integers make IDOR trivial to sweep. UUIDs make it harder to guess but not safe — a leaked or shared UUID is still served without an ownership check. "We use UUIDs" is not an authorization control.
  • Different verbs: you might be blocked from reading /orders/1024 but able to delete it — test each operation, not just GET.

The variants worth knowing

  • Horizontal: accessing another user's data at the same privilege level (User B reads User A's order). The classic.
  • Vertical: accessing data or actions above your privilege level (a regular user hitting an admin-only record). This overlaps with authorization testing.
  • Mass assignment cousin: sending an extra field the form never shows ("role": "admin", "accountId": 88) and the server accepting it. Same root cause — trusting client-supplied references.

Reporting it safely

Stay on test accounts you're authorised to use; never pull a real customer's record to "prove" it. Report the defect plainly: "Logged in as User B, requesting /orders/1024 (User A's order) returned A's full order data; expected 403." That's a clear, actionable bug — not an exploit. The security testing checklist frames the full QA-safe pass, and common bugs → auth & permissions catalogues the pattern.

IDOR pass (two accounts)

  • Note an object's ID as User A (URL, query param, body field, nested resource)
  • Request that exact ID as User B — expect 403, not A's data
  • Check IDs in bodies and PATCH/PUT/DELETE, not just GET URLs
  • Don't trust UUIDs as a control — test them the same way
  • Try an extra field the UI never sends (ownerId, role) — is it accepted?
  • Report as a defect with expected-vs-actual, on test data only

// RELATED QA.CODES RESOURCES


// related

Deep dives·13 June 2026 · 10 min read

OWASP Top 10 for testers, not hackers

The OWASP Top 10 translated for QA: what each category means for flows you already test, and the one check you can run without being a pentester.

security-testingowaspchecklistauth