Unveiling all the techniques to find IDOR’S in web applications

ADIP
21 min readMar 28, 2024

--

Here I explain in depth where a pentester and bug hunter can find bugs: Indetifying IDOR’S in URL Parameters & APIs , IDOR Enumeration , Bypassing Encoded References , IDOR in Insecure APIs ,Chaining IDOR Vulnerabilities.

Let’s get started:

Insecure Direct Object References (IDOR) vulnerabilities are among the most common web vulnerabilities and can significantly impact the vulnerable web application. IDOR vulnerabilities occur when a web application exposes a direct reference to an object, like a file or a database resource, which the end-user can directly control to obtain access to other similar objects. If any user can access any resource due to the lack of a solid access control system, the system is considered to be vulnerable.

Building a solid access control system is very challenging, which is why IDOR vulnerabilities are pervasive. In addition, automating the process of identifying weaknesses in access control systems is also quite difficult, which may lead to these vulnerabilities going unidentified until they reach production.

For example, if users request access to a file they recently uploaded, they may get a link to it such as (download.php?file_id=123). So, as the link directly references the file with (file_id=123), what would happen if we tried to access another file (which may not belong to us) with (download.php?file_id=124)? If the web application does not have a proper access control system on the back-end, we may be able to access any file by sending a request with its file_id. In many cases, we may find that the id is easily guessable, making it possible to retrieve many files or resources that we should not have access to based on our permissions.

What Makes an IDOR Vulnerability

In simple term, an IDOR vulnerability mainly exists due to the lack of an access control on the back-end.

Impact of IDOR Vulnerabilities

IDOR vulnerabilities can have a significant impact on web applications. The most basic example of an IDOR vulnerability is accessing private files and resources of other users that should not be accessible to us, like personal files or credit card data, which is known as IDOR Information Disclosure Vulnerabilities. Depending on the nature of the exposed direct reference, the vulnerability may even allow the modification or deletion of other users' data, which may lead to a complete account takeover. And IDOR vulnerabilities may also lead to the elevation of user privileges from a standard user to an administrator user, with IDOR Insecure Function Calls.

Identifying IDORs:

URL Parameters & APIs

The very first step of exploiting IDOR vulnerabilities is identifying Direct Object References. Whenever we receive a specific file or resource, we should study the HTTP requests to look for URL parameters or APIs with an object reference (e.g. ?uid=1 or ?filename=file_1.pdf). These are mostly found in URL parameters or APIs but may also be found in other HTTP headers, like cookies.

In the most basic cases, we can try incrementing the values of the object references to retrieve other data, like (?uid=2) or (?filename=file_2.pdf). We can also use a fuzzing application to try thousands of variations and see if they return any data. Any successful hits to files that are not our own would indicate an IDOR vulnerability.

AJAX Calls

We may also be able to identify unused parameters or APIs in the front-end code in the form of JavaScript AJAX calls. Some web applications developed in JavaScript frameworks may insecurely place all function calls on the front-end and use the appropriate ones based on the user role.

For example, if we did not have an admin account, only the user-level functions would be used, while the admin functions would be disabled. However, we may still be able to find the admin functions if we look into the front-end JavaScript code and may be able to identify AJAX calls to specific end-points or APIs that contain direct object references. If we identify direct object references in the JavaScript code, we can test them for IDOR vulnerabilities.

This is not unique to admin functions, of course, but can also be any functions or calls that may not be found through monitoring HTTP requests. The following example shows a basic example of an AJAX call:

function changeUserPassword() {
$.ajax({
url:"change_password.php",
type: "post",
dataType: "json",
data: {uid: user.uid, password: user.password, is_admin: is_admin},
success:function(result){
//
}
});
}

The above function may never be called when we use the web application as a non-admin user. However, if we locate it in the front-end code, we may test it in different ways to see whether we can call it to perform changes, which would indicate that it is vulnerable to IDOR. We can do the same with back-end code if we have access to it (e.g., open-source web applications).

Understand Hashing/Encoding

Some web applications may not use simple sequential numbers as object references but may encode the reference or hash it instead. If we find such parameters using encoded or hashed values, we may still be able to exploit them if there is no access control system on the back-end.

