The Authorization Maze
During the creation process of the OWASP API Top 10 2023, I found that some security participants look at the concept of authorization as a straightforward mechanism that doesn’t require special attention.
I’ve been asked - “Why do you insist on talking so much about Authorization when it comes to API Security?”; they view the authorization problem as something that can be solved using straightforward best practices and a little bit of engineering work, similar to Authentication and Injections.
That’s a valid standpoint that I understand. But in reality, authorization is an incredibly difficult problem to solve. And that’s why we see APIs of large companies with large security teams still get compromised repeatedly.
In this article, I’ll try to shed some light and explain the challenges that engineering teams face when they implement authorization in their APIs.
What is authorization?
Authorization is the process of determining if someone is allowed to perform a certain action.
For example - when I go to visit my local bank, I’m authorized to perform a limited set of actions, such as waiting in the lobby, withdrawing cash from my account, etc.
Anyhow, I’m not authorized to access the vaults of other people or sit at the teller’s counter. If I tried to do so, the security guard would enforce the authorization policies of the bank by kicking me off and potentially calling the police.
So the authorization policies of the bank would be something like:
When an engineer builds a new API, they must have the same idea in mind. Asking themselves - what are the authorization policies and how do I enforce them when building this new API?
These questions don’t always have straightforward answers. They tend to leave engineers confused, and APIs vulnerable, waiting to be exploited.
Authorization in APIs
APIs are a fertile ground for authorization problems. There are many reasons why, but I’ll just say that APIs, by design, expose a lot of sensitive information, they are frequently changed and accessed by different types of users.
The “OWASP Top 10 for APIs” list mentions 3 main types of Authorization problems:
Broken Object Level Authorization (BOLA): APIs tend to expose endpoints that handle object identifiers, creating a wide attack surface for Object Level Access Control issues. Object level authorization checks should be considered in every function that accesses a data source using an ID from the user.
Broken Object Property Level Authorization (BOPLA): This focuses on the lack of or improper authorization validation at the object property level, leading to information exposure or manipulation by unauthorized parties.
Broken Function Level Authorization (BFLA): Complex access control policies and unclear separation between administrative and regular functions lead to authorization flaws.
Each one of them talks about a different type of authorization problem.
Airbnb Example
To explain these three types, let’s talk about the API of Airbnb.
Disclaimer: I haven’t found any vulnerability on Airbnb (yet), but I just find it as a good example for an API-based modern application.
The Airbnb platform has two types of users: guests and hosts.
If you want to book a place as a guest, the host has to approve it first.
Let’s look at the reservation process and how it looks behind the scenes:
- Guest send a request to the host to stay at their place
POST /send_reservation_rqeuest
{“property_id”:717,”comments”:”I’d love to book the place but I’m traveling with my dog. He’s really small and well behaved. Can you make an exception for me?”}
- Host approves the request and changes the reservation based on the special request
POST /approve_reservation?reservation_id=919
{“approved”:true,”pets_allowed”:true}
- Guest gets charged and receives a confirmation
I want to talk about the second step in the process, the API call to “/approve_reservation” in order to explain the three types of authorization problems.
BFLA: the API endpoint “approve_reservation” should be accessed only by hosts.
If an Airbnb guest manages to access this endpoint without getting blocked, the API is vulnerable to Broken Function Level Authorization, since the user (Airbnb guest) can access a function that shouldn’t be accessed by its group.
BOLA: even if the user who’s trying to access the endpoint is an actual host, there’s still more authorization work to do. Each Airbnb host has their own properties.
If the API allows you to accept reservations that don’t belong to you, the API is vulnerable to BOLA. In this scenario a malicious user can decline or accept reservations on behalf of other hosts.
BOPLA: even if you verify that the user is a host, and that the reservation belongs to them, there is still one more type of authorization you should keep in mind. A host should be able to change only a specific set of properties in the reservation.
A host can change the check-in time, or the flag that determines whether dogs are allowed or not. But some properties are more sensitive and shouldn’t be accessed directly by the host.
For example - if the host manages to change directly the final price of the reservation, the API is vulnerable to BOPLA.
BOLA Challenges
BOLA is by far the most common API vulnerability.
Every company I’ve tested over the years has had at least one critical BOLA vulnerability that allowed me to leak information of other users.
BOLA Exploitation
Exploitation of other vulnerabilities, such as SQL Injection, can be VERY complex. Check this article and learn about different SQLi techniques: blind, out of band, etc.. exploitation depends on the framework, DB version, where the input is concatenated in the query, and much more.
On the other hand, when it comes to BOLA exploitation - it’s incredibly simple. Once you find the vulnerable input, you just change it to an ID of someone else.
For example, in the Airbnb example, an exploitation would look something like:
POST /accept_reservation?id=717 —> POST /accept_reservation?id=718
(legit) (attack)
BOLA Protection
You might look at the above payload and ask yourself - if the exploitation is so simple and straightforward, why is it so difficult to protect against?
Great question!
While protection against other types of attacks might be simple, such as special encoding or input validation, the protection against BOLA, in many cases, is complex, and could even involve more than one team of engineers.
Objects Access Policies
To prevent BOLA, your API has to implement an authorization system that considers object access policies.
Object ownership is the concept of determining “user X has Y access to object Z”, for example: “User 616[Inon] has READ access to reservation #717”.
A user might have or not have access to a specific object for different reasons:
* Ownership - the user is the owner of the object because they have created it.
E.g, if I posted a photo on my Instagram, I should be able to delete it, because I’m the owner of the object (photo).
* Sharing - the object was shared with the user.
E.g, if I want my colleague to review my article, I can share the Google Doc file with her. She would have write access to the file, even though she’s not the owner.
* Public objects - some objects are public by design.
E.g, everybody should have read access to an article on your favorite news website.
* Administration - some users have high privileges that allow them to access objects of others, even though they weren’t specifically “invited” to do so.
E.g, if I post an offensive tweet, Twitter moderators can delete my tweet. They have access to delete objects that I own.
The more applications evolve and become interconnected, the more scenarios you have when it comes to object access control.
Shared Responsibility
To make things more complicated, sometimes the engineer who writes the piece of code that should have BOLA protection, doesn’t know what the object’s access policies are.
An engineer who works for Uber has a new task assigned to them: implement an API Endpoint to export trip details into PDF. The API receives the ID of the trip
(e.g, GET /export_trip?trip_id=414) and returns a PDF.
The engineer knows very well how to pull the information from the DB, clean the data, and use third party libraries to convert it into a pretty PDF document.
Object access policies, on the other hand, are a different story and are more related to the business logic of the API rather than pure engineering.
Some scenarios that should be considered here:
- trip #414 is a carpool trip, meaning multiple riders should have access to the same object.
- trip #414 was taken in CA, but an Uber moderator (admin) from NY is trying to access it. It should be blocked since moderators are limited to their operation zone.
- the user who took the trip had invited a friend to join the trip, but they accidentally put the wrong phone number, and then removed the user from the trip.
The engineer who writes the endpoint might not be even aware of all of these particularities. It’s simply not their job.
As you can see, object access policies can be dynamic, and might even change after API Endpoint was already written.
For this reason, some companies decide to separate the BOLA protection into separate components:
* Decision Engine - a component that is written by an experienced team of engineers who deeply understand the business logic of the API and all possible authorization scenarios. It could be a different function in the code, another library or even another microservice.
* Asking the decision engine - the engineer who writes the API endpoint, simply needs to use a function, such as “authz.user_has_access_to_ride(user_id,trip_id)” and let the decision engine take care of it.
Bike Sharing Example
I recently spent a few weeks in my favorite US city - Austin, Texas. I decided to explore the city using the shared bike system.
After taking a ride, the app asks you if there were any problems with the bike. I had a flat tire one time, so I marked it on the app, and the bike was unavailable for other users.
That’s a great feature, but it made me wonder what happens behind the scenes
The API behind scenes looks something like:
POST /report_problem
{“bike_id”:841,”flat_tire”:true}
Can you cause a DoS by reporting a flat tire for all the bikes?
If you do, the exploit wouldn’t be very complicated, you’d just need to write a script to enumerate all the possible bike numbers (e.g, bike_id=1….9999).
On the other hand, writing the protection for this API Endpoint would be interesting, because of the dynamic object access policies: the user doesn’t own the bike. They just have access to it for a short period of time.
The “report_problem” endpoint should only allow you to access bikes that you’ve recently used. It wouldn't make sense to report other bikes in the same station, or a bike you used a couple of days ago.
I like this example, because it shows you how unique and dynamic the object access policies may be in APIs.
Conclusion
I hope the examples above helped you understand a bit better the challenging nature of implementing authorization mechanisms in APIs, and why authorization issues keep being the main attack vector when it comes to modern applications.
The Inside Trace
Subscribe for expert insights on application security.