In this blog post, we explore the topic of API Security Testing and provide real-world examples, including code snippets and attack scenarios. API security is a vast topic involving many components that an organisation needs to understand before pushing an API to production. If you are a developer looking to understand API vulnerabilities in further detail, this blog post is for you.
API security testing is the process of evaluating an API to detect security vulnerabilities. This usually involves various test techniques, such as penetration testing, fuzzing for specific vulnerabilities, and code reviews. API security testing aims to understand if an API can withstand several malicious activities and function securely within its intended environment. Incorporating API security testing is highly worthwhile and can save organisations money from costly data breaches; using security testing techniques, developers can identify and fix any issues before they become exploitable by an attacker and enhance the overall security posture of the application.
Rest API security is a huge talking point in web application security, mainly because APIs have become the backbone of modern applications. Many companies now develop and deploy APIs, so their security has become more critical than ever. APIs enable communication between services and devices. However, with more connections, we often see more vulnerabilities. Implementing robust API Security Testing practices ensures your assets are safe from attackers.
This section will explore the most critical API security risks identified by OWASP API Security Top 10 and provide real-world examples to illustrate each point. If you prefer to get these details at a glance, look at our downloadable PDF.
First on the OWASP top 10 list is “Broken Object Level Authorisation” controls. APIs often expose endpoints that handle object identifiers, making them susceptible to unauthorised data access if not properly secure. This risk occurs when the server doesn’t correctly verify whether a user is authorised to access a specific object. So, very similar to web application authorisation controls, users should only be able to access resources their permission level allows.
Example of Broken Authorisation Controls:
An example would be an e-commerce API where users can view order details, such as for an endpoint like /api/orders/[orderid].
Code example for “Broken Authorisation Controls” (Node.js):
app.get('/api/orders/:orderId', (req, res) => {
const orderId = req.params.orderId;
Order.findById(orderId, (err, order) => {
if (err) return res.status(500).send(err);
if (!order) return res.status(404).send('Order not found');
res.json(order);
});
});
While this code might seem valid to the layperson, it allows any user to access any order by specifying the order without verifying that the order belongs to the user requesting it.
If the API doesn’t check whether the order ID belongs to that authenticated user, then an attacker could manipulate that order ID parameter to access someone else’s order information.
Attack scenario for “Broken Authorisation Controls”:
If an attacker discovers this vulnerability, they could browse the orders of previous users and extract sensitive information from the endpoint.
HTTP request (Example request exploiting the above code):
DELETE /api/orders/4556 HTTP/1.1
Host: vulnerableapi.com
Next on the list, we have “Broken Authentication”. As we’ve seen with web applications in the past, authentication is a vast topic and requires a blog post in itself. Authentication is a critical component of securing any digital asset; however, to discuss this briefly within the context of REST API Security, authentication flaws can often allow an attacker to assume another user’s identity, or it could allow an attacker to ultimately compromise several accounts simultaneously. Authentication flaws happen when the authentication mechanisms are particularly weak or improperly implemented.
Example of Broken Authentication:
For example, imagine an API that uses JSON Web Token’s JWT for authentication but doesn’t validate the token signature or expiration time correctly. If the API accepts tokens without verifying their cryptographic signature, an attacker could create a forged token with any user ID and gain unauthorised access to accounts. While becoming less common, this has happened on sensitive assets, such as the “NHS COVID-19 Android app“. Alternatively, suppose an API sets tokens to expire far in the future or not at all. If a token user’s token was compromised during that period, perhaps through a man-in-the-middle attack, an attacker could use that token indefinitely to access a user’s account.
Code example for “Broken Authentication” (Node.js, Express):
function authenticateToken(req, res, next) {
const token = req.headers['authorization'];
if (!token) {
return res.status(401).send('Access denied');
}
// Decode token without verifying signature
const decoded = jwt.decode(token);
req.user = decoded;
next();
}
This code decodes the JWT without verifying its signature or expiration, which could allow an attacker to force a token.
Example Attack Scenario for Broken Authentication (Lack of JWT signature verification):
Using the above code example, if an attacker wanted to gain access to another user’s account without valid credentials, they could forge a JWT token and authenticate as that user. JWT tokens are structured with three main components: a header, payload, and signature, which are separated by a “.” They are then subsequently Base64 Encoded.
Example JWT Token:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxMjM0NSIsInJvbGUiOiJ1c2VyIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Breaking down the token:
An attacker could forge a token by encoding their header and payload and omitting the signature.
Header (With ‘none’ algorithm):
{
"alg": "none",
"typ": "JWT"
}
Payload (Impersonating a user with the ID 12345):
{
"userId": "12345",
"role": "admin"
}
The attacker would then encode the JSON data using Base64 encoding:
Header: eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0
Payload: eyJ1c2VySWQiOiIxMjM0NSIsInJvbGUiOiJhZG1pbiJ9
Combining them without a signature:
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VySWQiOiIxMjM0NSIsInJvbGUiOiJhZG1pbiJ9.
The attacker could then send a request to a protected endpoint, impersonating a user:
GET /api/protected-data HTTP/1.1
Host: vulnerableapi.com
Authorisation: Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1c2VySWQiOiIxMjM0NSIsInJvbGUiOiJhZG1pbiJ9.
Another common authentication vulnerability issue is a lack of an account lockout policy. So if an API’s login endpoint doesn’t have an account lockout policy and allows unlimited login attempts, attackers could use an automated tool to perform credential stuffing attacks, trying thousands of username/password combinations until they find a match.
This vulnerability category focuses on the lack of authorisation validation controls at the object property level, which could lead to information disclosure or manipulation by unauthorised people. Broken object property-level authorisation essentially occurs when an API doesn’t enforce checks on properties within an object. This vulnerability relates to “Mass Assignment,” which was previously in the top ten list provided by OWASP. Mass Assignment can be difficult to understand, likely due to how it is titled, but it is worth looking into and understanding if you are developing an API.
While a user might be authorised to access or modify a specific object, they might not have permission to access or modify particular properties. Failing to validate user permissions at the property level can lead to access or manipulation of sensitive data.
Example of “Broken Object Property Level Authorisation”:
Imagine an application that allows users to update their profile information through an API endpoint. The user profile object includes properties such as username, email, password, and role. Regular users should be able to update their username and email but not their role.
Code example for “Broken Object Property Level Authorisation” (Node.js with Express):
app.put('/api/users/:userId', authenticateUser, (req, res) => {
const userId = req.params.userId;
const updateData = req.body;
User.findByIdAndUpdate(userId, updateData, { new: true }, (err, user) => {
if (err) {
return res.status(500).send('Server error');
}
if (!user) {
return res.status(404).send('User not found');
}
res.json(user);
});
});
This vulnerable code allows the user to update the properties without actually validating which of the properties can be updated. Therefore, a user could update their role to something such as “admin.”
Example HTTP Request for the above attack scenario:
PUT /api/users/12345 HTTP/1.1
Host: vulnerableapi.com
Authorization: Bearer ...
Content-Type: application/json
{
"username": "newUsername",
"email": "[email protected]",
"role": "admin"
}
Next, we have Unrestricted Resource Consumption, more commonly understood as a lack of API Rate Limitations. When an API accepts a substantial amount of user input without proper limitations, it risks overwhelming the back-end system, leading to excessive consumption of server memory, CPU, and storage resources. Ensuring REST API security involves checking for unrestricted resource consumption that could lead to a denial of service.
Many developers are now choosing to utilise cloud resources for file upload storage, such as AWS or Azure storage, however, it is still common enough for developers to store files locally on the web-server.
Example of “Unrestricted Resource Consumption”:
Let’s take, for example, an API that processes image uploads but doesn’t have size restrictions on the upload. If an attacker were to send thousands of extremely large images within a short time (say 30 seconds), it could consume excessive resources.
Code example for “Unrestricted Resource Consumption”:
app.post('/api/upload', (req, res) => {
const file = req.files.file;
if (!file) {
return res.status(400).send('No file uploaded');
}
res.send('File uploaded successfully');
});
Another scenario involves data entry endpoints. An attacker might generate numerous invalid or malicious entries by sending thousands of requests with dummy data. This strains server resources and can corrupt back-end data, disrupt analytics, and interfere with the application’s normal operations.
Next, we have broken function-level authorisation. This vulnerability arises when an API doesn’t correctly enforce authorisation checks on specific functions or endpoints. It can also allow attackers to access functionalities they wouldn’t normally be able to. This vulnerability is typically seen when an API has overly complex hierarchies, multiple roles, or simply oversights in implementing proper authorisation checks. Attackers have been known to exploit this by sending legitimate API calls to endpoints they’re not authorised to access.
Example of “Broken Function Level Authorisation”:
Imagine there’s an application with both regular users and administrator users. The app’s admins have special access to endpoints that allow them to perform actions such as managing user accounts, deleting content or accessing sensitive data that only the admin should be able to see. Suppose the API does not properly enforce the authorisation checks on these admin endpoints.
Example code for “Broken Function Level Authorisation” (Node.js with Express):
app.delete('/API/users/:userId', (req, res) => {
const userId = req.params.userId;
User.findByIdAndDelete(userId, (err) => {
if (err) {
return res.status(500).send('Server error');
}
res.send('User deleted');
});
});
In the code above, any authenticated user (Or even an unauthenticated user) can send a DELETE request to the API endpoint /api/users/:userId and delete any user account.
Attack scenario for “Broken Function Level Authorisation”:
When an attacker discovers that the endpoint lacks functional-level authorisation controls, they fuzz it and delete all the application’s valid users. Fuzzing the application would be straightforward using a tool such as Burp Suite Intruder; the attacker would simply increment the user identifier (In this case, sequential numbers) and proceed to delete all the users on the system.
HTTP request for the above attack scenario:
DELETE /api/users/12345 HTTP/1.1
Host: vulnerableapi.com
Authorisation: Bearer attackerToken
Now, unrestricted access to sensitive business flows is a bit vaguely titled. Still, it intends to encompass vulnerabilities that arise when an API exposes some business function without appropriate restrictions. This category of vulnerability can allow an attacker to exploit a function in a way that could harm the business. What kind of business flows is this referring to? It could be accessing and purchasing products, making reservations, or posting comments.
Example of “Unrestricted Access to Sensitive Business Flows”:
Consider an online ticketing platform that sells concert tickets through it’s API. The API exposes a specific endpoint that allows the end-users to purchase tickets.
Example request for “Unrestricted Access to Sensitive Business Flows”:
POST /api/tickets/purchase HTTP/1.1
Host: vulnerableapi.com
Content-Type: application/json
{
"eventId": "concert123",
"quantity": 2,
"paymentDetails": {
"cardNumber": "4111111111111111",
"expiryDate": "12/25",
"cvv": "123"
}
}
Attack scenario for “Unrestricted Access to Sensitive Business Flows”:
An attacker wants to purchase all the available tickets for a high-demand concert (Take the recent Oasis tour example) and resell them at a higher price. As the API doesn’t have appropriate protections to stop this exploit, the attackers automate the requests and purchase a good portion of the available tickets.
Server-side Request Forgery is appropriately titled. It describes what it is: the server is manipulated into sending a request to an unintended destination (or a destination the attacker is trying to reach). So, instead of coming from the client side, the request is coming from the server side. Exploiting SSRF could have significant consequences. It could allow an attacker to bypass firewall rules and access internal resources that wouldn’t be accessible from the external perspective.
Example of “Server-Side Request Forgery”
Let’s explore a real-world example using the EC2 metadata endpoint and discuss possible attack scenarios. First, a little background on the EC2 metadata endpoint. In AWS, EC2 instances have access to an endpoint that provides metadata about the instance, including sensitive information such as IAM roles (http://169.254.169.254/latest/meta-data/iam/security-credentials/). This endpoint is accessible only from within the instance and should never be exposed externally. Surprisingly, this EC2 data retrieval example is a common enough example to mention. If you are interested in diving deeper into SSRF issues and looking through some common examples, HackTricks SSRF is a great resource.
Looking at an example attack scenario, imagine an API endpoint that fetches the contents of a specific URL provided by the user and then returns the response.
Example code for “Server Side Request Forgery” (Node.js with Express):
app.get('/api/fetch', (req, res) => {
const url = req.query.url;
// No validation on the URL
fetch(url)
.then(response => response.text())
.then(data => res.send(data))
.catch(error => res.status(500).send('Error fetching URL'));
});
If we look at the code, the API takes the URL parameter from the query string, uses the fetch function to retrieve the content at that URL, and then returns it to the client without any validation. An attacker could exploit this vulnerability to access an EC2 metadata endpoint and get the server to retrieve that information.
Example attack scenario for “Server Side Request Forgery”
The attacker sends a request to the vulnerable endpoint, and the endpoint retrieves the information from the meta-data endpoint and supplies this to the attacker. The attacker gains access to the IAM credentials, which they can then use to access AWS services with whatever permissions are applied to the role (Using the AWS CLI).
Example HTTP request:
GET /api/fetch?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/ HTTP/1.1
Host: vulnerableapi.com
Mitigation strategies:
Now we have another title, which is also appropriately titled, security misconfiguration. Security misconfiguration is pretty broad in scope, and it occurs when specific security settings are not correctly implemented and configured across the whole API technology stack. This can lead to vulnerabilities that attackers can exploit.
We’ll discuss what security misconfiguration entails, an example. Security misconfigurations could include outdated patches for specific systems the API is utilising, unnecessary features left enabled, default credentials, or even verbose error messages from the API.
Example of a “Security Misconfiguration”:
To give an example scenario, let’s consider the code base contains a hard-coded string that configures the connection to mongodb database:
const mongoose = require('mongoose');
mongoose.connect('mongodb://admin:admin@localhost:27017/myapp', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
In this example code, we have a database connection that uses the credentials admin:admin for MongoDB, an easy combination for an attacker to guess. An attacker could gain unauthorised access to the database using these default credentials, potentially leading to accessing or modifying a whole bunch of sensitive data.
Mitigating security misconfigurations is very broad, requiring examining the whole technology stack and hardening each specific area.
OWASP also provides guidance and examples on “Improper Inventory Management” and “Unsafe Consumption of APIs,” which are worth reading; however, the topics are relatively broad. It is worth looking at the content provided by OWASP to familiarise yourself with the issues discussed. However, regular API Penetration Testing must be included to address these areas and enhance overall REST API Security.
REST API Security is a broad topic involving many technology stack components. Securing the code base is essential; however, many developers struggle to implement all the required changes to their environment before pushing them to production. While there are many steps a developer can take to mitigate vulnerabilities, the best way to identify issues is to perform thorough API Security Testing with a reputable provider. If you need API penetration testing, please get in touch with us using our contact form.
The post API Security Testing: Examples, Vulnerabilities, Mitigation appeared first on Sencode.
*** This is a Security Bloggers Network syndicated blog from Blog - Sencode authored by SencodeTeam. Read the original post at: https://sencode.co.uk/api-security-testing-examples-vulnerabilities-mitigation/