Suppose the reference was encoded with a common encoder (e.g. base64). In that case, we could decode it and view the plaintext of the object reference, change its value, and then encode it again to access other data. For example, if we see a reference like (?filename=ZmlsZV8xMjMucGRm), we can immediately guess that the file name is base64 encoded (from its character set), which we can decode to get the original object reference of (file_123.pdf). Then, we can try encoding a different object reference (e.g. file_124.pdf) and try accessing it with the encoded object reference (?filename=ZmlsZV8xMjQucGRm), which may reveal an IDOR vulnerability if we were able to retrieve any data.

On the other hand, the object reference may be hashed, like (download.php?filename=c81e728d9d4c2f636f067f89cc14862c). At a first glance, we may think that this is a secure object reference, as it is not using any clear text or easy encoding. However, if we look at the source code, we may see what is being hashed before the API call is made:

$.ajax({
url:"download.php",
type: "post",
dataType: "json",
data: {filename: CryptoJS.MD5('file_1.pdf').toString()},
success:function(result){
//
}
});

In this case, we can see that code uses the filename and hashing it with CryptoJS.MD5, making it easy for us to calculate the filename for other potential files. Otherwise, we may manually try to identify the hashing algorithm being used (e.g., with hash identifier tools) and then hash the filename to see if it matches the used hash. Once we can calculate hashes for other files, we may try downloading them, which may reveal an IDOR vulnerability if we can download any files that do not belong to us.

Compare User Roles

If we want to perform more advanced IDOR attacks, we may need to register multiple users and compare their HTTP requests and object references. This may allow us to understand how the URL parameters and unique identifiers are being calculated and then calculate them for other users to gather their data.

For example, if we had access to two different users, one of which can view their salary after making the following API call:

{
"attributes" :
{
"type" : "salary",
"url" : "/services/data/salaries/users/1"
},
"Id" : "1",
"Name" : "User1"

}

The second user may not have all of these API parameters to replicate the call and should not be able to make the same call as User1. However, with these details at hand, we can try repeating the same API call while logged in as User2 to see if the web application returns anything. Such cases may work if the web application only requires a valid logged-in session to make the API call but has no access control on the back-end to compare the caller's session with the data being called.

If this is the case, and we can calculate the API parameters for other users, this would be an IDOR vulnerability. Even if we could not calculate the API parameters for other users, we would still have identified a vulnerability in the back-end access control system and may start looking for other object references to exploit.

IDOR Enumeration

Exploiting IDOR vulnerabilities is easy in some instances but can be very challenging in others. Once we identify a potential IDOR, we can start testing it with basic techniques to see whether it would expose any other data. As for advanced IDOR attacks, we need to better understand how the web application works, how it calculates its object references, and how its access control system works to be able to perform advanced attacks that may not be exploitable with basic techniques.

Let’s start discussing various techniques of exploiting IDOR vulnerabilities, from basic enumeration to mass data gathering, to user privilege escalation.

Insecure Parameters

Let’s start with a basic example that showcases a typical IDOR vulnerability. The exercise below is an Employee Manager web application that hosts employee records:

Our web application assumes that we are logged in as an employee with user id uid=1 to simplify things. This would require us to log in with credentials in a real web application, but the rest of the attack would be the same. Once we click on Documents, we are redirected to

/documents.php:

When we get to the Documents page, we see several documents that belong to our user. These can be files uploaded by our user or files set for us by another department (e.g., HR Department). Checking the file links, we see that they have individual names:

/documents/Invoice_1_09_2021.pdf
/documents/Report_1_10_2021.pdf

We see that the files have a predictable naming pattern, as the file names appear to be using the user uid and the month/year as part of the file name, which may allow us to fuzz files for other users. This is the most basic type of IDOR vulnerability and is called static file IDOR. However, to successfully fuzz other files, we would assume that they all start with Invoice or Report, which may reveal some files but not all. So, let's look for a more solid IDOR vulnerability.

We see that the page is setting our uid with a GET parameter in the URL as (documents.php?uid=1). If the web application uses this uid GET parameter as a direct reference to the employee records it should show, we may be able to view other employees' documents by simply changing this value. If the back-end end of the web application does have a proper access control system, we will get some form of Access Denied. However, given that the web application passes as our uid in clear text as a direct reference, this may indicate poor web application design, leading to arbitrary access to employee records.

