How to Detect and Avoid IDORs in Modern Web Applications?
This category of bugs is very hard to discover during static code analysis or code review. They can’t be mitigated with any kind of additional “magical” security appliance such as next generation firewalls or web application firewalls. This is a business logic error, made in the design or implementation phase. A webapp with an IDOR makes it possible for any user to access other users' data.
Why Should I Care?
Insecure Direct Object References always lead to unauthorized access to data, which may be very costly in the GDPR era. Perhaps the most famous IDOR–related data leak was the First Americans Financials’. They were charged by the New York State Department of Financial Services for leaking over 350,000 documents containing highly confidential mortgage–related data.
In the EU, regulators are not disclosing details about data leaks, but they’re describing this kind of security issue as “insufficient technical and organizational measures to ensure information security”. That’s why it’s hard to estimate how many of all data breaches were caused by IDORs.
Based on my experience, eight out of ten discovered critical vulnerabilities in modern web applications are insecure direct object references.
Why Is This Happening?
Typically, training materials (i.e. PortSwigger Web Security Academy) show IDOR on the example of a classic web application where request parameters can be easily modified in the browser's URL bar.
Such a URL catches the eye. Most testers will check what would happen if the parameter is changed and detect the vulnerability early enough, before the app is deployed live.
However, modern web applications typically use RESTful or GraphQL APIs. In such apps, it is more difficult to test IDOR vulnerabilities.
I will use the OWASP Juice Shop test application to better demonstrate this problem. In this webapp, the URL address visible in the browser does not contain parameters that, if manipulated, could easily allow to test the IDOR.
Instead, the client's browser-side application sends requests to the API server, which at first glance are invisible to the user.
Below you can find a screenshot of the DevTools console's Network view in Chromium, showing the requests being made while adding an item to the cart.
The same happens in mobile applications. It may seem that since these requests are not visible, it is impossible to abusively modify them and expose the vulnerability.
This assumption is, however, incorrect. An attacker who has deep knowledge of the HTTP protocol can take advantage of these vulnerabilities and compromise the app.
Detecting IDOR in Modern Web Applications
In order to test a modern web application that utilizes JavaScript AJAX methods to contact with APIs, use a web proxy on which these requests can be intercepted and modified. To show this I will use the BURP Suite.
Parameter Enumeration
Parameter enumeration in most cases allows one to access resources of other users with the same set of privileges in the app (horizontal movement).
The test scenario is as follows:
- Authenticate in the application with an existing account.
- Perform a series of actions that will be recorded by the proxy (Burp).
- Re-send the recorded requests with changed parameters which could potentially point to someone else's resources (using the Repeater module in the Burp Suite).
In this particular case, I will try to display the contents of someone else's cart.
In the Burp history, you can see that a GET request for a cart with ID 7 has been sent. This is the ID provided by the business logic of the application, stored on the user's browser side.
In the authorization header of this request you can see the JWT token of the currently logged-in user.
The API response is a JSON document representing the logged-in user's cart. It shows user ID 22 and an empty “Products” object, which corresponds to an empty cart view on the site.
What happens when I replace the cart ID in my request?
I sent this request again, changing the cart ID parameter to a number one lower.
In the response, I got a JSON file with the contents of someone else’s cart. The “Products” object now contains apple juice. Also note the different Cart User ID - 21.
So we have an IDOR vulnerability detected. You can use the same approach to test other endpoints and other HTTP methods, including those that modify data, e.g. adding a product to the cart.
Access to functions
There are applications where access to certain functions is restricted due to the role of the user.
When testing such an application, you should test a scenario where a user with lower privileges tries to perform actions intended only for users with higher privileges.
- Authenticate in the application with an account with the highest privileges.
- Perform actions in the app - requests to the API will be recorded by the proxy.
- Authenticate in the app with a lower privilege account to generate a token for the Authorization header.
- Replay the recorded API requests with the Authorization header changed - the token of the user with lower privileges is used.
Prevention
An Insecure Direct Object Reference is a business logic vulnerability. It can be prevented only in the design and implementation phase.
Add Security Requirements to User Stories
The first and most basic operation is to correctly define the security requirements in each designed function. I know, it sounds scary, so I will show what it is with an example.
Imagine a function defined as "As a user, I want to be able to display my shopping cart". For such a user story, the programmer will naturally implement an API method that shows a cart with a given identifier. The tester will test the story to confirm that the user receives the expected cart contents.
A story with a security requirement added might look like this: “As a user, I want to be able to view my shopping cart, but only if I have access to it.” In this case, it is natural for the developer to add a user permissions check before returning the result. The tester, seeing the condition in the story, will check whether the user has access to other people's resources.
An even better-worded story could read as follows: "As a user, I want to be able to display only my own cart, and not any other user’s cart”. For such a function, the programmer, instead of creating a GET method for the cart object with the given ID, will create a GET method for the dedicated cart object without the ID parameter (alternatively “mycart”). Determining which cart is to be returned by the method will require obtaining data about the currently logged-in user (e.g. from the JWT token), which will significantly reduce the risk of an IDOR vulnerability.
Create Proper Unit Tests
The next step is to create appropriate unit tests to cover the edge cases. It is much easier to define these cases having defined security requirements in stories as shown above. The standard set of edge cases should include the following scenarios:
- The user is not authenticated, e.g. the authorization header is missing or invalid.
- The user is authenticated but not authorized to the resource.
Perform Full Integration tests
The next step is full integration testing taking into account edge cases. For the API, each method must be tested for each endpoint, including their behavior in the cases described above. Dedicated tools are required for API testing, e.g. Postman, OWASP ZAP, Burp Suite.
Perform penetration tests
Penetration tests of web applications are crucial because they provide an end-of-state check of protection against real risks. Recent fines under GDPR have shown that regulators are taking the requirement of testing and measuring of security controls into consideration during investigations.
Summary
I’ve heard the following sentence: “Insecure Direct Object Reference is a modern age SQL injection”. Based on my experience, this is absolutely true when comparing the amount of such vulnerabilities discovered during my penetration tests.
There is a slight difference, though, which makes protection against IDOR so hard: no framework, library, or web application firewall can protect against business logic mistakes. The only solution is "security by design", introduced in the very early stages of software development. Developers must not only code, but also design applications, keeping in mind all threats which can be spotted on the Internet.