This article is a part of Web API Security Champion series focused on API security vulnerabilities presented in a practical manner.
Broken Object Property Level Authorization
Description
Broken Object Property Level Authorization is an API vulnerability that allows attackers to bypass access control mechanisms and manipulate individual properties of an object without proper permissions. This vulnerability occurs when the API does not adequately enforce authorization checks at the property level, enabling unauthorized users to modify sensitive fields within an object. While similar to Broken Object Level Authorization, this vulnerability focuses on specific properties within an object rather than the entire object itself. This vulnerability is also known as a “Mass Assignment” vulnerability.
In my career, I have observed a number of Broken Object Property Level Authorization vulnerabilities. These issues often arise when the same data model is utilised for various CRUD operations without granular authorization checks implemented, assuming that property-level access do not require the same scrutiny as object-level operations. This vulnerability is is listed among the OWASP TOP 10 API Security Risks.
Impact
An attacker with knowledge of an object and its properties can potentially update, modify, or delete information within that object. Depending on the specific property and its role within the application, this could lead to data breaches, unauthorized transactions, or other significant security impacts. For example, modifying a user’s role property to escalate privileges or altering transaction amounts in a financial application may have severe consequences.
A two examples of this vulnerability reported via HackerOne are listed below:
- Disclosed private bug bounty programs of a chosen team via object’s parameter
- Unrestricted admin ads approval at Reddit
Case Study — Damn Vulnerable RESTaurant API
To illustrate the vulnerability, I will use my open-source project — Damn Vulnerable RESTaurant API. This project is available on my GitHub:
Damn Vulnerable RESTaurant is an intentionally vulnerable web application designed for developers to explore and fix security issues. It is an excellent resource for developers, ethical hackers, and security engineers.
Vulnerability Description
The Broken Object Property Level Authorization vulnerability is present in the PUT /profile
API endpoint. This endpoint handles profile update operations. User can update various fields specified by the schema. Unfortunately, the User
schema includes role
field which might be also updated by the user. As a result, a regular customer user may perform privilege escalation by updating role
field to Employee
or Chef
!
Here is the vulnerable API endpoint implementation in FastAPI:
from apis.auth.schemas import User as UserSchema
...
@router.put("/profile", response_model=UserRead, status_code=status.HTTP_200_OK)
def update_current_user_details(
user: UserSchema,
current_user: Annotated[User, Depends(get_current_user)],
db: Session = Depends(get_db),
):
db_user = update_user(db, user.username, user)
return current_user
The User class with role
field is presented below:
class User(BaseModel):
username: str
first_name: Union[str, None] = None
last_name: Union[str, None] = None
phone_number: Union[str, None] = None
role: Union[str, None] = None
Broken Object Property Level Authorization Proof of Concept
Exploiting this vulnerability is straightforward. For example, the following curl HTTP request can be sent to change the role
of the current user to perform privilege escalation to restaurant’s employee:
curl -X 'PUT' \
'http://localhost:8080/profile' \
-H 'accept: application/json' \
-H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJNaWtlIiwiZXhwIjoxNzE2NzUxNjYwfQ.8khfY1H_2VLZPNuxptkEdOstuJlpME6ZAJNGnKZKFd0' \
-H 'Content-Type: application/json' \
-d '{
"username": "Mike",
"role": "Employee"
}'
HTTP response body shown below presents a role updated to Employee succesfully:
{
"username": "Mike",
"first_name": "Mike",
"last_name": "",
"phone_number": "",
"role": "Employee"
}
As a result, Mike was able to perform vertical privilege escalation and gain access to API endpoints reserved for employees that he previously couldn’t access.
Broken Object Property Level Authorization Fix
To fix this vulnerability, it’s recommended to implement a new schema UserUpdate
which defines what fields can be updated by the user. In this way, users won’t be able to update fields that are not included in the schema. Below snippet presents the new UserUpdate
schema which doesn’t include role
field.
class UserUpdate(BaseModel):
username: str
first_name: Union[str, None] = None
last_name: Union[str, None] = None
phone_number: Union[str, None] = None
Now, UserUpdate
schema can be utilised in update_current_user_details
function as presented below:
from apis.auth.schemas import UserUpdate
...
@router.put("/profile", response_model=UserRead, status_code=status.HTTP_200_OK)
def update_current_user_details(
user: UserUpdate,
current_user: Annotated[User, Depends(get_current_user)],
db: Session = Depends(get_db),
):
db_user = update_user(db, user.username, user)
return current_user
The HTTP request described in the previous section no longer sets the user’s role to the provided value as the role is not listed in the UserUpdate
schema. After the fix, the HTTP endpoint returns the user’s role without updating it in the response body.
Broken Object Property Level Authorization Recommendations
To prevent such vulnerabilities, I recommend the following:
- Deny access by default: Follow the principle of least privilege and only grant permissions necessary for specific roles.
- Use schemas/DTOs based validation mechanism with different schemas dedicated for each CRUD operation to make sure that only specified fields can be accessed or modified.
- Implement granular authorization checks: Ensure authorization is checked at both the object and property levels.
- Keep data structures to the bare minimum, according to the business/functional requirements for the endpoint.
- Use unpredictable identifiers: Prevent brute-force attempts by using GUIDs or other non-sequential identifiers.
- Write unit tests for property-level access: Ensure unauthorized access attempts are covered by unit tests.
- Perform code reviews: Regularly review code to identify potential security issues.
A recommended unit test to ensure unauthorized users cannot modify user’s role:
def test_update_role_by_customer_ignores_role_returns_200(test_db, customer_client):
data = {
"username": "customer",
"role": "Employee",
}
response = customer_client.put("/profile", json=data)
assert response.status_code == 200
assert db_customer.role == UserRole.CUSTOMER
Broken Object Property Level Authorization Automated Detection
Detecting such vulnerability manually or through code reviews can be effective. Additionally, Static Application Security Testing and Dynamic Application Security Testing tools can help automate the detection process but it may require a custom rules/workflows adjusted to the utilised technology. At the time of writing this article, the detection of such vulnerabilities by automated tools is challenging and may not provide a satisfying results without a proper project-specific adjustments.
In the first article of this series, I presented how to develop a custom Semgrep rool to detect project-specific vulnerable patterns — Web API Security Champion II: Broken Object Level Authorization.
Summary
In this article, I presented the Broken Object Property Level Authorization vulnerability with practical example, fix, and recommendations for developers and security engineers. This insight into detecting and addressing such vulnerabilities will help you build more secure APIs.
References
- Damn Vulnerable RESTaurant API Game on GitHub
- API3:2023 Broken Object Property Level Authorization
- OWASP Authorization Cheat Sheet