When we try changing the uid to ?uid=2, we don't notice any difference in the page output, as we are still getting the same list of documents, and may assume that it still returns our own documents:

However, we must be attentive to the page details during any web pentest and always keep an eye on the source code and page size. If we look at the linked files, or if we click on them to view them, we will notice that these are indeed different files, which appear to be the documents belonging to the employee with uid=2:

/documents/Invoice_2_08_2020.pdf
/documents/Report_2_12_2020.pdf

This is a common mistake found in web applications suffering from IDOR vulnerabilities, as they place the parameter that controls which user documents to show under our control while having no access control system on the back-end. Another example is using a filter parameter to only display a specific user’s documents (e.g. uid_filter=1), which can also be manipulated to show other users' documents or even completely removed to show all documents at once.

Mass Enumeration

We can try manually accessing other employee documents with uid=3, uid=4, and so on. However, manually accessing files is not efficient in a real work environment with hundreds or thousands of employees. So, we can either use a tool like Burp Intruder or ZAP Fuzzer to retrieve all files or write a small bash script to download all files, which is what we will do.

We can click on [CTRL+SHIFT+C] in Firefox to enable the element inspector, and then click on any of the links to view their HTML source code, and we will get the following:

<li class='pure-tree_link'><a href='/documents/Invoice_3_06_2020.pdf' target='_blank'>Invoice</a></li>
<li class='pure-tree_link'><a href='/documents/Report_3_01_2020.pdf' target='_blank'>Report</a></li>

We can pick any unique word to be able to grep the link of the file. In our case, we see that each link starts with <li class='pure-tree_link'>, so we may curl the page and grep for this line, as follows:

curl -s "http://hackerhere.com/documents.php?uid=1" | grep "<li class='pure-tree_link'>"

<li class='pure-tree_link'><a href='/documents/Invoice_3_06_2020.pdf' target='_blank'>Invoice</a></li>
<li class='pure-tree_link'><a href='/documents/Report_3_01_2020.pdf' target='_blank'>Report</a></li>

As we can see, we were able to capture the document links successfully. We may now use specific bash commands to trim the extra parts and only get the document links in the output. However, it is a better practice to use a Regex pattern that matches strings between /document and .pdf, which we can use with grep to only get the document links, as follows:

curl -s "http://Hackerhere.com/documents.php?uid=3" | grep -oP "\/documents.*?.pdf"

/documents/Invoice_3_06_2020.pdf
/documents/Report_3_01_2020.pdf

Now, we can use a simple for loop to loop over the uid parameter and return the document of all employees, and then use wget to download each document link:

#!/bin/bash

url="http://Hackerhere.com"

for i in {1..10}; do
for link in $(curl -s "$url/documents.php?uid=$i" | grep -oP "\/documents.*?.pdf"); do
wget -q $url/$link
done
done

When we run the script, it will download all documents from all employees with uids between 1-10, thus successfully exploiting the IDOR vulnerability to mass enumerate the documents of all employees. This script is one example of how we can achieve the same objective. Alternatively, You can try Burp Intruder or ZAP Fuzzer.

Bypassing Encoded References

we saw an example of an IDOR that uses employee uids in clear text, making it easy to enumerate. In some cases, web applications make hashes or encode their object references, making enumeration more difficult, but it may still be possible.

Let’s go back to the Employee Manager web application to test the Contracts functionality:

If we click on the Employment_contract.pdf file, it starts downloading the file. The intercepted request in Burp looks as follows:

We see that it is sending a POST request to download.php with the following data: contract=cdd96d3cc73d1dbdaffa03cc6cd7339b

Using a download.php script to download files is a common practice to avoid directly linking to files, as that may be exploitable with multiple web attacks. In this case, the web application is not sending the direct reference in cleartext but appears to be hashing it in an md5 format. Hashes are one-way functions, so we cannot decode them to see their original values.

We can attempt to hash various values, like uid, username, filename, and many others, and see if any of their md5 hashes match the above value. If we find a match, then we can replicate it for other users and collect their files. For example, let's try to compare the md5 hash of our uid, and see if it matches the above hash:

[!bash!]$ echo -n 1 | md5sum

