A Deep Dive On The Most Critical API Vulnerability — BOLA (Broken Object Level Authorization)

Intro

In this article I dig into the details about Broken Object Level Authorization (BOLA) — the most common and most severe API vulnerability today according to the OWASP API Security Project.

  • For engineers — technical explanation
  • For builders — how software developers can fix BOLA
  • For breakers — how pen testers can exploit BOLA
  • Q&A for curious engineers — deep dive

For Managers — How To Understand BOLA

Unlike other vulnerabilities, BOLA is not very intuitive and some people struggle to understand it. I found a good metaphor to explain BOLA to a non-technical audience. Feel free to use this at your next cocktail party.

  • James is the vulnerable code who doesn’t implement authorization checks.
  • The jacket is the exposed object.

How Can We Mitigate BOLA Today?

There are a few ways to protect your APIs and eliminate these types of vulnerabilities:

  1. Solution Planning — A developer with a deep understanding of the API can define specific authorization policies for the vulnerable endpoint.
  2. Fix — The fix is usually just a few lines of code and a developer who’s familiar with the API should be able to apply this fix.

For Engineers — A Deeper Dive On BOLA

Resources In Modern Applications

Modern applications handle many types of resources and each resource might have sub-resources. Let’s take a look at a ride sharing application as an example:

  • Trip
  • Receipt
  • Text conversation between driver & rider → Message
  • Vehicle → Car, Scooter, Bike

API Endpoints And Resources

In modern REST APIs, each one of these resources is represented by a JSON object, and each resource usually has an API endpoint that exposes it in different ways. For example:

  • {“pick_up_location”:”Golden Gate Park”,”destination”:”Dolores Park”}
  • API endpoints that are related to “Trip”:
  • GET /api/trips/{trip_id} — to fetch a specific trip
  • POST /api/trips/{trip_id}/add_tip_to_trip — add tip to an old trip
  • POST /api/trips — create a new trip
  • DELETE /admin/api/trips/{trip_id} — admin function to delete a trip

API Endpoints And Objects ID

Because one user can have multiple objects from the same resource (e.g. trip), some API endpoints need to know which object (e.g. a specific trip) the user is willing to access. This is where the object ID comes into the picture. The client basically sends the object ID that the user needs to access.

The Challenge

Exposing an object via its ID might be a good software design practice, but once the client can specify the ID, it opens a door to a very dangerous vulnerability — BOLA.

The Exploit

In BOLA, a user accesses objects that he should not have access to by manipulating the IDs.

For Builders — How To Build A Good Authorization Mechanism

A good authorization mechanism that addresses BOLA, should contain the following components:

  • Does user #666 have access to edit trip #929
  • Does admin #222 have access to delete user #555
  1. There is no one generic way to do it since it totally depends on the logic of the application.
  • Admin #222 is a regional admin of Northern California, and she should have access to delete all the users in this area. She wants to delete user #555, but she shouldn’t be able to do it, since user #555 is from NYC.
  • Centralized
  • Spread across many different places in the code

For Breakers — How To Exploit BOLA

Disclaimer: This is my comfort zone and I’m really excited to start writing about the fun part of BOLA — exploitation. As a pentester, if you deeply understand how to find and exploit BOLA, your glory is guaranteed :)

How To Think

  1. Understand the business logic of the application
    Don’t be on auto pilot and don’t change random IDs in API calls until you see PII in the response. There are automatic tools that can do this for you. Think.
  2. Every time you see a new API endpoint that receives an object ID from the client, ask yourself the following questions:
    * Does the ID belong to a private resource? For example, If we talk about some “news” feature, and the API endpoint is “/api/articles/555”, there’s a good chance that all the objects should be public by design, since articles are public.
    * What are the IDs that belong to me?
  3. Understand relationships between resources
    Understand that there’s a relationship between “trips” and “receipts” and that each trip belongs to a specific user.
  4. Understand the roles and groups in the API
    Try to understand what the different possible roles in the API are. For example — user, driver, supervisor, manager.
  5. Leverage the predictable nature of REST APIs to find more endpoints
    You saw some endpoint that exposes a resource in a RESTy way?
    For example: GET /api/chats/<chat_id>/message/<message_id>
    Don’t be shy, try to replace GET with another HTTP method.
    If you receive an error, try to:
    * Add a “Content-length” HTTP header
    * Change the “Content-type”

How To Test:

The basic way to test for BOLA is to guess the random ID of the object, but this doesn’t always work. The more efficient way would be to perform “Session Label Swapping”:

  1. Log two session labels from different users:
    Create two different users and document their session label.
    For example:

    User 1, Hugo =
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMTEiLCJuYW1lIjoiSHVnbyIsImlhdCI6MTUxNjIzOTAyMn0.JaTvHH5TbKzgRMa5reRDMiPjHEmPKY8axsu5dFMu5Ao

    User 2, Bugo =
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMjIiLCJuYW1lIjoiQnVnbyIsImlhdCI6MTUxNjIzOTAyMn0.uXTOzhX1mnYI3TZUSyjWS7qQXfYNn6qw-MehVGAgvpc

    Keep them in a .txt file.
  2. Login as user #1, sniff the traffic and find an API call to an endpoint that receives an object ID from the client. For example: /api/trips/e6d84adc-b1c7–4adf-a392–35e5b71f068a/details
  3. Repeat and intercept the suspicious API call.
  4. Change the session label of user #1 to the session label of user #2 — in order to make a call on behalf of user #2x.
  5. Absorb the result. If you received an authorization error, the endpoint is probably not vulnerable.
  6. If the endpoint returns details of the object, compare the two results and check if they are the same. If they are the same — the endpoint is vulnerable.

