Web API Security Champion: Broken Object Level Authorization (OWASP TOP 10)

Explaining one of the most common web API vulnerability classes — Broken Object Level Authorization in a practical manner. Providing a case study example based on the Damn Vulnerable RESTaurant API, including methods for identifying and preventing these vulnerabilities.

Krzysztof Pranczk
ITNEXT

--

Broken Object Level Authorization

Description

Broken Object Level Authorization is an API vulnerability that allows an attacker to bypass the access control mechanism and perform actions on a chosen object without the required permissions. Objects are usually accessed by unique identifiers, such as integers or UUIDs. An attacker with access to this identifier can read, modify, or delete a given object. This vulnerability often occurs when the authorization mechanisms, which check if a user has permissions to access the object are meant to be implemented within the API endpoint. The vulnerability is commonly refered as IDOR (Insecure Direct Object Reference).

In my career, I observed a number of Broken Object Level Authorization security vulnerabilities. In most cases, this vulnerability did not affect all of the endpoints but only a small subset of API endpoints where a developer could forget about implementing checks within the endpoint’s logic. This is one of the most common vulnerabilities and is listed in the OWASP TOP 10 API Security Risks in the 1st position.

Impact

An attacker with knowledge of an object’s identifier could potentially read, update, modify, or delete the object, depending on the issue. Furthermore, if an integer-based, incremental identifier is used to access the object, an attacker could brute-force identifiers and launch a massive attack against the vulnerable API endpoint. This could have a significant security impact, depending on the business use of the API endpoint.

A few examples of the vulnerability reported via HackerOne can be found below:

Case Study — Damn Vulnerable RESTaurant API

To demonstrate the vulnerability through a real case example and code, I’ve chosen my open-source project — Damn Vulnerable RESTaurant API. The project is available on my GitHub:

Damn Vulnerable RESTaurant is an intentionally vulnerable web application, with an interactive game focused on developers where they can investigate and fix vulnerabilities directly in the code. I recommend taking a look at this project if you’re a developer, an ethical hacker, or a security engineer.

Vulnerability Description

The Broken Object Level Authorization vulnerability is present in one of the /menu API endpoints. These endpoints are responsible for reading, modifying, and deleting items available in the restaurant that customers could order. The issue specifically occurs in the DELETE /menu/{ID} API endpoint, which is intended to be used by the restaurant’s employees to remove items from the menu, for example, to delete a burrito when it’s no longer served. However, in the presented example, any logged-in user is able to delete a menu item!

The vulnerable API endpoint implementation in FastAPI is presented below:

@router.delete("/menu/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_menu_item(
item_id: int,
current_user: Annotated[User, Depends(get_current_user)],
db: Session = Depends(get_db),
):
utils.delete_menu_item(db, item_id)
return {"ok": True}

As you can see above, the item_id variable is obtained from the URL and is passed directly to the delete_menu_item function, which deletes the item. There are no additional authorization checks performed against the current_user to ensure that only an employee can delete them. It should also be noted that the item_id is an integer value.

Broken Object Level Authorization Proof of Concept

Attackers may be able to identify such vulnerabilities with relatively low effort, and it’s extremely easy to abuse, which makes this vulnerability highly severe.

For example, the following curl HTTP request can be sent to delete a menu item with the identifier set to 1:

curl 'http://localhost:8080/menu/1' \
-X 'DELETE' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0X3VzZXIiLCJleHAiOjE3MTMyOTk1NTN9.bhx6I0XYUjbovaBi1g5xzule9VPKsB429dX1abqOsvI' \
-H 'Origin: http://localhost:8080'

As a result, the menu item will be deleted!

Broken Object Level Authorization Fix

In the presented example, the vulnerability can be easily fixed locally by adding specific authorization checks. The Damn Vulnerable RESTaurant API project follows FastAPI conventions, and authorization logic can be implemented within the API endpoint through dependency injection. The project already provides a RolesBasedAuthChecker class.

Adding auth=Depends(RolesBasedAuthChecker([UserRole.EMPLOYEE, UserRole.CHEF])) to the method’s arguments will ensure that user role will be validated before executing implemented logic — deleting a menu item.

The following Python code presents the fixed implementation of the API endpoint:

@router.delete("/menu/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
def delete_menu_item(
item_id: int,
current_user: Annotated[User, Depends(get_current_user)],
db: Session = Depends(get_db),
auth=Depends(RolesBasedAuthChecker([UserRole.EMPLOYEE, UserRole.CHEF])),
):
utils.delete_menu_item(db, item_id)
return {"ok": True}

Now, only users with theEMPLOYEE and CHEF roles will be able to delete menu items. Fixing the vulnerability locally is relatively easy, right?

Broken Object Level Authorization Recommendations

There are several advisories and from my experience, I recommend the following:

  • Deny access by default — follow the least privilege principle and provide access only to users who need it from both a business and security perspective.
  • Implement a proper authorization mechanisms — especially in the context of this vulnerability, I’d recommend to implement ReBAC access model
  • For object identifiers, use unpredictable values as GUIDs (version 4 recommended) — this will prevent from brute-force attempts
  • Write unit tests covering unauthorized access — unit tests are extremely useful not only for ensuring quality and preventing regression issues but also for addressing security aspects.

Based on the presented case study, the following unit test would be recommended to ensure that a menu item can’t be deleted by an unauthorized user:

def test_delete_menu_item_by_unauthorized_user_returns_401(test_db, anon_client):
menu_item = MenuItem(
name="Item", price=10.99, category="", description="", image_base64=""
)
test_db.add(menu_item)
test_db.commit()
response = anon_client.delete(f"/menu/{menu_item.id}")
# validating response status code
assert response.status_code == 401
# validating if menu item still exists in db
assert test_db.query(MenuItem).count() == 1

Broken Object Level Authorization Automated Detection

In the above example, the vulnerability was identified manually through a code review. It could also be identified dynamically by sending HTTP requests to API endpoints using the browser’s mechanisms, including Swagger, or more advanced tools such as Burp Proxy or ZAP.

However, automated detection of such vulnerability might be a rather challenging approach and may require a customised approach dedicated to the utilised technology or code conventions in the project. From my experience, it’s possible to utilise Dynamic Application Security Testing (DAST), but this requires adjusting the scanner. To make such detection more project-specific, customisable scanners such as Nuclei would be required. Another approach that I would like to present here is utilisig Static Application Security Testing (SAST) — writing a simple Semgrep rule. Semgrep is extremely fast and can be easily placed in CI/CD pipelines to detect vulnerabilities before merging the vulnerable code or to detect vulnerabilities at scale.

Semgrep is a great solution for Static Application Security Testing tool, which I have presented in my previous articles, especially in:

Let’s focus on identifying API endpoints in Damn Vulnerable RESTaurant that use sensitive HTTP methods such as DELETE , POST and PUT, and don’t contain any authorization checks. Also, to limit false positives, let’s focus only on API endpoints that take _id integer variables as input from the user. This way, it will be possible to detect other cases of Broken Object Level Authorization with minimal engineering effort.

To make this write-up more valuable, I utilized the power of LLMs — ChatGPT in this case, to create a Semgrep rule. The code shown below presents a rule with patterns of potentially vulnerable API endpoints in this specific project (based on the technology and used code conventions):

rules:
- id: missing-auth-in-sensitive-endpoints
patterns:
- pattern-either:
- pattern: |
@router.delete($PATH, ...)
def $FUNC(...):
...
- pattern: |
@router.post($PATH, ...)
def $FUNC(...):
...
- pattern: |
@router.put($PATH, ...)
def $FUNC(...):
...
- pattern-not: |
@router.$METHOD($PATH, ...)
def $FUNC(..., auth=Depends(RolesBasedAuthChecker(...))):
...

- pattern-inside: |
def $FUNC(..., $VAR_ID: int, ...):
...
- metavariable-regex:
metavariable: $VAR_ID
regex: '.*_id$'

message: "Endpoint is missing RolesBasedAuthChecker for sensitive HTTP method."
languages: [python]
severity: ERROR

I had to do minor improvements to the ChatGPT output, but overall, I was amazed by how accurate this rule was! Especially because it took me only 10–15 minutes to create it. This example demonstrates that LLMs are highly valuable for creating such vulnerability detection rules.

Now, let’s save the above rule as a file named missing-auth-in-sensitive-endpoints.yaml and execute Semgrep with the following command in the Damn Vulnerable RESTaurant project’s directory:

# the repository shown above has to be cloned earlier
semgrep -c missing-auth-in-unauthenticated-sensitive-endpoints.yaml . --error

The output of this command is presented below:

               
┌─────────────┐
│ Scan Status │
└─────────────┘
Scanning 68 files (only git-tracked) with 1 Code rule:

CODE RULES
Scanning 28 files.

...

┌────────────────┐
│ 1 Code Finding │
└────────────────┘

app/apis/menu/service.py
missing-auth-in-sensitive-endpoints
Endpoint is missing RolesBasedAuthChecker for sensitive HTTP method.

44┆ @router.delete("/menu/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
45┆ def delete_menu_item(
46┆ item_id: int,
47┆ current_user: Annotated[User, Depends(get_current_user)],
48┆ db: Session = Depends(get_db),
49┆ ):
50┆ utils.delete_menu_item(db, item_id)
51┆ return {"ok": True}

As you can observe the developed rule identified the vulnerable endpoint which I presented! Unfortunately, at the time of writing this article, there was only one vulnerability like this, but implementing such rule in the pipeline, triggered on Pull Requests will make sure that similar vulnerability will be detected in future before materialising on a production environment.

Summary

In this article, I presented Broken Object Level Authorization vulnerability through a practical example, along with a fix and recommendations that should be valuable for developers and security engineers. I also provided ideas for detecting similar vulnerabilities at scale with Semgrep.

References

If you’re looking for more Application Security, DevSecOps and related technical articles, just let me know what type of content you’re interested in and Follow me to get notified about new articles as soon as they’re released! 🚀🚀🚀

You can also visit my LinkedIn or Twitter profiles, if you’re keen on speaking about technical stuff! 💬

btw. usually, articles shared on Medium appear firstly on my personal blog:

--

--

Writer for

Software Engineer and Security Researcher, writing about application security in SDLC and DevSecOps - https://devsec-blog.com 🔐