c4ca4238a0b923820dcc509a6f75849b -

Unfortunately, the hashes do not match. We can attempt this with various other fields, but none of them matches our hash. In advanced cases, we may also utilize Burp Comparer and fuzz various values and then compare each to our hash to see if we find any matches. In this case, the md5 hash could be for a unique value or a combination of values, which would be very difficult to predict, making this direct reference a Secure Direct Object Reference. However, there's one fatal flaw in this web application.

Function Disclosure

As most modern web applications are developed using JavaScript frameworks, like Angular, React, or Vue.js, many web developers may make the mistake of performing sensitive functions on the front-end, which would expose them to attackers. For example, if the above hash was being calculated on the front-end, we can study the function and then replicate what it's doing to calculate the same hash. Luckily for us, this is precisely the case in this web application.

If we take a look at the link in the source code, we see that it is calling a JavaScript function with javascript:downloadContract('1'). Looking at the downloadContract() function in the source code, we see the following:

function downloadContract(uid) {
$.redirect("/download.php", {
contract: CryptoJS.MD5(btoa(uid)).toString(),
}, "POST", "_self");
}

This function appears to be sending a POST request with the contract parameter, which is what we saw above. The value it is sending is an md5 hash using the CryptoJS library, which also matches the request we saw earlier. So, the only thing left to see is what value is being hashed.

In this case, the value being hashed is btoa(uid), which is the base64 encoded string of the uid variable, which is an input argument for the function. Going back to the earlier link where the function was called, we see it calling downloadContract('1'). So, the final value being used in the POST request is the base64 encoded string of 1, which was then md5 hashed.

We can test this by base64 encoding our uid=1, and then hashing it with md5, as follows:

[!bash!]$ echo -n 1 | base64 -w 0 | md5sum

cdd96d3cc73d1dbdaffa03cc6cd7339b -

Tip: We are using the -n flag with echo, and the -w 0 flag with base64, to avoid adding newlines, in order to be able to calculate the md5 hash of the same value, without hashing newlines, as that would change the final md5 hash.

As we can see, this hash matches the hash in our request, meaning that we have successfully reversed the hashing technique used on the object references, turning them into IDOR’s. With that, we can begin enumerating other employees’ contracts using the same hashing method we used above. Before continuing, try to write a script similar to what we used in the previous section to enumerate all contracts.

Mass Enumeration

Once again, let us write a simple bash script to retrieve all employee contracts. More often than not, this is the easiest and most efficient method of enumerating data and files through IDOR vulnerabilities. In more advanced cases, we may utilize tools like Burp Intruder or ZAP Fuzzer, but a simple bash script should be the best course for our exercise.

We can start by calculating the hash for each of the first ten employees using the same previous command while using tr -d to remove the trailing - characters, as follows:

[!bash!]$ for i in {1..10}; do echo -n $i | base64 -w 0 | md5sum | tr -d ' -'; done

cdd96d3cc73d1dbdaffa03cc6cd7339b
0b7e7dee87b1c3b98e72131173dfbbbf
0b24df25fe628797b3a50ae0724d2730
f7947d50da7a043693a592b4db43b0a1
8b9af1f7f76daf0f02bd9c48c4a2e3d0
006d1236aee3f92b8322299796ba1989
b523ff8d1ced96cef9c86492e790c2fb
d477819d240e7d3dd9499ed8d23e7158
3e57e65a34ffcb2e93cb545d024f5bde
5d4aace023dc088767b4e08c79415dcd

Next, we can make a POST request on download.php with each of the above hashes as the contract value, which should give us our final script:

#!/bin/bash

for i in {1..10}; do
for hash in $(echo -n $i | base64 -w 0 | md5sum | tr -d ' -'); do
curl -sOJ -X POST -d "contract=$hash" http://SERVER_IP:PORT/download.php
done
done

With that, we can run the script, and it should download all contracts for employees 1–10:

[!bash!]$ bash ./exploit.sh
[!bash!]$ ls -1