Tips & Tricks:

  • Object IDs in URLs tend to be less vulnerable. Try to put more effort on IDs in HTTP headers / bodies.
  • GUID instead of numeric values? Don’t give up!
    Use the “session label swapping” technique or find endpoints that return IDs of objects that belong to other users.
  • Always try numeric IDs. If you found that an endpoint receives a non-numeric object ID, like a GUID or an email address, give it a shot and try to replace it with a numeric value (e.g.: replace “inon@traceable.ai“ with the number “100”)
  • Received 403/401 once? Don’t give up!
    It’s not extremely common, but some weird authorization mechanisms only work partially. Try many different IDs. For example: if the endpoint “api/v1/trips/666” returned 403, run a script to enumerate 50 random IDs from 0001 to 9999.
  • Find the most niche features in the application
    Let’s say you found a weird feature to create a customized picture to your profile only during avocado awareness month (it’s June btw), and it performs an API call to /api/avocado_awarness_month/profile_pics/<avocado_emoji_id>.
    There’s a very good chance that the developers haven’t thought about authorization there.

How to bypass object level authorization:

  • Wrap the ID with an array.
    Instead of {“id”:111} send {“id”:[111]}
  • Wrap the ID with a JSON object
    Instead of {“id”:111} send {“id”:{“id”:111}}
  • Try to perform HTTP parameter pollution:
    /api/get_profile?user_id=<legit_id>&user_id=<victim’s_id>
    OR
    /api/get_profile?user_id=<victim’s_id>&user_id=<user_id>
    This can work in situations where the component that performs the authorization check and the endpoint itself use different libraries to parse query parameters. In some cases, library #1 would take the first occurence of “user_id” while library #2 would take the second.
  • Try to perform JSON parameter pollution
    POST api/get_profile
    {“user_id”:<legit_id>,”user_id”:<victim’s_id>}

    OR
    POST api/get_profile
    {“user_id”:<victim’s_id>,”user_id”:<legit_id>}

    It’s pretty similar to HTTP parameter pollution.
  • Try to send a wildcard instead of an ID. It’s rare, but sometimes it works.
  • Find API hosts where the authorization mechanism isn’t enabled. Sometimes, in different environments, the authorization mechanism might be disabled, for various reasons. For example: QA would be vulnerable but production would not.
  • Try to perform type-juggling
  • Not common, but a great example of how you can bypass BOLA protection

Q&A For Curious Engineers

Q: What is the object ID?

Usually the object ID is the actual primary key of the object in the database.
For example, this is the user table:

  • Email address / phone number (instead of user ID)
  • Alphanumeric name

Q: Are there different types of BOLA?

Yes. There are two main types of BOLA:

  1. Based on object ID.
    The API endpoint receives an ID of an object which is not a user object. For example:
    /api/trips/receipts/download_as_pdf?receipt_id=1111
    In many cases, it’s not a simple question — who should have access to this object?

Q: Why is it so common in modern applications?

I asked myself many times “why is BOLA so common in APIs?” After extensive research, I believe that the main reasons are:

Q: What solutions don’t solve the problem?

I saw some security talks that suggest using solutions that don’t really solve the problem.
Worse than that, I saw security solutions that claim to solve BOLA, but don’t.

  1. Relying on IDs from the JWT token.
    If you compare the ID inside the JWT token or any other type of auth token, to the user ID that was sent to the endpoint itself as a parameter, it doesn’t solve all BOLA occurrences.
    It could potentially solve type #1, where the vulnerable ID is the user ID. But it doesn’t solve type #2, and could miss many cases of type #1, like situations where a user is allowed to edit other users.
  2. Relying on an input from the request
    You could potentially put all the IDs that the client should have access to inside a storage that the client can’t manipulate because it’s signed by the server (JWT token or some type of viewstate), and then you could perform ID comparison of potentially vulnerable IDs to this list of “secured IDs”.
    If we ignore the fact that it breaks the REST design, it’s not feasible, because in modern APIs one user might have access to hundreds and even thousands of IDs.
  3. OAuth: Has nothing to do with BOLA.

Q: Why did we change the name from IDOR to BOLA?

Great question. IDOR is a cool name, but it’s not accurate.
The name “IDOR — Insecure Direct Object Reference” suggests two things:

  1. The ID should not be direct
  • It will be very hard to implement — developers simply won’t do it (Please read Eric Sheridan’s comment for more info) .
  • The IDOR vulnerability is more generic, contains other use cases and covers issues like “file path access control”. The other use cases are much less common in modern apps and APIs and we don’t see them as severe as BOLA.

I love to learn, build and break things. Head of Security Research @ Traceable.ai; Security Consultant @ Tangent Logic