Before working in the Security Testing team at Aon, I set myself the goal of receiving a bug bounty from a public vulnerability disclosure program. As is often recommended, I decided to look for one bug class in as many places as possible. I considered a few options for which bug class to look for, before deciding to go with Insecure Direct Object References (IDORs). Why? Firstly, they can be easy to test for. IDORs do not require a strong development background to understand and could be explained to a beginner in a matter of minutes. Secondly, they are difficult to fully automate testing for. Often, IDORs require a human’s eye to understand where in a request an object is being referenced, which API endpoints are likely to be vulnerable, and where one object could be replaced with another. There are no hard and fast rules for detecting these bugs, so they lend themselves to manual testing and a trained eye. Finally, due to organizations having such bespoke API implementations there is often no blanket fix for IDORs, meaning they are difficult to fully eradicate.
Equipped with this knowledge I began looking for IDORs, expecting to have some luck in a few days or so… After much frustration, three long weeks and having read just about everything on the internet for such a simple bug class, I finally found my first IDOR in the wild. Throughout this process I kept notes on what I was learning about this apparently simple bug class and wrote up a curated list of tips and tricks for finding IDORs. In this post I will share with you the tips that I picked up on my journey to receiving my first bug bounty.
Although this post will assume some understanding of topic, I feel that I should clarify exactly what an IDOR is before continuing. At its core, an IDOR is an access control vulnerability in which an application relies on user-supplied input to reference objects directly. In this case, the object could be a picture, a comment on a post, personally identifiable information (PII) associated with a user or even an entire department within an organization. We have seen the stereotypical use case of an IDOR before: an application is referencing an ID (e.g., id=1101) when retrieving information from the database and an attacker can attempt to change the ID to something else (e.g., id=1104) in the attempt to pull back another user’s information. There are, however, many more attack vectors for IDORs and I hope that the following list of tips will give you some new things to consider when testing.
Object scope: Is it private or public?
Start by looking at what objects should be publicly readable versus the ones that should not be. For instance, in an online store, product details and reviews associated with each product may be readable by the general public using the following endpoints:
However, the buyer’s private profile information should not be:
If the API structure looks something like the above, then you can assume that the user-related information should only be visible to that user. That is one of the best places to start testing for high-impact IDORs.
Pro tip: Don’t forget to try create/update/delete operations on objects that are publicly readable but shouldn’t be writable. Can you
/api/products and change a price?
Find patterns in API route naming to discover new endpoints
If you see an endpoint that exposes a resource in typical fashion such as:
Think about what other directories and endpoints there are likely to be in the API. Tools such as Burp Suite Intruder or FFUF are great here when combined with API-specific wordlists. If regular API wordlists are not finding anything, then consider using a tool like CeWL which will generate custom wordlists for individual applications. Sometimes there will be endpoints that the web app itself rarely hits, but you can send your own requests to them if you find one. These can be gold mines! In the above example, you could try looking for:
We can also provide alternative values for each section and test to see if they exist. For example, version 1 of the API may have the appropriate access controls in place, but perhaps version 2 is not fully rolled out yet. You may find that version 2 of the API is still accessible if you make calls directly to it and that it lacks the access controls as it is not finished.
- service: application context
- v1: version
- users: resource
- <user_id>: parameter
Add IDs to requests that don’t have them
At times, simply providing an API with information that it does not require or expect may produce results. Try adding object identifiers to requests that didn’t originally have them. The application might mistakenly honor the unexpected ID parameter without subjecting it to access control. Something like the following example might get you access to another user’s photo album:
Some applications will accept client-supplied ID values when creating an object. This ID value can be anything. If you do not see parameters like id, user_id, account_id, pid or picture_id while creating an object, you should try adding one yourself and test it. You may find that you are able to assign arbitrary IDs and then reference the objects.
Pro tip: You can find parameter names to try by deleting or editing other objects and seeing the parameter names used.
Try replacing parameter names
Once you have spent a decent amount of time testing an application, you may start to remember parameters that have been used throughout it. You can try these parameters in other areas and see if they will be accepted. For example, if you see something like:
You could replace the album_id parameter with something completely different and potentially get other data.
This could be used to obtain a list of a user’s album_ids, which you could then use in the original query. There is a Burp extension called Paramalyzer which will help with this by remembering all the parameters you have passed to a host.
Supply multiple values for the same parameter
Similarly, sometimes an API will require an ID and you can try supplying multiple IDs to provoke odd behavior and potentially bypass access control mechanisms. This is known as HTTP parameter pollution. Something like this might get you access to the admin’s account:
Try changing the HTTP request method when testing for IDORs
These are some of the most satisfying bugs to find! Sometimes, applications will only enforce access control on the typical HTTP request method that the client will be making but neglect to implement the same access controls across all HTTP request methods.
Try switching POST and PUT and see if you can upload something to another user’s profile. For RESTful services, try changing GET to POST/PUT/DELETE to discover create/update/delete actions.
Try changing the request’s content type
Access controls may be inconsistently implemented across different content types. For example, if your app sends XML to the server with a Content-Type header of application/xml, try rewriting the request to use JSON with a Content-Type header of application/json. Don’t forget to try alternative and less common values like text/xml, text/x-json, and similar.
Are credit card details being saved on a website?
When making a second purchase, the client may just be sending a credit card ID number instead of the actual card details themselves. Try looking for and changing this credit card ID number.
Adding someone to a chat?
Alarm bells! Pay close attention to these types of actions as they are often where you can find IDORs. Can add yourself to another chat simply by changing values?
Similarly, if there is a copy, move or delete function on the web app then this is a good place to try looking for IDORs! Try testing all CRUD functions (Create, Read, Update, Delete) on every endpoint.
Try changing the requested file type
This one is simple; it is not very common but sometimes requesting a different file type or extension may be enough to bypass the access control. Experiment by appending different file extensions (e.g. .json, .xml, .config) to the end of requests that reference a document.
Does the app ask for non-numeric IDs? Use numeric IDs instead
There may be multiple ways of referencing objects in the database and the application only has access controls on one. Try numeric IDs anywhere non-numeric IDs are accepted:
Try using an array
If a regular ID replacement isn’t working, try wrapping the ID in an array and see if that does the trick. For example:
These can be very exciting bugs to find in the wild and are so simple. Try replacing an ID with a wildcard. You might get lucky!
Do not despair. Sometimes applications will throw an error message regardless of if the action you attempted was successful or not. Even if you get an error message, check manually to see if the action worked.
Make sure if you are replacing one ID with another that you replace it in every part of the request, not just in one instance. This is an easy mistake to make and could cause you to overlook potential vulnerabilities.
Random or encrypted ID?
These can be tricky and daunting at first. The best option is to try to decode it by looking at a few different IDs. This may reveal that only a small part of the ID is changing in each instance. Knowing this may help you narrow down the link between the decrypted input and the encrypted output.
Try other object IDs if you receive access errors
If you receive an “access denied” response from the server such as a 401 Unauthorized, it’s worth trying other object identifiers to make sure the access controls are uniformly applied. You can use something like Burp Suite Intruder to quickly enumerate identifiers.
High Level Tips
Find whichever Burp plugin you prefer for repeating requests on the fly and learn it. It will be your greatest companion when looking for IDORs. My personal favorite is Autorize but you can use any plugin that suits you (Authz and AutoRepeater are also popular). These plugins will allow you send requests with the cookies of another user (or as an unauthenticated user) on the fly. They can greatly reduce the amount of time you are spending manually repeating requests and can land you a quick and easy finding on a test.
On larger applications with very lengthy requests, it can be difficult to tell which part of your request is referencing the object that you want to test. The quickest way to narrow down the search is to make 2 requests in which you change the object you would like to test, and then send both to Burp comparer. Burp will show you exactly where in the request the object is being referenced.
Start with high-impact functionality
If time is of the essence, try focusing on functions that have a clear security impact such as those involving account/password recovery before moving on to other functions. This also includes any functions that read or write confidential data.
Pay attention to new features
If you stumble upon a newly added feature within the web app, such as the ability to upload a profile picture for an upcoming charity event, and it performs an API call to:
It is possible that the application may not enforce access control for this new feature as strictly as it does for core features.
I hope that this list of tips and tricks gives you some new things to try on your next app test. Once you get the hang of them, finding access control issues can be some of the simplest, most impactful and enjoyable findings you can get on a test, happy hunting!
Author: Max Corbridge
Copyright 2021 Aon plc