contract_006d1236aee3f92b8322299796ba1989.pdf
contract_0b24df25fe628797b3a50ae0724d2730.pdf
contract_0b7e7dee87b1c3b98e72131173dfbbbf.pdf
contract_3e57e65a34ffcb2e93cb545d024f5bde.pdf
contract_5d4aace023dc088767b4e08c79415dcd.pdf
contract_8b9af1f7f76daf0f02bd9c48c4a2e3d0.pdf
contract_b523ff8d1ced96cef9c86492e790c2fb.pdf
contract_cdd96d3cc73d1dbdaffa03cc6cd7339b.pdf
contract_d477819d240e7d3dd9499ed8d23e7158.pdf
contract_f7947d50da7a043693a592b4db43b0a1.pdf

As we can see, because we could reverse the hashing technique used on the object references, we can now successfully exploit the IDOR vulnerability to retrieve all other users’ contracts.

IDOR in Insecure APIs

So far, we have only been using IDOR vulnerabilities to access files and resources that are out of our user’s access. However, IDOR vulnerabilities may also exist in function calls and APIs, and exploiting them would allow us to perform various actions as other users.

While IDOR Information Disclosure Vulnerabilities allow us to read various types of resources, IDOR Insecure Function Calls enable us to call APIs or execute functions as another user. Such functions and APIs can be used to change another user's private information, reset another user's password, or even buy items using another user's payment information. In many cases, we may be obtaining certain information through an information disclosure IDOR vulnerability and then using this information with IDOR insecure function call vulnerabilities, as we will see later in the module.

Going back to our Employee Manager web application, we can start testing the Edit Profile page for IDOR vulnerabilities:

When we click on the Edit Profile button, we are taken to a page to edit information of our user profile, namely Full Name, Email, and About Me, which is a common feature in many web applications:

We can change any of the details in our profile and click Update profile, and we'll see that they get updated and persist through refreshes, which means they get updated in a database somewhere. Let's intercept the Update request in Burp and look at it:

We see that the page is sending a PUT request to the /profile/api.php/profile/1 API endpoint. PUT requests are usually used in APIs to update item details, while POST is used to create new items, DELETE to delete items, and GET to retrieve item details. So, a PUT request for the Update profile function is expected. The interesting bit is the JSON parameters it is sending:

{
"uid": 1,
"uuid": "40f5888b67c748df7efba008e7c2f9d2",
"role": "employee",
"full_name": "Yourname",
"email": "yourname@gmail.com",
"about": "A Release is like a boat. 80% of the holes plugged is not good enough."
}

We see that the PUT request includes a few hidden parameters, like uid, uuid, and most interestingly role, which is set to employee. The web application also appears to be setting the user access privileges (e.g. role) on the client-side, in the form of our Cookie: role=employee cookie, which appears to reflect the role specified for our user. This is a common security issue. The access control privileges are sent as part of the client's HTTP request, either as a cookie or as part of the JSON request, leaving it under the client's control, which could be manipulated to gain more privileges.

So, unless the web application has a solid access control system on the back-end, we should be able to set an arbitrary role for our user, which may grant us more privileges. However, how would we know what other roles exist?

Exploiting Insecure APIs

We know that we can change the full_name, email, and about parameters, as these are the ones under our control in the HTML form in the /profile web page. So, let's try to manipulate the other parameters.

There are a few things we could try in this case:

  1. Change our uid to another user's uid, such that we can take over their accounts
  2. Change another user’s details, which may allow us to perform several web attacks
  3. Create new users with arbitrary details, or delete existing users
  4. Change our role to a more privileged role (e.g. admin) to be able to perform more actions

Let’s start by changing our uid to another user's uid (e.g. "uid": 2). However, any number we set other than our own uid gets us a response of uid mismatch:

The web application appears to be comparing the request’s uid to the API endpoint (/1). This means that a form of access control on the back-end prevents us from arbitrarily changing some JSON parameters, which might be necessary to prevent the web application from crashing or returning errors.

Perhaps we can try changing another user’s details. We’ll change the API endpoint to /profile/api.php/profile/2, and change "uid": 2 to avoid the previous uid mismatch:

As we can see, this time, we get an error message saying uuid mismatch. The web application appears to be checking if the uuid value we are sending matches the user's uuid. Since we are sending our own uuid, our request is failing. This appears to be another form of access control to prevent users from changing another user's details.

Next, let’s see if we can create a new user with a POST request to the API endpoint. We can change the request method to POST, change the uid to a new uid, and send the request to the API endpoint of the new uid:

