IDOR: Reading Other Customers' Data Through an Exposed API
This is lab work from a TryHackMe web application security module, done against a deliberately vulnerable training app. Lab IPs are genericized and room answers are redacted. To see where I got this from, and to give it a try yourself, you can go to: TryHackMe | IDOR
TL;DR
The account page of a support-ticket app prefills your username and email. Dev tools show that data comes from /api/v1/customer?id=50. Changing id returns other customers’ usernames and emails, because the server never checks that the record belongs to the logged-in user.
What IDOR is
IDOR stands for Insecure Direct Object Reference. It means you can get to something you shouldn’t by sending an input that the server doesn’t verify or validate. The application takes the reference you hand it at face value and never checks whether you are allowed to see the object it points to.
The classic example is a URL like this:
1
https://onlinestore.thm/order/1234/invoice
Changing the order number to:
1
https://onlinestore.thm/order/1000/invoice
returns the real invoice for order 1000, even though it belongs to someone else.
Finding the reference
The ID won’t always be a plain number sitting in the address bar.
- Encoded: the value may be encoded, usually Base64, and needs decoding before you can read or change it properly.
- Hashed: it may be hashed as an extra layer of protection. If it isn’t salted, it can often be cracked with a lookup or rainbow table (CrackStation, or a local hash cracker).
- Unpredictable: if you can’t make sense of the ID at all (plaintext, encoded or hashed), make two accounts and swap their IDs between them. That won’t tell you what the IDs mean, but it will show whether an IDOR exists.
- Not in the URL: the reference might be loaded through an AJAX request or a JavaScript file. It could also be a hidden or unreferenced parameter that went to production and is no longer used.
The room also covers parameter mining. A page like /user/details shows your own account info, but by discovering an unlisted user_id parameter you can request other users’ details:
1
/user/details?user_id=123
Recon: where does the account page get its data?
The lab needs you to log in, so the first step is creating an account and opening the Your Account tab.
The account page prefills the username and email.
The page prefills our details, so it has to get them from somewhere. Opening the browser dev tools and checking the request/response for the page is a good way to find an exposed endpoint.
The request initiator chain shows jquery.min.js calling /api/v1/customer?id=50.
That /api/v1/customer?id=50 call is an exposed endpoint we can test against by changing the id.
Exploit: changing the id
Requesting the URL directly with our own ID gives the expected response:
Then the same endpoint with a different ID, the “first” account, id=1:
Another customer’s username and email, redacted here.
It returns another customer’s username and email without any check that the record belongs to us, which confirms the IDOR. The room also asks for id=3, which works the same way:
How to detect it
IDOR attacks exploit a missing authorization check in an application. An application doesn’t innately know a good request from a bad one; it acts on the data it’s given. When an application or API needs to reference a user’s data, it needs something to look that information up, such as an ID or username. The problem comes when an attacker can manipulate that reference to access records they shouldn’t, and to the application, the request looks completely legitimate.
To pull this off, an attacker generally has to probe the application first to see whether it’s even possible. That probing generates a lot of noise in the logs, which can be checked in a few places. IDOR also often requires the attacker to be logged in to their own account, because the fields being manipulated are only exposed to authenticated users. That means we know both who is making a request and what they’re requesting, and a mismatch between the two can be a strong indicator.
- Application Logs. This is the primary place to check, since IDOR exploits a flaw in the application logic itself: a missing authorization check. If the application logs which account made a request and who owns the record it returned, a mismatch is a direct signal of IDOR exploitation. This is the strongest signal you’ll have, though it’s important to note that some users legitimately access other accounts’ data, such as support desk staff viewing customer records.
- WAF Logs. When configured, a WAF can often see more than traditional network or web server logs because it inspects the contents of requests. Check these logs for sequential probing and unusually high traffic volume. Those indicate an attack, though not specifically IDOR. If the WAF can inspect the session token, it can also flag a single account, or a pool of accounts, making many requests with slightly different IDs.
- Web Server Logs. These are a weaker place to look, but they may still hold useful clues when combined with other sources. Access logs typically record the request method, the requested URL (including any query string), the source IP, and the server’s response code. That means IDs passed in GET parameters will show up here, and a high volume of 404 Not Found responses can be evidence of probing. However, web server logs don’t record request bodies, so IDs sent in POST requests won’t be visible.
How to fix it
As hinted at above, IDOR vulnerabilities exist because an application is missing an authorization check: it doesn’t confirm that the user making the request is authorized to receive the data they’re requesting. This vulnerability lives on the server itself, in any endpoint or API used to access data, and it must be corrected at each of those endpoints. Fixing one does not fix the others.
Any time a request is made for data that shouldn’t be publicly accessible, it should be verified against who is making the request. As mentioned above, there are legitimate reasons for one account to access another account’s information, so the fix needs to account for that with some form of access control, such as role-based access control (RBAC). That way, the average user is prevented from accessing another user’s information, while support staff, for example, keep their ability to look up a customer’s records.
Another possible fix is to stop relying on ID values entirely. Rather than letting a user pass an ID to the server and using it to look up the requested data, the server can rely on the user’s session token, which is already sent with every request, and derive the requested data from that. This would have completely removed the attack vector we used above, where we changed the id value in the request. Had the server derived the response from our session token, we could only ever have accessed our own information. This method only works when a user needs their own data, though. Any time a user needs another user’s information, as in the support staff example above, we can no longer rely purely on session tokens, and still need an ID value plus proper authorization checks.
There are additional layers that can help protect against IDOR, but none of them are perfect, and all of them still depend on the base-level fix.
- Rate Limiting. This is a general defense against many attack vectors, and here it makes probing more difficult. Rate limiting makes it harder for an attacker to brute force their way through random or sequential ID values, but it doesn’t help if they have a specific target, like an order number they found elsewhere or an admin’s ID.
- Random IDs / UUIDs. UUIDs make brute forcing nearly impossible due to the vast number of possible values, so an attacker would need to find another user’s UUID by some other means before accessing their data. Like rate limiting, this makes exploiting an IDOR harder but doesn’t stop it. UUIDs can leak through insecure API responses or shared links, and if a non-random version is used, such as the time-based UUIDv1, the values can be predicted. UUIDv4, the random version, should be used instead.

