DEV Community

Vickie Li for ShiftLeft

Posted on • Originally published at blog.shiftleft.io on

API Security 101

The top ten vulnerabilities that threaten your API, how to identify them, and how to prevent them

You’ve probably heard of the OWASP top ten or the top ten vulnerabilities that threaten web applications. OWASP also periodically selects a list of top ten vulnerabilities that threaten APIs, called the OWASP API top ten.

The current API top ten are Broken Object-Level Authorization, Broken User Authentication, Excessive Data Exposure, Lack of Resources & Rate Limiting, Broken Function-Level Authorization, Mass Assignment, Security Misconfiguration, Injection, Improper Assets Management, and Insufficient Logging & Monitoring.

Today, let’s go through each of these vulnerabilities to understand how they happen, how to identify them, and how to prevent them. We’ll start with API top ten #1, Broken Object-Level Authorization.

API #1: Broken Object-Level Authorization

APIs often expose object identifiers used to access resources. And when access control is not properly implemented on these endpoints, attackers can view or operate on resources that they should not have access to. This vulnerability affects all types of API architectures, including SOAP, REST, and GraphQL.

Let’s look at an example! Say that an API allows users to retrieve details about their payment methods based on their user ID:

[https://api.example.com/v1.1/users/payment/show?user\_id=12](https://api.example.com/v1.1/users/payment/show?user_id=12)
Enter fullscreen mode Exit fullscreen mode

Here, if the application does not require additional proof of identity in the API call and simply returns the requested data to anyone, the application exposes sensitive information to attackers. Attackers can either guess, leak, or brute force a victim’s ID and steal their payment information via the API endpoint.

Often, applications allow users to access data objects based on an object ID instead of their user IDs. Example.com also has an API endpoint that allows users to retrieve the contents of their private messages. Messages are referenced using numeric IDs:

[https://api.example.com/v1.1/messages/show?id=12345](https://api.example.com/v1.1/messages/show?id=12345)
Enter fullscreen mode Exit fullscreen mode

If the server does not implement access control on this endpoint, attackers can brute force these numeric IDs and retrieve other users’ messages!

These were instances of broken object-level authorization. There is no identity check in place before users access individual data objects. The server was not verifying whether the user is legitimate. It simply returned the information, as asked.

Besides endpoints that read data objects, these issues can also affect API endpoints that update, delete or create data entries. For instance, a common issue in GraphQL implementations is that the API allows unauthorized users to edit data by switching out IDs in mutation requests.

What can go wrong

The impact of broken object-level authorization depends on the data object exposed. If a critical object like users’ PII or credentials are exposed, the bug could allow a data breach or the compromise of the application.

This vulnerability could be exploited even more aggressively: attackers could write a script to query all user IDs and scrape data automatically. If this vulnerability were to happen on an online shopping site, attackers might be able to harvest millions of bank accounts, credit card numbers, and addresses. On a banking site, it could lead to attackers leaking everyone’s credit information and tax forms!

And if a write-based broken object-level authorization happens on critical functionalities such as password reset, password change, and account recovery, attackers can often pivot these vulnerabilities to take over user or admin accounts.

How can I prevent broken object-level authorization?

The ideal way to prevent this issue is to infer the API user’s identity from an access token or another form of secret. Then, implement access control on all sensitive API endpoints that require some privilege. Also, remember that every API endpoint and request method needs to be audited and properly protected. For instance, I often see API implementations where simply changing to a different request method will bypass controls.

If that is not possible, reference data objects with random and unpredictable values instead of simple numeric IDs. However, simply using a random ID cannot be considered well-rounded protection since IDs can be leaked or stolen. Let’s say that this API endpoint is not restricted by access control:

[https://api.example.com/v1.1/messages/show?id=d0c240ea139206019f692d](https://api.example.com/v1.1/messages/show?id=d0c240ea139206019f692d)
Enter fullscreen mode Exit fullscreen mode

Even though it’s hard to guess the IDs used to reference messages, attackers might be able to steal or leak these IDs. IDs stored in URLs are also by default available to browser extensions and browsing histories.

Preventing broken object-level authorization should be your top priority as a developer. But even APIs with proper object-level authorization can be vulnerable to attacks. For instance, implementing proper authentication mechanisms for an API service could also be tricky.

API #2: Broken User Authentication

Authentication is hard for APIs. Often, it’s not feasible to prompt for user credentials or use multi-factor authentication during API calls. For that reason, authentication in API systems is often implemented using access tokens embedded into individual API calls to authenticate the user. If authentication is not implemented correctly, attackers can exploit these misconfigurations to masquerade as someone else.

API with no authentication

First of all, an API can lack authentication mechanisms altogether. Sometimes, API developers assume that API endpoints will only be accessed by authorized applications and will not be discovered by anyone else. They allow the API to be made available to anyone who knows its endpoints and query structure. In such cases, anyone is free to request data or execute actions via APIs if they can figure out its query structure.

Faulty implementation of authentication

Thankfully, APIs that lack authentication are becoming less common. Most of the time, broken user authentication is instead caused by faulty access token design or implementation.

One common mistake is not generating access tokens properly. First of all, if tokens are short, simple, or predictable, attackers might be able to brute force them. This can happen when the tokens are generated with insufficient entropy or derived from user information using weak encryption or hashing algorithms. For instance, what is wrong with the following API token?

access_token=dmlja2llbGk=
Enter fullscreen mode Exit fullscreen mode

The token is simply the base64 encoding of the user’s username, “vickieli”!

APIs that don’t use a simple access token string can be insecure too. For instance, JSON Web Tokens (JWTs) can also be improperly signed or missing signatures altogether. This issue is especially dangerous if the insecure tokens are used to authenticate admins or others with special privileges into the API.

Long-lived tokens

Even if tokens are generated properly, improper token invalidation could also cause trouble. Long-lived tokens are a huge security issue in many API implementations.

API tokens should expire periodically and after sensitive actions such as logout, password change, account recovery, and account deletion. If access tokens are not properly invalidated, attackers can maintain access to the system indefinitely after stealing a token.

Token leaks

That brings us to the issue of token leaks. Sometimes, developers transport access tokens insecurely, such as in URLs or via unencrypted traffic. If a token is transmitted via a URL, anyone with access to the URL via browser extensions or browsing histories can steal the token.

[https://api.example.com/v1.1/users/payment/show?user\_id=12&access\_token=360f91d065e56a15a0d9a0b4e170967b](https://api.example.com/v1.1/users/payment/show?user_id=12&access_token=360f91d065e56a15a0d9a0b4e170967b)
Enter fullscreen mode Exit fullscreen mode

To steal an API token transmitted via unencrypted traffic, attackers could launch a Man in the Middle (MITM) attack and intercept a victim’s traffic.

Preventing broken authentication

Broken user authentication is devastating for APIs because a single mistake can enable attackers to take over user accounts and access restricted data and functionality.

Preventing this issue requires a comprehensive approach. First, you need to ensure that you are implementing access control for all sensitive data and functionalities. After all, authentication is futile unless you use it to restrict access to the system!

Next, make sure that your API tokens are long, random, unpredictable strings. If you are using tokens derived from user information, use strong algorithms and secret keys to ensure that users cannot forge their own tokens. Finally, if your API uses signature-based authentication such as the JWT, implement a strong signature on the token and validate it properly.

Invalidate tokens periodically and after actions like logout, password reset, account recovery, and account deletion. Finally, treat access tokens as secrets and never transport them in a URL or unencrypted traffic.

Broken user authentication can cause serious security incidents, so as a developer, never let this happen! Up next, let’s look at the OWASP API top ten #3, excessive data exposure.

API #3: Excessive Data Exposure

What is OWASP API #3, excessive data exposure, exactly? It’s when applications reveal more information than necessary to the user via an API response.

Let’s consider a simple use case of APIs. A web application retrieves information using an API service, then uses that information to populate a web page to display to the user’s browser.

       displays data requests data
user ← — — — — — — — — application — — — — — — — — — -> API service
(browser) (API client)
Enter fullscreen mode Exit fullscreen mode

For many API services, the API client applications do not have the ability to pick and choose which data fields are returned in an API call. Let’s say that an application retrieves user information from the API to populate user profiles. The API call to retrieve user information looks like this:

[https://api.example.com/v1.1/users/show?user\_id=12](https://api.example.com/v1.1/users/show?user_id=12)
Enter fullscreen mode Exit fullscreen mode

The API server will respond with the entire corresponding user object:

{
  "id": 6253282, 
  "username": "vickieli7", 
  "screen_name": "Vickie", 
  "location": "San Francisco, CA", 
  "bio": "Infosec nerd. Hacks and secures. Creates god awful infographics.", 
  "api_token": "8a48c14b04d94d81ca484e8f32daf6dc", 
  "phone_number": "123-456-7890", 
  "address": "1 Main St, San Francisco, CA, USA"
}
Enter fullscreen mode Exit fullscreen mode

You notice that besides basic information about the user, this API call also returns the API token, phone number, and address of that user. Since this call is used to retrieve data to populate the user’s profile page, the application only needs to send the username, screen name, location, and bio to the browser.

Some application developers assume that if they do not display the sensitive information on the webpage, users cannot see it. Because of that, they send the entire API response to the user’s browser without filtering out the sensitive info first and rely on client-side code to filter out the private information. When this happens, anyone who visits a profile page will be able to intercept this API response and read sensitive info about that user!

Attackers might also be able to read sensitive data by visiting certain endpoints that leak information or perform a MITM attack to steal API responses sent to the victim.

Preventing excessive data exposure

Excessive data exposure happens when the API client application does not filter the results it gets before returning the data to the user of the application.

When APIs send data that is sensitive, the client application should filter the data before forwarding it to the user. Carefully determine what the application’s user should know and make sure to filter out anything the user should not be allowed to access. Ideally, return the minimum amount of data needed to render the webpage.

If the API allows it, you could also request the minimum amount of data needed from the API server. For instance, GraphQL APIs allow you to specify the exact object fields you need in an API request. Finally, avoid transporting sensitive information with unencrypted traffic.

Hunting for excessive data exposure

I’ve always looked out for excessive data exposure when I hunt for bugs. As a bug hunter and penetration tester, I got into the habit of grepping every server response for keywords like “key”, “token”, and “secret”. More often than not, I’d find sensitive info leaks this way.

These sensitive info leaks are often made possible by precisely the problem I described here: the server is too permissive and returns the entire API response from the API server instead of filtering it before forwarding it to the user.

Excessive data exposure is, unfortunately, extremely common. And when combined with OWASP API #4, Lack of Resources and Rate Limiting, they could become an even bigger issue.

API #4: Lack of Resources & Rate Limiting

Lack of resources and rate limiting occurs when the API does not restrict the number or frequency of requests from a particular API client. This allows an API client to make thousands or even more API calls per second, or request hundreds or thousands of data records at once; the server will still try to fulfill these requests.

This sounds okay, right? In many cases, the lack of resources and rate-limiting is not an issue. But sometimes, they could allow attackers to do something more.

When is this an issue?

First of all, a lack of rate-limiting can impact the performance of the API servers and allow attackers to launch DoS attacks. When too many requests by a single client or multiple clients occur, they can overwhelm the server’s ability to process requests, and in turn, make the service slow or unavailable for other users.

Another issue is that a lack of rate-limiting can lead to brute-forcing attacks on authentication endpoints and on endpoints with broken object-level authorization. For instance, if there is no limit on how many times a user can submit login requests, malicious attackers can brute-force user passwords by trying to log in with different passwords until they succeed. On the other hand, if the application suffers from broken object-level authorization, attackers can use a non-rate limiting endpoint to brute-force the IDs that point to sensitive data.

Finally, the lack of rate limiting can help attackers exfiltrate sensitive data faster if an API endpoint is leaking information. For example, let’s say that this endpoint is used to retrieve a user’s email and is not restricted by any form of access control. This endpoint will return the contents of 20 emails:

[https://api.example.com/v1.1/emails/view?user\_id=123&entries=20](https://api.example.com/v1.1/emails/view?user_id=123&entries=20)
Enter fullscreen mode Exit fullscreen mode

This endpoint will return 5000, allowing attackers to read all the user’s emails via one API call:

[https://api.example.com/v1.1/emails/view?user\_id=123&entries=5000](https://api.example.com/v1.1/emails/view?user_id=123&entries=5000)
Enter fullscreen mode Exit fullscreen mode

Similarly, when this API call returns a user’s email address and is not restricted by rate limiting:

[https://api.example.com/v1.1/profile/email/view?user\_id=123](https://api.example.com/v1.1/profile/email/view?user_id=123)
Enter fullscreen mode Exit fullscreen mode

Attackers can potentially fire up large numbers of API requests to harvest user email addresses:

[https://api.example.com/v1.1/profile/email/view?user\_id=123](https://api.example.com/v1.1/profile/email/view?user_id=123)
https://api.example.com/v1.1/profile/email/view?user_id=124
https://api.example.com/v1.1/profile/email/view?user_id=125
...
...
...
https://api.example.com/v1.1/profile/email/view?user_id=2345
Enter fullscreen mode Exit fullscreen mode

Preventing resource and rate-limiting issues

So how can you prevent these issues from happening? You need to restrict users’ access to resources! But that is easier said than done.

The appropriate rate and resource limit for each functionality often needs to be different. For instance, the rate limit for authentication endpoints should be much lower to prevent brute-forcing and password guessing attacks. The first thing you can do is to determine what is “normal usage” for that particular functionality. Then, block users whose request resources at a much higher rate than usual.

Determining the risk of rate limit issues is all about where the vulnerability is located in the application’s context. Next up, let’s look at another API issue that would also mean different things in different contexts: OWASP API #5, broken function-level authorization.

API #5: Broken Function-Level Authorization

Broken function-level authorization is when applications fail to limit sensitive functions to the authorized users. Unlike broken object-level authorization, this flaw refers specifically to when unauthorized users can access sensitive or restricted functions they should not have access to.

For instance, when one user can modify another user’s account, or when a regular user can access admin functionality on a site. These issues are made possible by missing or misconfigured access controls. They can manifest themselves in many ways, so let’s look at a few examples.

Example #1: Deleting someone else’s post

Let’s say that an API allows its users to retrieve blog posts by sending a GET request an endpoint like this one:

GET /api/v1.1/user/12358/posts?id=32
Enter fullscreen mode Exit fullscreen mode

This request will cause the API to return post 32 from user 12358. Since all posts on this platform are public, any user can submit this request to access others’ posts. However, since only the users themselves should modify blog posts, only user 12358 can submit POST requests to modify or edit the post.

What if the API does not place the same restrictions on requests sent with the PUT and DELETE HTTP methods? In that case, malicious users might modify or delete other users’ posts by using a different HTTP method. This request deletes another user’s post.

DELETE /api/v1.1/user/12358/posts?id=32
Enter fullscreen mode Exit fullscreen mode

Example #2: Pretending to be admin

The site also allows admins of the platform to modify or delete anyone’s post. So these requests would all succeed if sent from an admin’s account.

DELETE /api/v1.1/user/12358/posts?id=32
POST /api/v1.1/user/12358/posts?id=32
PUT /api/v1.1/user/12358/posts?id=32
Enter fullscreen mode Exit fullscreen mode

But the site determines who is admin with a special header in requests:

Admin: 1
Enter fullscreen mode Exit fullscreen mode

In this case, any malicious user can simply add this header to their requests and gain access to this special admin functionality! Broken functional-level authorization can be caused by both missing access control and the bad implementation of access control.

Example #3: No locks on the door

Finally, the site allows admins to view the site’s statistics via a special API endpoint:

GET /api/v1.1/site/stats/hd216zla
Enter fullscreen mode Exit fullscreen mode

This admin endpoint does not implement any user-based restrictions. The site relies on the fact that the URL endpoints contain a random string at the end to prevent unauthorized users from accessing it. This practice is called “Security through Obscurity,” which means to increase security by withholding knowledge from outsiders.

But security through obscurity is not reliable as the only security mechanism. If an attacker can find out the obscure URL via an information leak, the attacker can access the sensitive functionality hidden behind the endpoint.

What can attackers do?

What can an attacker do with a broken function-level authorization vulnerability? It would depend on the functionality that the attacker can access via the bug. The attacker might be able to impersonate other users, gain access to restricted data, modify others’ accounts, or even become the admin of a site.

The key to preventing broken function-level authorization is to implement granular, strict access control based on the user’s session and ensure that this access control is implemented consistently regardless of a request’s method, header, and URL parameters.

API #6: Mass Assignment

“Mass assignment” refers to the practice of assigning values to multiple variables or object properties all at once. But how could this feature cause security vulnerabilities? Let’s explore by taking a look at an example object.

Object properties

Application objects often have many properties that describe the object. For instance, let’s say a “user” object is used to store user information in your application. It contains properties that describe the user like the user’s ID, name, location, and so on.

{
  "id": 12345,
  "name": "Vickie",
  "location": "San Francisco, CA",
  "admin": false,
  "group_membership": [121, 322, 457]
}
Enter fullscreen mode Exit fullscreen mode

In this case, users should be able to modify some of these properties stored in their objects, like the location and name properties. But other parts of this user object should be restricted from the user, such as the “admin” property, which denotes whether the user is an admin, and the “group_membership” property, which records which user groups the user is a member of.

Mass assignment vulnerabilities

Mass assignment vulnerabilities happen when the application automatically assigns user input to multiple program variables or objects. This is a feature in many application frameworks designed to simplify application development.

But this feature sometimes allows attackers to overwrite, modify, or create new program variables or object properties at will. For instance, let’s say the site allows users to change their names via a PUT request like this one. This request will update the name of the user 12345 from “Vickie” to “Vickie Li”.

PUT /api/v1.1/user/12345

{
  "name": "Vickie Li"
}
Enter fullscreen mode Exit fullscreen mode

Now, what if a malicious user submitted this request instead?

PUT /api/v1.1/user/12345

{
  "name": "Vickie Li", 
  "admin": true
}
Enter fullscreen mode Exit fullscreen mode

If the application uses mass assignment to automatically update the properties of the user object, this request would update the “admin” field of the object as well, and grant the user 12345 admin privileges. This is what a mass assignment vulnerability looks like.

Similarly, the malicious user might be able to add themselves to private user groups by assigning themselves to new groups using the endpoint.

PUT /api/v1.1/user/12345

{
  "name": "Vickie Li", 
  "admin": true, 
  "group_membership": [1, 35, 121, 322, 457]
}
Enter fullscreen mode Exit fullscreen mode

To prevent mass assignments, you can disable the mass assignment feature with the framework you are using, or use a whitelist to only allow assignment on certain properties or variables.

API #7: Security Misconfiguration

Security misconfigurations are a constant threat against both APIs and non-API applications alike. Let’s talk about a few common security misconfigurations in APIs, and how they can affect APIs.

Verbose error messages

First of all, one of the most common security misconfigurations is sending verbose error messages to the user. These verbose error messages might contain stack traces, information about the system, such as the server version or underlying database structure, and give the user insights into how the application works under the hood. In this case, a malicious user can force an error message from the application (through providing malformed or illegal inputs) to gather information about the server.

A lot of default 404 pages also contain custom signatures that allow attackers to fingerprint the technology used, an example being the Ruby on Rails framework.

Misconfigured HTTP headers

Another common configuration is misusing or missing HTTP headers. There are many HTTP security headers that help enhance the security of an application. If they are not properly configured, attackers can often find security holes that allow them to exfiltrate data, or perform common web attacks on the application’s users.

For example, the Content-Security-Policy (CSP) header controls which resource the browser is allowed to load for a page. It should be set to disallow scripts from random domains, inline scripts, and event-handling HTML attributes to prevent XSS (cross-site scripting) attacks. For details on how to configure CSP securely, read my post on the topic here: Intro to the Content Security Policy (https://blog.shiftleft.io/intro-to-the-content-security-policy-csp-c29266fa095f).

CORS (Cross-Origin resource sharing) misconfiguration is also an issue stemming from the misconfiguration of HTTP headers. Cross-origin resource sharing (CORS) is a safe way to relax the same-origin policy (SOP). It allows servers to explicitly specify the list of origins that are allowed to access its resources via the Access-Control-Allow-Origin header. Access-Control-Allow-Origin should be configured to allow cross-origin communication from trusted sites exclusively. Misconfigured CORS policy allows attackers to steal confidential data by messing with cross-origin communication. You can read more about how attackers can exploit misconfigured CORS here: Hacking the Same-Origin Policy (https://medium.com/swlh/hacking-the-same-origin-policy-f9f49ad592fc).

Unnecessary services or HTTP methods

Another common misconfiguration is failing to close down unnecessary services or HTTP methods. I mentioned the following example in the broken function-level authorization section. The API allows its users to retrieve blog posts by sending a GET request to an endpoint like this:

GET /api/v1.1/user/12358/posts?id=32
Enter fullscreen mode Exit fullscreen mode

This request will cause the API to return post 32 from user 12358. Since all posts on this platform are public, any user can submit this request to access others’ posts. However, since only the users themselves should modify blog posts, only user 12358 can submit POST requests to modify or edit the post. If the API does not place the same restrictions on requests sent with less popular HTTP methods, like PUT and DELETE, malicious users might be able to modify or delete other users’ posts by using a different HTTP method.

DELETE /api/v1.1/user/12358/posts?id=32
Enter fullscreen mode Exit fullscreen mode

We also talked about how the site allows admins to view the site’s statistics via a special API endpoint:

GET /api/v1.1/site/stats/hd216zla
Enter fullscreen mode Exit fullscreen mode

This admin endpoint does not implement any user-based restrictions. The site relies on the fact that the URL endpoints contain a random string at the end to prevent unauthorized users from accessing it. But security through obscurity is not reliable as the only security mechanism. If an attacker can find out the obscure URL via an information leak, the attacker can access the sensitive functionality hidden behind the endpoint. Leaving sensitive services like this one open to outsiders can cause malicious users to gain access to it.

Insecure default configurations

Many third-party dependencies like databases and web frameworks are insecure by default and require developers to tighten up security via custom configuration. For instance, older versions of MongoDB were accessible to the internet and required no authentication by default. This caused thousands of databases to be exposed to the public if the developer did not change default configurations.

Insecure default configurations like this example can cause many serious security issues if the developer is not aware of their consequences. As of this writing, MongoDB still does not require authentication by default. To see how you can set up authentication to your MongoDB database, read the MongoDB documentation (https://docs.mongodb.com/manual/tutorial/enable-authentication/).

Note that this list does not include all the possible security misconfigurations that can happen to an API; rather, it is an overview of the most common ones. I hope that this list will help you stamp out the most common security misconfigurations in your API systems and help you consider other potential security gaps in your API!

API #8: Injection

Injection is the underlying issue for a large number of vulnerabilities, such as SQL injection, OS command injection, and XML injection. Together, injections account for a huge percentage of vulnerabilities found in real-world applications and APIs.

How injections happen

In a single sentence, injection happens when an application cannot properly distinguish between untrusted user data and code.

Untrusted user data can be HTTP request parameters, HTTP headers, and cookies. They can also come from databases or stored files that can be modified by the user. If the application does not properly process the untrusted user data before inserting it into a command or query, the program’s interpreter will confuse the user input as a part of a command or a query. In this case, attackers can send data to an application in a way that will change the meaning of its commands.

In a SQL injection attack, for example, the attacker injects data to manipulate SQL commands. And in a command injection attack, the attacker injects data that manipulates the logic of OS system commands on the hosting server. Any program that combines user data with programming commands or code is potentially vulnerable.

Injection vulnerabilities can affect API systems as well because an API is just another way untrusted user input can enter an application. Let’s take a look at how injection vulnerabilities appear in an API.

Example #1: Retrieving blog posts

Let’s say that an API allows its users to retrieve blog posts by sending a GET request like this one:

GET /api/v1.1/posts?id=12358
Enter fullscreen mode Exit fullscreen mode

This request will cause the API to return post 12358. The server will retrieve the corresponding blog post from the database with a SQL query, where post_id refers to the id passed in by the user via the URL.

SELECT * FROM posts WHERE post_id = 12358
Enter fullscreen mode Exit fullscreen mode

Now, what if the user requests this from the API endpoint instead?

GET /api/v1.1/posts?id= **12358; DROP TABLE users**
Enter fullscreen mode Exit fullscreen mode

The SQL server would interpret the portion of the id after the semicolon as a separate SQL command. So the SQL engine will first execute this command to retrieve the blog post as usual:

SELECT * FROM posts WHERE post_id = 12358;
Enter fullscreen mode Exit fullscreen mode

Then, it will execute this command to delete the users table, causing the application to lose the data stored in that table.

DROP TABLE users
Enter fullscreen mode Exit fullscreen mode

This is called a SQL injection attack and can happen whenever user input is passed into SQL queries in an unsafe way. Note that user input in an API doesn’t just travel via URL parameters, they can also reach the application via POST requests, URL path parameters, and so on. So it’s important to secure those places too.

Example #2: Reading system files

Let’s say the site allows users to read the files that they’ve uploaded via an API endpoint:

GET /api/v1.1/files?id=1123581321
Enter fullscreen mode Exit fullscreen mode

This request will cause the server to retrieve the user’s files via a system command:

cat /var/www/html/users/tmp/1123581321
Enter fullscreen mode Exit fullscreen mode

In this case, a user could inject new commands into the OS system command by adding additional commands after a semicolon.

GET /api/v1.1/files?id=1123581321; rm -rf /var/www/html/users
Enter fullscreen mode Exit fullscreen mode

This command will force the server to remove the folder located at /var/www/html/users, which is where the application stores user information.

rm -rf /var/www/html/userswal
Enter fullscreen mode Exit fullscreen mode

Preventing injection vulnerabilities in APIs

These are simplified examples of injection vulnerabilities. In practice, it’s important to remember that injection vulnerabilities are not always this obvious. Manipulation can happen any time the injected data is being processed or used. Even if the malicious user data is not used by the application right away, the untrusted data can eventually travel somewhere in the program where it can do something bad, such as a dangerous function or an unprotected query. This is where they cause damage to the application, its data, or its users.

You can see why injection is so difficult to prevent. Untrusted data can attack any application component that it touches downstream. For every piece of untrusted data the application receives, meanwhile, it needs to detect and neutralize attacks targeting every part of the application. An application might conclude that a piece of data is safe because it does not contain any special characters used for triggering XSS — but the attacker actually intends to trigger an SQL injection instead. It’s not always straightforward to determine what data is safe and what data is not, because safe and unsafe data looks very different in different parts of the application.

Input validation

So how do you protect against these threats? The first thing you can do is to validate the untrusted data. This means that you either implement a blocklist to reject any input that contains dangerous characters that might affect application components. Or you implement an allowlist that only allows input strings with known good characters. For example, let’s say that you are implementing a sign-up functionality. Since you know that the data is going to be inserted into a SQL query, you reject any username input that is special characters in SQL, like the single quote. Or, you can implement a rule that only allows alphanumeric characters.

But sometimes blocklists are hard to do because you don’t always know which characters are going to be significant to application components down the line. If you just miss one special character, that can allow attackers to bypass protection.

And allowlists may be too restrictive and in some cases, and sometimes you might need to accept special characters like single quotes in user input fields. For example, if a user named Conan O’Brien is signing up, he should be allowed to use a single quote in his name.

Parameterization

Another possible defense against injection is parameterization. Parameterization refers to compiling the code part of a command before any user-supplied parameters are inserted.

This means that instead of concatenating user input into program commands and sending it to the server to be compiled, you define all the logic first, compile it, then insert user input into the command right before execution. After the user input is inserted into the final command, the command will not be parsed and compiled again. And anything that was not in the original statement will be treated as string data, and not executable code. So the program logic part of your command will remain intact.

This allows the database to distinguish between the code part and the data part of the command, regardless of what the user input looks like. This method is very effective in preventing some injection vulnerabilities, but cannot be used in every context in code.

Escaping

And finally, you can escape special characters instead. Escaping means that you encode special characters in user input so that they are treated as data and not as special characters. By using special markers and syntax to mark special characters in user input, escaping lets the interpreter know that the data is not intended to be executed.

But this method comes with its problems as well. For one, you must use the exact encoding syntax for every downstream parser or risk the encoded values being misinterpreted by a parser. You might also forget to escape some characters, which attackers can use to neutralize your encoding attempts. So a key to preventing injection vulnerabilities is to understand how parsers of different languages work, and which parsers run first in your processes.

API #9: Improper Assets Management

Although “improper assets management” sounds complicated, it’s essentially this: not keeping track of your API endpoints. This can either be due to incomplete API documentation, or a complete lack of API documentation. But why is the lack of API documentation an issue?

An API usually has many different versions, functionalities, endpoints, and a lot of parameters that affects the behavior of that endpoint. And if you don’t keep track on all of this functionality, you become unaware of the security vulnerabilities hiding within the unknown endpoints. You cannot secure what you don’t know about.

Besides incomplete or missing documentation, inaccurate documentation is also a security issue. Even if you have documentation detailing the API endpoints, does it tell you what each endpoint does? Are there any behaviors of the endpoint that are not documented in the docs, such as accepting alternate HTTP methods? Are there any undocumented parameters that can affect the endpoint’s functionality? Inaccurate documentation can make you think that the endpoint is secure, when it is not really behaving the way you think it is.

Some examples

For instance, let’s say you discovered a sensitive information leak on an API endpoint. But you are unaware that an older version of the API with the same information leak vulnerability is also available to the public. So you did not mitigate the vulnerability in the older version, and attackers can still exploit the vulnerability via the older API.

Or you might want to restrict the access of some sensitive endpoints to the site’s admins. But without detailed records of each endpoint and its functionalities, you have no way of deciding which endpoints should be restricted.

Sensitive data leaks, authorization issues, the OWASP API top ten, and many other vulnerabilities often manifest themselves in an API. But you have no way to test and secure the endpoints that you don’t know about.

What should I do to secure my API?

The best way to prevent the security blindspots caused by improper assets management is to have detailed documentation about your API hosts, versions, endpoints, and its parameters and expected behavior. Document consistently, and document everything! Make sure that others who work on the API has access to appropriate documentation. And as always, scan your API for common vulnerabilities, and routinely audit the security of all of your API endpoints.

Improper assets management might not seem like an issue at first. But in the long run, it is actually extremely dangerous to not have comprehensive documentation of your API.

API #10: Insufficient Logging & Monitoring

So far in our API security guide, we’ve talked about measures that you can take to prevent malicious attacks against your API. But what happens after an attacker finds a way to exploit your application or API?

After an attacker gains initial access to your system, they will start to utilize that initial foothold to explore and exploit the network. They might look for for more vulnerabilities that they can exploit on the machine, perform recon to discover other machines on the system, exploit new vulnerabilities they found on the system, establish persistent access to the system, or move across the network to steal data from other machines, and so on.

Cyber attacks do not happen within a few hours or even a few days. Attackers often need time to explore the network and construct suitable strategies to fully exploit the system and steal the data it contains. The longer an attacker is able to access the system without detection, the more likely the attacker would find a way to exploit the system, steal data, and cause extensive damage to the application and its users. That’s why it’s essential to have a logging and monitoring system capable of detecting malicious activity as soon as possible.

Many organizations only conduct infrastructure logging like logging network events and server logins, and lack API or application specific monitoring infrastructure. If this is you, this means that you do not have insight into the malicious activities going on in your network. You need an API logging system that would be responsible for monitoring for abnormal API usage. You can then log events such as input validation failures, authentication and authorization success and failures, application errors, and any other API events that deals with sensitive functionality like payment, account settings, and so on. Over time, you will be able to understand what normal usage of the API looks like, and detect suspicious activity that could be an attack.

That brings us to the second part of the equation. Once you set up logging and monitoring, make sure that you have an effective alert system in place in case of a security incident. After all, a monitoring system is only useful if its results could be delivered in time and to the right people. This way, your team can resolve the incident as soon as possible and limit the damage to your systems.

That wraps up our overview of the OWASP API top ten! Which one of the OWASP API top ten do you see the most in your work? What other security concepts do you want to learn about? I’d love to know. Feel free to connect on Twitter @vickieli7.

Want to learn more about application security? Take our free OWASP top ten courses here: https://www.shiftleft.io/learn/.


Top comments (0)