OWASP API security (https://www.owasp.org/index.php/OWASP_API_Security_Project) is an open source project which is aimed at preventing organizations from deploying potentially vulnerable APIs. APIs expose micro services to consumers, making it important to focus on how to make APIs safer and avoid known security pitfalls. Let’s take a look at the OWASP top ten list of API security vulnerabilities:
- Broken Object Level Authorization
- Broken authentication
- Excessive data exposure
- Lack of resources and rate limiting
- Broken Function Level Authorization
- Mass assignment
- Security Misconfiguration
- Injection
- Improper asset management
- Insufficient logging and monitoring
From Microservices Security in Action by Prabath Siriwardena and Nuwan Dias
This article explores the OWASP API top-ten list of API security vulnerabilities.
1. Broken Object Level Authorization
Broken Object Level Authorization is a vulnerability which is present when using IDs to retrieve information from APIs. Users authenticate to APIs using protocols like OAuth2.0. When retrieving data from APIs, users can use object IDs to fetch data. Let’s take a look at an example API from Facebook, where we get user details using an ID:
\> curl -i -X GET “https://graph.facebook.com/{user-id}?fields=id,name&access_token={your-user-access-token}”
This example shows an API which is used to retrieve details of a user identified by an ID. We pass the user-ID in the request as a path parameter to get details of the respective user. We also pass in the access token of the user who’s authenticated to the API in a query parameter.
Unless Facebook performs authorizations to check if the consumer of the API (the owner of the access token) has permissions to access details of the user to whom the ID belongs to, an attacker can gain access to details of any user they prefer;-for example, getting details of a user who’s not in your friends list. This authorization check needs to happen for every API request.
To reduce this type of attack, you should either avoid passing the user-ID in the request or use a random (non-guessable) ID for your objects. If your intention is to expose only the details of the user who’s authenticating to the API through the access token, you can remove the user ID from the API and use an alternative ID such as /me. For example,
\> curl -i -X GET “https://graph.facebook.com/me?fields=id,name&access_token={your-user-access-token}”
In case you can’t omit passing in the user-ID and need to allow access to details of different users, use a random non-guessable ID for your users. Assume that your user identifiers were an auto-incrementing integer in your database. In certain cases, you’ll might pass the value 5 as the user and, in another case, 976.
This provides hints to the consumers of your API that you have user IDs ranging from 5 to a 1000 in your system, and they can therefore randomly request user details. It’s best to use a non-guessable ID in your system. In case your system is already built, and you can’t change IDs, use a random identifier in your API layer and an internal mapping system to map externally exposed random strings to the internal IDs. This way, the true ID of the object (user) remains hidden from the consumers of the API.
2. Broken authentication
Broken authentication is a vulnerability that occurs when the authentication scheme of your APIs isn’t strong enough or isn’t implemented properly. OAuth2.0 is the de facto standard for securing APIs, and OAuth2.0 combined with OpenID Connect (OIDC) provides the required level of authentication and authorization for your APIs. We’ve seen situations where API keys (fixed keys) are used by applications to authenticate and authorize APIs on behalf of users. This is mainly due to choosing convenience over security and it isn’t a good practice.
OAuth2.0 works on opaque (random) access tokens or self-contained JWT-formatted tokens. When we use an opaque access token to access an API deployed on an API gateway, the gateway validates the token against the token issuer with a security token service (STS). If JWTs are used as access tokens, the gateway can validate the token by itself. In either case, gateways need to make sure the authentication of the tokens is done properly. For example, in the case of JWTs, the gateways need to validate the tokens and check if:
- The issuer of the token is trusted (signature verified).
- The audience of the token is correct.
- The token isn’t expired.
- The scopes bound to the token permits it to access the requested resource.
Failure to implement the security scheme properly can lead to APIs being left vulnerable to attacks that can exploit them.
3. Excessive data exposure
APIs should return only the data which is relevant and required for its consumers. For example, if an application (consumer) requires whether a particular user is older than the age of eighteen, instead of exposing the user’s date of birth or age, the API should only return a Boolean that indicates the user is older than eighteen. This is also true for other software systems and websites.
Software systems or websites shouldn’t expose the technology or versions of the technologies they run on. It’s common to find technologies used in websites by viewing the HTML page source.
If the website runs on a particular platform, there are many cases where the JavaScript libraries or CSS that appear in the HTML source contain the names and versions of the technology platform. This isn’t a good practice because it allows attackers to mine for vulnerabilities of the mentioned technologies and attack the system using that information.
It wasn’t long ago when an excessive data-exposure vulnerability were uncovered in a mobile application called 3fun, which is a location-based, online-dating application. Using location data that its API exposed unnecessarily, attackers could find dating preferences of the app’s users in the White House, Supreme Court, and major cities in the world. By using the birth date it exposed, attackers could pinpoint the exact users and hack into their personal photos!
4. Lack of resources and rate limiting
APIs often don’t impose limits on the number of requests they serve within a given time nor limit the amount of data returned. This can lead to attackers performing distributed denial-of-service (DDOS) attacks that make the system unavailable to legitimate users. Imagine an API like the following which allows retrieving details of users:
https://findusers.com/api/v2?limit=10000000
If the API doesn’t impose a limit on the maximum number of users that can be queried in a single API request, consumers could set a large value on the limit. This makes the system fetch details of many users, which runs the risk of consuming all resources and becoming unable to serve requests from legitimate users.
A setting for the maximum number of records to be retrieved can be implemented at the application layer or by using an API gateway.
Similarly, attackers could perform DDOS attacks by sending a large number of requests within a short time; for example, sending a million requests per second using distributed attack clients. This too makes the system unavailable for legitimate users. Preventing such attacks is typically done at the network perimeter using Web Application Firewall (WAF) solutions.
5. Broken Function Level Authorization
This vulnerability is about the lack of authorization at a fine-grained level of an API. An API can consist of multiple operations (resources); for example, a GET /users/{user-id} for retrieving user information and a DELETE /users/{user-id} for removing a particular user profile. Both operations are part of a single API called /users. Authorization for this API should be done by operation, not only at an API level.
Performing authorization at an API level results in anyone having permissions to retrieve user information (GET) and also to remove user information (DELETE), which isn’t correct. You could use OAuth2.0 scopes for this. Different scopes could be bound to the different resources, and only users with the relevant permissions should be granted the scope when requesting OAuth2.0 access tokens.
In some cases authorization is delegated to the consuming application of this API, such as with Single Page Applications (SPAs). The SPA can use OIDC to obtain the roles of the user and hide the relevant action (DELETE, for example) from the UI if the user doesn’t have the proper role. This isn’t proper design because the functionality is still exposed at the API level and remains vulnerable. Authorization should be enforced at the resource using OAuth2.0 scopes or something similar.
A few recommendations can make operations that require fine-grained authorizations non-guessable;
for example, using GET /users/{user-id}?action=delete instead of DELETE /users/{user-id}.
This isn’t good design, because it not only messes up your API design, but it doesn’t solve the problem. It’s not hard to find these links from various sources, and once they’re discovered, your APIs are vulnerable.
6. Mass assignment
Mass assignment is a vulnerability which is exposed when APIs blindly bind to JSON objects received via clients without being selective about the attributes it binds to. Let’s assume we have some JSON that represents user attributes, including roles. A possible GET /users/{user-id} operation returns the following:
{“user”: {
“id”: “18u-7uy-9j3”,
“username”: “robert”,
“fullname”: “Robert Smith”,
“roles”: [“admin”, “employee”]
}
}
As you can observe, this operation returns the details of the user, including his/her roles. Now imagine using the same JSON to create or modify a user in the system. This is typically done by a POST /users or PUT /users/{user-id} to the users API and by passing in the JSON message.
If the API assigns user roles by reading them from the JSON message q which are passed in, anyone having permissions to add or modify users can assign roles to users or even themselves. Imagine a sign-up form to a particular system being powered by such an API. This enables anyone to sign themselves up as admin users in the system. To avoid such errors, the API should be selective about what fields it picks from the input message to assign to its entities.
7. Security misconfiguration
Security misconfigurations on APIs can occur due to various reasons. These misconfigurations mainly occur due to insecure default configurations. The following lists some examples:
- Not disabling HTTP when allowing only HTTPS on your APIs
- Allowing unnecessary HTTP methods on API resources (for example, allowing POST on a resource when only a GET is required)
- Including stack traces on error messages that reveal the internals of a system
- Permissive Cross-Origin Resource Sharing (CORS) that allows access to APIs from unnecessary domains
Preventing these types of errors requires attention to both the design time of APIs and the runtime. We need to use tools that check our API specifications to make sure it adheres to API design best practices. This prevents design-time errors such as allowing unnecessary HTTP methods on APIs. Tools like the API contract security auditor provided by APISecurity.io let you check your API definitions (Open API files) for vulnerabilities and malpractices in API design.
Runtime strengthening of the system needs to happen by automated mechanisms as much as possible. For example, when deploying an API, we’d typically expose the API on HTTPS only (disabling HTTP). Instead of expecting an administrator to disable HTTP, the deployer scripts of the APIs should be automated to disable HTTP.
In addition to this, the software should always be run on servers which have been hardened for security, and where all the security patches have been applied. You should also build strong verification (tests), which verify that all necessary runtime configurations are applied. Netflix’s Security Monkey (though now in maintenance mode) is one such tool to make sure their AWS and GCP accounts always run on secure configurations.
8. Injection
Injection flaws, such as SQL injections or command injections, can occur when APIs accept data and pass it on to interpreters to execute as a part of a query or a command. For example, imagine that a search operation on the users’ API accepts a name to search and passes it to a SQL statement. The API looks like the following:
GET /search/users?name=robert
The name returned from the query is then passed onto a SQL query that looks like this:
SELECT * FROM USERS WHERE NAME = robert;
Now if the name passed in is changed from robert; DELETE FROM USERS WHERE ID = 1;, the resulting SQL statement is as follows and it removes a user from the system:
SELECT * FROM USERS WHERE NAME = robert; DELETE FROM USERS WHERE ID = 1;
To mitigate these types of attacks, user input should always be sanitized. Static code analysis tools are also capable of detecting whether input parameters have been directly used in SQL statements. WAF solutions are also capable of detecting and preventing such attacks at runtime. Programming languages too have their own mechanisms for preventing such attacks. For example, Java provides the Prepared Statement construct that can be used to execute SQL statements. It takes care of such vulnerabilities and prevents injection attacks.
9. Improper asset management
Platforms such as Kubernetes and containers have made it easy for developers to deploy APIs into various environments, but this has brought a new challenge-a lot of APIs tend to get deployed easily and forgotten over time. When APIs are forgotten and newer versions of APIs deployed, the older versions get less attention. Organizations can miss applying the security patches and other fixes on old APIs, but that may still be in operation under the radar. Unless properly documented and maintained, people forget the details of these APIs and are therefore unwilling to make changes to them.
This leads to situations where older APIs remain unpatched and vulnerable. It’s important to document and maintain these APIs using a proper API management system.
Red Hat’s scale and WSO2’s API Manager are two examples of open source API management solutions. These systems enforce best practices on APIs when deployed and also give an indication of which APIs are being used and which are old enough to retire. These systems also maintain the test scripts of the API and help you with testing the APIs when necessary.
10. Insufficient logging and monitoring
All actions performed in systems need to be logged, monitored, and analyzed for abnormalities. The lack of logs and monitoring results in not knowing what’s going on in a system. Assume a user is accessing an API by using a token from an IP address from the United Kingdom. Now, if the same token is being used by a user from the United States a few minutes later, it’s highly likely that the token was hacked by an intruder. Our authentication and authorization layers won’t detect anything wrong with these requests because they contain valid credentials to access APIs. We need other mechanisms to detect such abnormalities.
This is another instance where a proper API Management system can help. A system that analyses user behavior and processes them to find abnormalities and suspicious patterns are the only way of detecting and preventing such vulnerabilities.
That’s all for this article. If you want to learn more about the book, you can check it out on our browser-based liveBook reader here.
You can save 40% off Microservices Security in Action, as well as of all other Manning books and videos.
Just enter the code psmicro40 at checkout when you buy from manning.com.
You may also like:- Top 20 Cybersecurity Career Options
- Top 5 Tips to Prevent Online Scams
- Top 10 Platforms to Learn Cybersecurity
- Top 7 Commercial Linux Distributions
- Why Do I Need a Website?
- Reinforcement Learning in Real-world Applications: The Latest Successes and Challenges
- Various Python Libraries for developing RESTful APIs
- Top 7 NodeJS Frameworks You Need To Know
- How Buying Instagram Followers Can Help Businesses Soar
- How To Find Gaps In Your Cybersecurity And How To Address Them