API1:2019 Broken Object Level Authorization
Introduction
API1:2019 Broken Object Level Authorization
Threat agents/Attack vectors | Security Weakness | Impacts |
---|---|---|
API Specific : Exploitability 3 | Prevalence 3 : Detectability 2 | Technical 3 : Business Specific |
Whenever a request contains an identifier, an attacker has a possibility of exploiting the endpoint by changing that identifier. For example: /index.php?userID=1 An attacker might change this to /index.php?userID=2 and see data that does not belong to the attacker. The reason this vulnerability is so common is because API-based applications often do not check the users' authorisation level while relying on object identifiers where they should not even have access to. | This is the most common vulnerability on API endpoints. Modern applications are widespread and often consist of multiple integration points which makes authorisation a lot a harder. Even if a good security design is chosen and the organisation creates a good common authorisation framework, the developers might forget to implement the solution while creating or refactoring a feature with sensitive data. Typically we can not automate access control testing, which makes this vulnerability even more prevelant. | Sensitive data might leak to users that are not authorised to view or edit that data. For example /index.php?id=1 might display the data of another user that the attacker is not allowed to see. The attacker might also manipulate the data, for example if they send a POST or a PUT request on an object that does not have proper access control. This issue so severe, it might even lead to a full account takeover, for example if an attacker can change the email address of another user, the attacker can reset the password and it will be sent to their inbox. |
Untitled |
What is Broken Object Level Authorisation?
Broken Object Level Authorisation all starts with an object. Objects should be looked at in the context of "Object-Oriented Programming", what I mean by that is objects are the things you think about first in designing a program and they are also the units of code that are eventually derived from the process. This can be anything, ranging from an account to an invoice to a credit note and everything in between. Usually, these objects are marked with an identifier because we need to address these objects directly and this is where issues can arise because we need to always check if the user is allowed to access that object. This might seem simple at first but I can assure you it's not, more about that later in the "testing" section of this article.
A broken Object issue occurs when the server does not properly check if the currently logged-in user or if a logged-out user can read, update or delete an object to which they do not have the rights.
Two main types of Broken Object Level Authorization
There are two types of broken authorization on objects, these can occur because either a userID is passed on to the server or an object, we will look into both.
Based on user ID
Sometimes a userID is passed on to the server, this can happen when we request all the resources for a certain user for example:
https://google.com/get_users_search_history?userID=1234
If we replace the userID with someone else's userID, we should not be able to get the search history of other users but when a Broken Object Level Authorization issue arises, we can view the search history of other users.
This issue is simple to solve as a developer. We simply need to check if the currently logged-in user is allowed to access those objects. In this example, we need to check if the userID from the GET parameter is the same as the userID of the object's owner.
Psuedo code:
if($_GET['userID'] === object.ownerID){
ShowData()
}else{
echo "You are not allowed to view this data"
wait(5s)
Redirect(Homepage)
}
This is the safest way to handle the exception, DO NOT USE THE FOLLOWING CODE AS IT IS NOT SAFE:
if(!$_GET['userID'] === object.ownerID){
echo "You are not allowed to view this data"
wait(5s)
Redirect(Homepage)
}
ShowData()
The data does not get rendered because of the redirect but a simple push of the back button can still enable the data to be displayed
Based on object ID
This vulnerability type can also exist because objectID's are passed to the server when the server does not properly check if the user is authorized for that object. This can happen very easily for example when a developer needs to secure some resources and some not. They might forget to secure one of the objects which should be secured.
Example of an attack
Whatever type we are dealing with, some properties are the same across both.
These issues can arise more easily in two situations but of course, they can arise any time that a developer forgets to add authorization. We will zoom into two examples:
- When functionality is being developed and needs a change or addition after a long time. Often sufficient documentation does not exist and by the time development begins, the developers might have forgotten part of the functionality. If this happens, it's easy to forget initial authorization requirements and they might get overwritten or simply removed.
- For example, if you build a function to add products but users should only be able to edit their own products. We have 3 calls we can make to a product:
- GET product.php?id=12 for getting the details
- POST product.php?id=12 for updating a product's details
- DELETE product.php?id=12 to delete a product
- OPTIONS
- These calls are secure and users can only execute them for their own objects
- After a year we want to add an option to import products via CSV
- POST /import.php? with a CSV body containing id,name and price: {1,"test",12}
- The problem is that the developers forgot to check if the user is allowed to write to that product with id=1 and the attacker can overwrite product details of products that do not belong to them
POST updateProduct.php?productID?id=1 if($_GET['productID'].owner === object.ownerID){ UpdateData() }else{ echo "You are not allowed to view this data" wait(5s) Redirect(Homepage) } POST /import.php? StartUpdateData()
In this pseudocode example we can see the described vulnerability in action
- For example, if you build a function to add products but users should only be able to edit their own products. We have 3 calls we can make to a product:
- When a functionality integrates with another functionality, it's easy to overlook certain security considerations because the two functionalities are often developed separately and sometimes even in separate teams. If software designers and developers are not careful, they can easily make a mistake and forget to implement some required checks that prevent Broken Object Level Authorization vulnerabilities.
- For example, we are building a function to update the prices of our products based on the products of our supplier every 10 minutes if a user is using the website but we are only allowed to update the prices of our own products. A call might get implemented that checks the prices in the background. An attacker can abuse this call to check the prices if they simply replace the objectID but it goes unnoticed because the call happens in the background.
TimerForProducts(){ wait(24H) UpdatePrices($_GET[UsersProducts[]) } UpdatePrices(products[]){ for each product in products{ getPrice() } } GET /updatePrices?UsersProducts=1,2,3,4 An attacker can easily abuse this background call by passing any productID' GET /updatePrices?UsersProducts=1,2,3,4,5,6,7,8,9,...
How to Detect and Prevent Broken Object Level Authorization
Detection
To detect these vulnerabilities we need to test read, update, and delete actions on all objects that we are not authorized for. We can do this in two ways:
- Replace every object that we encounter with one that we are not authorized for and see if we can execute the call successfully
- Replace our authentication token with the token from another user and browse our own objects. The advantage of doing this is we can automate this approach, it's really hard to automate replacing every object because it can be named differently (i.e. address, productID,...) but the authorization header is always the same.
Whatever approach we decide to take, it is vital that we check all objects and that we check them for reading, updating, and deleting actions. We need to check every functionality that has access to these objects, even if it's via a secondary route (for example importing products instead of adding them manually).
Prevention
We can form some general tips for preventing Broken Object Level Authorization defects. These will help prevent the vulnerability or will lower the impact if one occurs.
- Instead of sending the userID as a parameter, we should use an auth token such as JWT and extract the userID from there
- We should always use GUIDs as id's, these are long and random strings of numbers and letters that make it a lot harder to guess other users' identifiers
- Create a centralized authorization solution that you can re-use for every sensitive object. This will prevent your code from becoming a mess of different authorization mechanisms
- We should use that authorization mechanism to verify read, delete, and update functions on objects that should be private
- We need to make sure that tests are in place to ensure the existing authorization mechanisms keep functioning as intended
Conclusion
Broken Object Level Authorization defects are becoming ever more prevalent as functionalities of applications increase and more and more APIs are built. This requires more and more ethical hackers as it can be a severe vulnerability and it can be very easy to notice, all someone has to do is replace a number in a request if the server has not been configured properly.
Every endpoint that handles objects and receives an ID should properly enforce Object Level Authorization. The Object Level Authorization should check that the user who is trying to read or manipulate an object has the correct authorization for it.
It is really important to have proper authorization checks are in place and we should always stop users from performing actions on objects they are not allowed to perform.