← all writeups
Bug Bounty

Chaining an IDOR into Full Account Takeover

Jan 20, 2026 · 6 min · by Abhishek Bhaskar (Abhi HackZ)

Severity is a story about impact, not about a single request. This writeup walks through how a finding that a scanner would have labelled "low — information disclosure" became a full account takeover (ATO) once I followed the data instead of stopping at the first anomaly. Details are anonymised; the target and any customer data are omitted under the engagement's disclosure terms.

Where it started

The application was a fairly standard multi-tenant SaaS: users belong to organisations, and each organisation sees only its own orders. While mapping the app I logged every request through my proxy and tagged anything that carried a numeric or sequential identifier. One endpoint stood out:

GET /api/v2/orders/{orderId}
Authorization: Bearer <session-jwt>

Changing orderId to a value that belonged to a different organisation still returned 200 OK with the order body. Classic Insecure Direct Object Reference (IDOR) — the server authenticated who I was but never checked whether this object was mine.

On its own this looked minor. The order object leaked another tenant's shipping city and an item list. Annoying, reportable as a "low", and easy to dismiss. So the interesting question wasn't "is this a bug?" — it was "what exactly is inside the object I can now read?"

Reading the whole object

The habit that pays off here is never trusting the rendered UI to tell you what an API returns. The front-end displayed four fields. The raw JSON returned sixteen. Buried near the bottom:

{
  "orderId": 84213,
  "status": "shipped",
  "customer": {
    "email": "[email protected]",
    "passwordResetToken": "a1b2c3d4-...",
    "resetTokenExpiresAt": "2026-01-21T10:00:00Z"
  }
}

The order object embedded the customer's live password-reset token. That is the entire ballgame. A field that had no business being in an order-details response turned an authorization flaw into an authentication bypass.

The chain

Three weak links, each harmless-looking in isolation:

  1. No object-level authorization on GET /orders/{id} — I can read any order.
  2. Over-fetching / mass exposure — the order serializer embedded the full customer record, including the reset token, instead of a minimal DTO.
  3. A replayable reset flow — the public endpoint POST /reset-password accepted that token with no second factor and no binding to the requesting session.

Put together:

A "low-severity information disclosure" was, in practice, account takeover for any user who had ever placed an order. In the report I demonstrated it against a single test account I controlled on the staging tenant — proving the chain without ever touching a real customer.

Why scanners miss this

An automated scanner flags the IDOR (it sees the ID swap return 200) and stops. It has no concept of what the leaked field means. It cannot know that passwordResetToken in an order body is catastrophic while shippingCity is trivia. The escalation lives in human reasoning about the data, and in chaining a second, physically separate endpoint into the first. That gap between "a scanner flags anomalies" and "an attacker reasons about impact" is exactly where the real findings live — and it's the philosophy behind SentryScan, which tries to prove an exploitable path rather than just list anomalies.

Takeaways for testers

If you're shipping a multi-tenant product and want this class of chained authorization flaw found before an attacker enumerates it, that's exactly the kind of engagement I run — get in touch.

Abhishek Bhaskar (Abhi HackZ)
Security Analyst & VAPT Engineer
Book a free 30-min security scoping call →