API Security: How to avoid Broken Object Level Authorization & Broken Function Level Authorization
OWASP project recently finalised their API Security Top 10 list into RC level; you can have a look at it from here. When I went through the list, I was a bit surprised because most of the top security vulnerabilities are fundamental principles that we had been practising for a long time; it seems that we have forgotten most of these basics while we are busy with new technology advancements.
This post discusses Broken Object Level Authorization and Broken Function Level Authorization vulnerabilities and several remedies to avoid these vulnerabilities. I start by discussing Broken Function Level Authorization and then move into Broken Object Level Authorization.
Broken Function Level Authorization
Broken Function Level Authorization is the 5th top-most API security vulnerability in OWASP list. First, let’s understand what that means without using any security jargon; here, I use a very straightforward (hypothetical) exam management system to explain the issue.
Assume there is a modern exam management system called “GetMyResults”, which provides two separate mobile apps for students & teachers, and a modern RESTful API backs the mobile apps. Once the student app is installed, it only allows for a particular student to check individual marks; there are no other functionalities available in this app. In contrast to that, the teacher app will enable teachers to submit marks to the system; teachers are allowed to submit marks for modules that have been assigned to them.
If we look at the system architecture, there is a RESTful API called “Results”, which supports basic CURD operations; and this API is adequately secured using OAuth2 protocol in a way that mobile apps should receive an access token from the system by redirecting to the system for authentication and authorization.
Assume one bright student managed to analyse the student mobile app and found out the following details.
- Student mobile app calls the following API.
2. The mobile app uses the following OAuth2 access token to call the above API.
Now, with his RESTful API design skills, the student tried to perform the following experiment, just using a straightforward tool like cURL. He tried to make the following call presenting the above access token.
After the experiment, the student surprised to see that his marks for maths have changed to 98! How that possible?
To understand what went wrong, let’s carefully examine the following OAuth2 response that mobile apps have received from the system.
Can you find any similarities between the above OAuth2 responses other than the same format?
Yes, you are correct; although access tokens and refresh tokens are different, the value of the scope attribute is the same.
Now you may think, what is wrong with that? It makes sense to have different access tokens and refresh tokens because those are bound to different users and different apps (OAuth2); but, the scope is attached with API, so it should be the same value. This is the exact point that I wanted to emphasise! According to API security best practices, OAuth2 scopes need to be designed based on functional capabilities in a fine-grained manner instead of having one scope per each API. The current scope design can be depicted as follows.
The following diagram shows the same API after applying the above-discussed fine-grained scope principle
Now, let’s try to compare OAuth2 response traces from both apps after applying the above principle.
As you can see, the student app has received an access token with only “read-scope”, which means it can only execute GET resources. Still, on the other hand, the teacher app has received an access token with two scopes to perform both read and write operations.
If you have some experience in the OAuth2 framework, you may want to raise another question: regardless of the above design fix, when an attacker makes a token request, the attacker still can send a request with both scopes, so, what can we do to prevent that?
Firstly, your API scope implementation is needed to be backed with a robust authorization mechanism. Once a user gets authenticated to the system, it should check whether is it possible to generate a token with all the requested scopes based on authorization policies. For example, let’s consider a simple Role-Based Access Control (RBAC) system; in such scenarios, it’s required to maintain a scope-to-role mapping somewhere and evaluate before generating access tokens.
An alternative approach would be, keep the Coarse-grained scope design at the API level and perform fine-grained authorization checks at the backend API level; personally, I don’t like this approach because it misses the whole point of the Secure API concept. However, this does not mean you should forget backend service-level authorization; instead, the point is you should not design vulnerable APIs making assumptions on backend security.
Broken Object Level Authorization
Now, let’s try to understand Broken Object Level Authorization vulnerability using the same example. Also, note that this is the number -1 API security evaluability of OWASP list. Our smart student knows the student-ids of some of his friends, so he wanted to give it a try by making a call to the API with an id belong to one of his friends. He eventually ran a cURL with the following format and managed to read his friends’ mark! This is not the intended usage of the system. Students should check their own marks and certainly not the marks of other students! In this example, the issue begun due to a lack of object (data) level authorization.
To prevent this vulnerability, the API layer should check whether the requested party can access the required data/objects based on authorization policies. To highlight again, this issue is different from the previously discussed Broken Function Level Authorization vulnerability. In the current case, the attacker is making a legitimate API call by trying to view results, but the problem here is the attempt to access a data element that is not authorised to access.
There are multiple solutions to prevent this vulnerability but performing the authorization check for data/object access is a common requirement for any of these solutions; I have discussed two possible solutions below.
- For an API hosted in an API Gateway, it is possible to enforce an authorization check at the gateway level; either the API Gateway itself can perform policy evaluation locally, or it is possible to delegate an authorization check to a remote policy evaluation point. These access policies can be stored centrally or possible to sync with the local policy stores periodically. Policy implementations such as XACML, Open Policy Engine (OPA) can define these policies. For instance, in this example, there should be a policy similar to “ If the user is authenticated and the role is ‘student’ then he can only access the data record that contains user’s id”. However, this centralised policy management and evaluation approach may not suit some use cases due to complexity and inflexibility.
- Suppose the API is local to the app, such as in Java JAX-RS or SpringBoot REST service. In that case, it’s straightforward to perform these data/object level authorization checks using traditional Role-Based Access Control (RBAC) mechanisms. Compare to the above approach; this is much simple and easy to manage.
How to address both together?
Considering both vulnerabilities and fixes discussed so far, I would like to present two unified solutions to address both vulnerabilities
Solution 1 :
- Perform API resource (function) level authorization at the API Gateway based on OAuth2 scopes to prevent Broken Function Level Authorization vulnerability. If the access token is an opaque token, the gateway should contact the OAuth2 STS via Introspection Endpoint to learn more about the token before the authorization check occurs. If the access token is a self-contained token such as JWT, the gateway can validate the token itself by checking the signature.
- Once the above step is completed, to prevent Broken Object Level Authorization vulnerability, the gateway should perform another authorization check to determine whether the authenticated user can access the requested data/object, for this, the gateway can locally evaluate the access policies, or the gateway can contact remote policy evaluation points such as OPA engine or XACML PEP/PDP.
Solution 2 :
- This step is identical to the 1st step of the above solution, perform API resource (function) level authorization at the API Gateway based on OAuth2 scopes to prevent Broken Function Level Authorization vulnerability. If the access token is an opaque token, the gateway should contact the OAuth2 STS via Introspection Endpoint to learn more about the token before the authorization check takes place. If the access token is a self-contained token such as JWT, the gateway can validate the token itself by checking the signature.
- Once the 1st step is completed, the gateway should pass the message to the backend along with a JWT token, and this token carries user identity, user attributes and roles. The gateway or STS service can generate this JWT token based on the authenticated user.
- Based on information available in the JWT token at the backend API, the backend API is responsible for performing an authorization check to determine whether the authenticated user can access the requested data/object. The backend API either perform this check locally or possible to delegate to a remote policy engine.