Application programming interface (API) usage has exploded in recent years. An increased desire for connectivity between applications, the greater need for data, and the rise of the internet of things have all combined to create a massive growth in API traffic. According to Cloudflare, APIs accounted for more than 50% of the traffic generated by end users and connected devices in 2021. Despite their increasing popularity, APIs are particularly vulnerable if they are not properly implemented or secured. International research and consulting firm Gartner predicted that, by 2022, API abuses would become the most frequent attack vector leveraged by threat actors, and a recent joint study by Marsh McLennan and Imperva concluded that API insecurity is the cause of anywhere from $41 billion to $75 billion of losses annually.
Group-IB’s Audit and Consulting department has extensive experience in assessing where API risks lie in a company’s digital infrastructure. Throughout the 2022 financial year, Group-IB researchers conducted a significant number of security assessments for companies in the financial and technology sectors. They found that the top 3 API vulnerability types within organizations in these two core industries were: security misconfiguration, excessive data exposure, and injections.
This blog provides a concise overview of API security, including key domains and nuances from the perspectives of API developers and end users. It outlines the importance of secure coding practices, authentication, authorization and other key domains, and provides recommendations for securing your environment. By following our tips, organizations can better protect their APIs against potential threats and vulnerabilities.
As modern software development increasingly relies on the use of APIs, ensuring the security of these interfaces has become an essential aspect of protecting sensitive data and systems. It’s now almost impossible to find a company without a public API. Even government agencies have begun offering APIs as part of their digital transformation initiatives for fellow ministries, companies and residents.
API usage introduces certain unique challenges that require specific security considerations, but it is important to recognize that many security guidelines are applicable to all types of technologies. Therefore, a robust security strategy should encompass a comprehensive set of security measures and best practices that are tailored to the specific needs of the organization and the APIs in use.
Top 3 types of API vulnerabilities
1. Security Misconfiguration
Security misconfiguration issues, such as those related to Cross-Origin Resource Sharing (CORS), Content Security Policy (CSP), security headers, and parameters, are among the most common API vulnerabilities, and were discovered in 47% of security assessments carried out by Group-IB specialists in the 2022 financial year. These features are easy to introduce, but they are challenging to configure correctly. As a result, they are the prevalent concern when it comes to API security.
Most of the time, these vulnerabilities go unnoticed. However, in some cases even minor errors in configuring these mechanisms can have significant and devastating consequences.
2. Excessive Data Exposure
In its categorization of API security risks, the Open Web Application Security Project (OWASP) recently merged the issue of Excessive Data Exposure with Mass Assignment under the umbrella of Broken Object Property Level Authorization.
Despite this, the issue of excessive data exposure remains a top priority due to the inherent risk of sensitive data being exposed to unauthorized parties. Group-IB specialists found excessive data exposure vulnerabilities in roughly a third of their security assessments over the past financial year. Excessive data exposure can be the result of poor configuration of an API, which then results in it returning more information than is necessary for a specific request.
3. Injections
It may come as a surprise to see injection-related vulnerabilities, such as SQL, SMTP, HTML/XSS, and host header injections, in the top 3 types of API vulnerabilities, but they continue to pose significant security risks.
These weaknesses enable attackers to manipulate the application’s behavior, execute malicious queries or commands, or insert harmful content. Over recent years, developers have made significant progress in addressing and mitigating this type of vulnerability, although it is essential that they remain vigilant and ensure they adhere to best practices.
Learn where your API vulnerabilities lie with Group-IB’s security assessment and penetration testing services
Just fill out the form, and our representative will contact you soon.
API security checklist for developers
To effectively secure their APIs, developers must assess a large number of concerns. Here’s a run down of some of the most pressing issues that may arise during the development stage:
Input validation
Validating all input data, including query parameters and JSON or XML payloads, is essential to ensure that everything is in the correct format and free of malicious code. While some developers may feel secure using API frameworks that primarily employ safe data formats like JSON, it is crucial to check where the data input ultimately lands. For instance, a valid XSS payload sent via JSON format could end up being displayed on an HTML page, resulting in an XSS vulnerability. The same applies to other vulnerabilities, such as LDAP/SQL/NoSQL injections, remote code execution, and more.
For frameworks utilizing XML as a data format, it’s vital to assess the potential for XXE vulnerabilities. Developers should also be mindful of any XML processing engines they use and whether they entail user input. In addition, developers should evaluate whether their APIs are susceptible to denial of service vulnerabilities. One way to do this is to adopt a “use case” approach.
Use case example:
What would happen if an attacker attempts to send an invalid JSON object? Could it disrupt the processing engine, causing the application to slow down significantly or even crash?
Practical example:
In order to illustrate the types of API vulnerabilities that can arise, we will highlight a number of practical case studies. In the below example, the API endpoint retrieves data from the database based on the user ID provided in the request.
@app.route('/api/user/<user_id>', methods=['GET'])
def get_user_data(user_id):
query = "SELECT * FROM users WHERE id = '{user_id}'"
cursor.execute(query)
user_data = cursor.fetchone()
return jsonify(user_data)
By tracing the flow of the user_id parameter, we can see that it originates from an unvalidated user input and is subsequently incorporated into a SQL query without proper escaping. This results in a SQL injection vulnerability.
One simple way to resolve this issue is to apply a validation check at the very beginning of the process by casting the type to integer. Additionally, we can switch to a parameterized query instead of string formatting.
@app.route('/api/user/<int:user_id>', methods=['GET'])
def get_user_data(user_id):
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_id,))
user_data = cursor.fetchone()
return jsonify(user_data)
Authentication
Authentication is a process of verifying the identity of the user or system making the API request. Authentication related vulnerabilities come in different forms. Some of them are related to applications that fail to conduct proper or sufficient checks of the authentication tokens. There are also attacks such as brute forcing, password spraying, and credential stuffing, which allow a malicious actor to access someone else’s account by exploiting weaknesses in the authentication system.
Overall, the process of establishing secure authentication on APIs differs little from any other type of infrastructure. Therefore, OWASP’s handy authentication cheat sheet is applicable for most types of authentication implementations.
Practical example:
Having addressed the input validation vulnerability, let’s now evaluate and tackle other potential vulnerabilities.
@app.route('/api/user/<int:user_id>', methods=['GET'])
def get_user_data(user_id):
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_id,))
user_data = cursor.fetchone()
return jsonify(user_data)As you may have noticed, this method lacks an authentication check, which means that anyone can invoke it, regardless of whether they have an account within the application or not. To fix this issue we can simply add an authentication validation before everything else:
@app.route('/api/user/<int:user_id>', methods=['GET'])
def get_user_data(user_id):
# Check if the user is authenticated
if current_user.is_authenticated:
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_id,))
user_data = cursor.fetchone()
return jsonify(user_data)
else:
abort(401, "Unauthorized: You need to be authenticated to access this resource.")
Authorization
Authorization means controlling access to API resources and ensuring that users or systems have the appropriate permissions to access specific resources. OWASP differentiates between object and function level authorization issues. This resource states that function level authorization entails checking the privileges of the user before giving them the opportunity to perform API operations. Object level authorization requires checking whether the logged-in user has the required privileges to perform the required action or access a specific object, even if they have the privileges to execute an API method.
Many developers rely on GUID identifiers for object level authorization, but it’s crucial to ensure that these GUIDs are inaccessible from other parts of the application, in addition to them having a suitable lifetime to prevent reuse in the event of leakage. While additional precautions can help, relying on probabilities is insufficient for authorization, which requires certainty. As a best practice, developers must implement server-side authorization checks to verify the relationship between the GUID and the user requesting access to the object.
Practical example:
If we return to our prior example, some may notice that while we addressed the authentication vulnerability, the code still lacks any authorization checks.
Let’s address this issue by adding an explicit authorization check, which can be done by comparing the requested user ID with the current user ID.
@app.route('/api/user/<int:user_id>', methods=['GET'])
def get_user_data(user_id):
# Check if the user is authenticated
if current_user.is_authenticated:
# Check if the authenticated user is the owner of the requested data
if user_id == current_user.id:
query = "SELECT * FROM users WHERE id = %s"
cursor.execute(query, (user_id,))
user_data = cursor.fetchone()
return jsonify(user_data)
else:
abort(403, "Forbidden: You don't have permission to access this resource.")
else:
abort(401, "Unauthorized: You need to be authenticated to access this resource.")
Logical vulnerabilities
Logical vulnerabilities refer to weaknesses in an application’s logic and design, as opposed to technical implementation issues. These flaws can compromise the intended behavior of the application, even when individual components function as expected.
Logical vulnerabilities can encompass a variety of forms, including a blend of technical vulnerabilities (e.g., authorization) and manipulations of legitimate functions. While some of these vulnerabilities are technical, others rest primarily in the logic of the application.
As there is no definitive guide or cheat sheet for safeguarding against logical vulnerabilities, it’s crucial to enlist the services of experienced application security specialists and conduct regular third-party security assessments to identify and mitigate these vulnerabilities. Automated solutions typically cannot detect such vulnerabilities.
For readers interested in learning more, the OWASP Testing Guide Business Logic overview is an excellent resource.
Example:
With our API method now secured, it’s reasonable to assume that we can apply the same constructs to other API methods, isn’t it? Let’s find out!
Suppose we need to create two additional API methods: one for a user to modify their own data, and another for an admin to alter user data. By employing the same constructs, we aim to ensure that the vulnerabilities related to authentication, authorization, and input validation are effectively addressed.
modify_user_data
@app.route('/api/user/<int:user_id>', methods=['POST'])
def modify_user_data(user_id):
if current_user.is_authenticated:
if user_id == current_user.id:
data = request.get_json()
hashed_password = bcrypt.generate_password_hash(data['password'])
query = "UPDATE users SET name = %s, surname = %s, password=%s WHERE id = %s"
cursor.execute(query, (data['name'], data['surname'], hashed_password, user_id))
return jsonify({"message": "User data updated successfully."})
else:
abort(403, "Forbidden: You don't have permission to modify this user's data.")
else:
abort(401, "Unauthorized: You need to be authenticated to access this resource.")
admin_modify_user_data
@app.route('/api/admin/user/<int:user_id>', methods=['POST'])
def admin_modify_user_data(user_id):
if current_user.is_authenticated:
if user_id == current_user.id or current_user.admin:
data = request.get_json()
hashed_password = bcrypt.generate_password_hash(data['password'])
query = "UPDATE users SET username = %s, password = %s, role = %b WHERE id = %s"
cursor.execute(query, (data['username'], hashed_password, data['role'], user_id))
return jsonify({"message": "User data updated successfully."})
else:
abort(403, "Forbidden: You don't have permission to modify this user's data.")
else:
abort(401, "Unauthorized: You need to be authenticated to access this resource.")
Take a moment to analyze the code and try to uncover the issues with it. After examining the code, let’s identify the purpose of each track.
- modify_user_data is designed for a user to update their own data.
- admin_modify_user_data is intended for an admin to modify other users’ data.
We can assume that the user object in these two scenarios will likely differ, as evident in the provided code examples. While modify_user_data works with parameters like name, surname, and password, admin_modify_user_data utilizes parameters such as username, password, and role.
The vulnerability arises when an authenticated user is allowed to execute the admin_modify_user_data method, potentially changing their role and escalating their privileges. This serves as an example of a logical vulnerability, which often results from the replication of patterns in programming. The developer may have assumed that these checks would suffice for both authentication and authorization, but they failed to account for the differences between user object models.
Now, let’s attempt to address these issues. There are several approaches we can take. One option is to simply verify if the current user is an administrator. Alternatively, we can adopt a more universal approach by compiling a list of privileges and checking if the required privilege is present. Each approach has its own implications and limitations. It’s important to regularly review the list of privileges or permissions to ensure security remains up to date.
Below is one example of fixing this issue
admin_modify_user_data
@app.route('/api/admin/user/<int:user_id>', methods=['POST'])
def admin_modify_user_data(user_id):
if current_user.is_authenticated and current_user.has_permission('admin_modify_user_data'):
data = request.get_json()
hashed_password = bcrypt.generate_password_hash(data['password'])
query = "UPDATE users SET username = %s, password = %s, role = %b WHERE id = %s"
cursor.execute(query, (data['username'], hashed_password, data['role'], user_id))
return jsonify({"message": "User data updated successfully by admin."})
else:
abort(403, "Forbidden: You don't have permission to modify this user's data as an admin.")
Knowledge of the API
It is essential that developers know and understand the API framework they are required to use. Some API types have built-in functionalities that can be manipulated by the attackers. For example, GraphQL has introspection queries and some REST API variants have a “swagger” file listing all methods and their usage.
Exploring the API is a first step in the vulnerability discovery process. Therefore, it is crucial to understand the functionality of a specific type of API and secure it accordingly.
What third-party API users can do to protect themselves
Earlier in this blog, we looked at API security from a developer’s point of view. However, there are instances when APIs are designed for use by other organizations or a company’s clients. Let’s now explore some recommendations for securely utilizing APIs in such contexts.
Securing secrets
API keys and tokens are frequently leaked due to the negligence of an organization’s technical staff. Many developers store credentials in source code or configuration files either in plaintext or using weak encryption types. This often results in leaks and unauthorized usage.
While not very common, token leakage can still occur through JavaScript files, where API keys may inadvertently be exposed in client-side code. As these files are easily accessible to attackers, it’s crucial to ensure that sensitive data remains out of the reach of potential threats.
Mobile applications can also contribute to API key leaks. Developers should be mindful that attackers can decompile the source code of mobile apps in search of sensitive information.
To mitigate these risks, organizations should adhere to best practices for storing secrets and utilize secret vaults or management solutions to ensure the security of their APIs.
Multi-factor authentication (MFA)
Cybersecurity is directly correlated to risks and probabilities. That’s why defense mechanisms should have a multi-layered approach. MFA can provide an extra layer of protection that can either help prevent data from being leaked or assist in any subsequent investigation.
Companies must have a process for detecting and responding to security incidents. When it comes to API keys and the circumstances as to when and how they are leaked, a company must know where, how and by whom this token is supposed to be used. Thus, in case of abuse, the company can identify it and take action.
General recommendations to secure your environment
Adhere to the Secure Software Development Life Cycle (SSDLC)
The Software Security Development Life Cycle (SSDLC) is a secure variant of the traditional Software Development Life Cycle (SDLC) that incorporates security measures into every stage of the development process from the initial architecture design phase to the final release of the product.
One of the key components of SSDLC is the inclusion of comprehensive security testing at every stage of the development process. This includes both static and dynamic testing of every build and release, which helps to identify and remediate security vulnerabilities before they can be exploited by attackers.
Static testing involves analyzing the source code for security issues, such as coding errors or weaknesses that could be exploited by attackers. Dynamic testing, on the other hand, involves testing the software in real-world scenarios to identify potential vulnerabilities that may not have been detected during static testing.
In addition to testing, SSDLC also emphasizes the importance of incorporating security measures into every aspect of the development process. This includes incorporating secure coding practices, performing regular security audits and assessments, and establishing incident response plans in case of a security breach.
Conduct regular 3rd party assessments
Even though SSDLC provides a solid foundation for ensuring security, it may not be sufficient on its own to fully safeguard against all potential threats.
To ensure the highest level of security for the company and its key assets, organizations should regularly engage third-party security vendors to perform comprehensive security assessments. This can help identify vulnerabilities that may have been missed during the internal team’s SSDLC processes and ensure that all areas of the organization’s security posture are thoroughly evaluated.
Security vendors bring a wealth of knowledge and expertise to the table, including the latest threat intelligence and advanced techniques for identifying and exploiting vulnerabilities. They also offer an impartial and independent assessment, free from the biases and limitations that may be present within an organization’s internal team.
Conclusion
Securing API vulnerabilities should be a critical aspect of any company’s security strategy. API security requires a comprehensive approach that includes assessing key domains such as authentication, authorization, input validation, and logical vulnerabilities. In this blog, we outlined some of the best practices that both developers and third parties should implement to address some of the weaknesses that can appear in the event that APIs are misconfigured. APIs will continue to be a prime target for cybercriminals, and successful attacks can have devastating consequences, whether that be service disruption or the theft of sensitive data.
It is crucial to stress that API security is a round-the-clock process that requires constant attention and effort. Organizations should ensure that their in-house or outsourced developers are aware of the importance of API security, in addition to having all the necessary resources to create secure APIs.




