API Security Testing
APIs are a common target for attackers because they expose underlying business logic and data.
Discovering API Endpoints
Documentation
Some endpoints may refer to API documentation, which can reveal available endpoints and request structures.
/api
/swagger/index.html
/openapi.json
If you identify an endpoint for a resource, make sure to investigate the base path. E.g., /api/swagger/v1/users/123
/api/swagger/v1
/api/swagger
/api
Endpoint Enumeration
Even if documentation is available, manually exploring the application can uncover undocumented endpoints.
- Consider paths like
/api/user/update
and test variations such as/delete
,/add
, etc. - Use wordlists with common API naming conventions.
- Analyze JavaScript files for hidden API calls.
Testing HTTP Methods
Test all possible HTTP methods (GET
, POST
, PUT
, DELETE
, etc.) to check for unintended access control weaknesses.
Hidden Parameters
APIs often include hidden parameters that could be exploited.
- Bruteforce parameter names with wordlists.
- Use the Param Miner Burp extension to identify hidden parameters.
Manipulating Content Types
Changing the Content-Type
header can lead to:
- Unexpected errors that reveal useful debugging information.
- Bypassing security filters that only validate specific content types.
- Exploiting differences in API logic when processing different formats (e.g., JSON vs. XML).
Modify the Content-Type
header and reformat request data to test for such issues.
Mass Assignment Vulnerabilities
Many modern APIs use frameworks that allow automatic assignment of incoming request data to an object. If the application does not properly filter which fields can be updated, an attacker can send unexpected data to modify sensitive fields that they shouldn’t be able to change.
Example
Consider an API that allows users to update their profile with a PUT /users/{id}
request. A User model might look like this:
class User {
constructor(id, name, email, role, isAdmin) {
this.id = id;
this.name = name;
this.email = email;
this.role = role;
this.isAdmin = isAdmin;
}
}
If the API assigns the request body to the user object like this:
app.put('/users/:id', (req, res) => {
let user = getUserFromDatabase(req.params.id);
Object.assign(user, req.body); // 🔴 Vulnerability: blindly assigns all fields!
saveUserToDatabase(user);
res.json(user);
});
An attacker could send a request like this:
{
"name": "attacker",
"isAdmin": true
}
Testing for Mass Assignment
Send two request with:
- Valid expected parameter:
{
"name": "attacker",
"isAdmin": "foo"
}
- Invalid expected parameter:
{
"name": "attacker",
"isAdmin": true
}
If the app behaves differently, the invalid value may affect the query, while the valid one doesn’t — suggesting the user can update the parameter.
Server-Side Parameter Pollution (SSPP)
APIs that pass query parameters between internal services may be vulnerable to manipulation.
Truncating Query Strings
A browser request:
GET /userSearch?name=test&back=/home
Might result in an internal query:
GET /users/search?name=test&publicProfile=true
By injecting a URL-encoded #
, you may truncate parameters:
GET /userSearch?name=test%23foo&back=/home
Which could modify the internal query:
GET /users/search?name=test#foo&publicProfile=true
Injecting Invalid Parameters
Use a URL-encoded &
to attempt parameter injection and observe responses:
GET /userSearch?name=test%26foo=xyz&back=/home
Resulting in:
GET /users/search?name=test&foo=xyz&publicProfile=true
if the response is unchanged it may indicate that the parameter was successfully injected but ignored by the application.
Injecting Invalid Parameters
If you’ve identified a parameter, add it and see if the server processes it.
GET /userSearch?name=test%26email=foo&back=/home
Resulting in:
GET /userSearch?name=test%26email=foo&publicProfile=true
Overriding existing Parameters
The impact of this depends on how the application processes the second parameter.
GET /userSearch?name=test%26name=test2&back=/home
Resulting in:
GET /users/search?name=test&26name=test2&publicProfile=true