We get an error message saying Creating new employees is for admins only. The same thing happens when we send a Delete request, as we get Deleting employees is for admins only. The web application might be checking our authorization through the role=employee cookie because this appears to be the only form of authorization in the HTTP request.

Finally, let’s try to change our role to admin/administrator to gain higher privileges. Unfortunately, without knowing a valid role name, we get Invalid role in the HTTP response, and our role does not update:

So, all of our attempts appear to have failed. We cannot create or delete users as we cannot change our role. We cannot change our own uid, as there are preventive measures on the back-end that we cannot control, nor can we change another user's details for the same reason. So, is the web application secure against IDOR attacks?.

So far, we have only been testing the IDOR Insecure Function Calls. However, we have not tested the API's GET request for IDOR Information Disclosure Vulnerabilities. If there was no robust access control system in place, we might be able to read other users' details, which may help us with the previous attacks we attempted.

Chaining IDOR Vulnerabilities

Usually, a GET request to the API endpoint should return the details of the requested user, so we may try calling it to see if we can retrieve our user's details. We also notice that after the page loads, it fetches the user details with a GET request to the same API endpoint:

As mentioned in the previously, the only form of authorization in our HTTP requests is the role=employee cookie, as the HTTP request does not contain any other form of user-specific authorization, like a JWT token, for example. Even if a token did exist, unless it was being actively compared to the requested object details by a back-end access control system, we may still be able to retrieve other users' details.

Information Disclosure

Let’s send a GET request with another uid:

As we can see, this returned the details of another user, with their own uuid and role, confirming an IDOR Information Disclosure vulnerability.

This provides us with new details, most notably the uuid, which we could not calculate before, and thus could not change other users' details.

Modifying Other Users’ Details

Now, with the user’s uuid at hand, we can change this user's details by sending a PUT request to /profile/api.php/profile/2 with the above details along with any modifications we made, as follows:

We don’t get any access control error messages this time, and when we try to GET the user details again, we see that we did indeed update their details:

In addition to allowing us to view potentially sensitive details, the ability to modify another user’s details also enables us to perform several other attacks. One type of attack is modifying a user's email address and then requesting a password reset link, which will be sent to the email address we specified, thus allowing us to take control over their account. Another potential attack is placing an XSS payload in the 'about' field, which would get executed once the user visits their Edit profile page, enabling us to attack the user in different ways.

Chaining Two IDOR Vulnerabilities

Since we have identified an IDOR Information Disclosure vulnerability, we may also enumerate all users and look for other roles, ideally an admin role. Try to write a script to enumerate all users, similarly to what we did previously.

Once we enumerate all users, we will find an admin user with the following details:

{
"uid": "X",
"uuid": "a36fa9e66e85f2dd6f5e13cad45248ae",
"role": "web_admin",
"full_name": "administrator",
"email": "webadmin@employees.com",
"about": "I'm Admin haha!"
}

We may modify the admin’s details and then perform one of the above attacks to take over their account. However, as we now know the admin role name (web_admin), we can set it to our user so we can create new users or delete current users. To do so, we will intercept the request when we click on the Update profile button and change our role to web_admin:

This time, we do not get the Invalid role error message, nor do we get any access control error messages, meaning that there are no back-end access control measures to what roles we can set for our user. If we GET our user details, we see that our role has indeed been set to web_admin

Now, we can refresh the page to update our cookie, or manually set it as Cookie: role=web_admin, and then intercept the Update request to create a new user and see if we'd be allowed to do so:

We did not get an error message this time. If we send a GET request for the new user, we see that it has been successfully created:

By combining the information we gained from the IDOR Information Disclosure vulnerability with an IDOR Insecure Function Calls attack on an API endpoint, we could modify other users' details and create/delete users while bypassing various access control checks in place. On many occasions, the information we leak through IDOR vulnerabilities can be utilized in other attacks, like IDOR or XSS, leading to more sophisticated attacks or bypassing existing security mechanisms.
With our new role, we may also perform mass assignments to change specific fields for all users, like placing XSS payloads in their profiles or changing their email to an email we specify.

You may do so by retrieving their uuids and then sending a PUT request for each with the new email.

--

--