- API testing is important as vulnerabilities in APIs may undermine core aspects of a website's confidentiality, integrity, and availability.
-
Find much information as possible about the API.
-
To begin, you should identify API endpoints. These are locations where an API receives requests about a specific resource on its server.
- Once you have identified the endpoints, you need to determine how to interact with them
-
For example, you should find out information about the following:
- The input data the API processes, including both compulsory and optional parameters.
- The types of requests the API accepts, including supported HTTP methods and media formats.
- Rate limits and authentication mechanisms.
-
API documentation is often publicly available, particularly if the API is intended for use by external developers. If this is the case, always start your recon by reviewing the documentation.
Even if API documentation isn't openly available, you may still be able to access it by browsing applications that use the API.
To do this, you can use Burp Scanner to crawl the API. You can also browse applications manually using Burp's browser. Look for endpoints that may refer to API documentation, for example:
/api
/swagger/index.html
/openapi.json
If you identify an endpoint for a resource, make sure to investigate the base path. For example, if you identify the resource endpoint /api/swagger/v1/users/123
, then you should investigate the following paths:
/api/swagger/v1
/api/swagger
/api
You could also use a list of common paths to directly fuzz for documentation.
You can use a range of automated tools to analyze any machine-readable API documentation that you find.
You can use Burp Scanner to crawl and audit OpenAPI documentation, or any other documentation in JSON or YAML format. You can also parse OpenAPI documentation using the OpenAPI Parser extension.
You may also be able to use a specialized tool to test the documented endpoints, such as Postman or SoapUI.
You can also gather a lot of information by browsing applications that use the API. This is often worth doing even if you have access to API documentation, as sometimes documentation may be inaccurate or out of date.
You can use Burp Scanner to crawl the application, then manually investigate interesting attack surface using Burp's browser.
While browsing the application, look for patterns that suggest API endpoints in the URL structure, such as /api/
. Also look out for JavaScript files. These can contain references to API endpoints that you haven't triggered directly via the web browser. Burp Scanner automatically extracts some endpoints during crawls, but for a more heavyweight extraction, use the JS Link Finder BApp. You can also manually review JavaScript files in Burp.
Fuzzing to find hidden endpoints
Once you have identified some initial API endpoints, you can fuzz to uncover hidden endpoints.
consider a scenario where you have identified the following API endpoint for updating user information:
PUT /api/user/update
To identify hidden endpoints, you could use Burp Intruder to fuzz for other resources with the same structure. For example, you could fuzz the /update
position of the path with a list of other common functions, such as delete
and add
.
When fuzzing, use wordlists based on common API naming conventions and industry terms. Make sure you also include terms that are relevant to the application, based on your initial recon.
Mass assignment (also known as auto-binding) can inadvertently create hidden parameters. It occurs when software frameworks automatically bind request parameters to fields on an internal object. Mass assignment may therefore result in the application supporting parameters that were never intended to be processed by the developer.
Identifying hidden parameters
Since mass assignment creates parameters from object fields, you can often identify these hidden parameters by manually examining objects returned by the API.
For example, consider a PATCH /api/users/ request
, which enables users to update their username and email, and includes the following JSON:
{ "username": "wiener", "email": "wiener@example.com", }
A concurrent GET /api/users/123
request returns the following JSON:
{ "id": 123, "name": "John Doe", "email": "john@example.com", "isAdmin": "false" }
This may indicate that the hidden id
and isAdmin
parameters are bound to the internal user object, alongside the updated username and email parameters.
To test whether you can modify the enumerated isAdmin
parameter value, add it to the PATCH
request:
{ "username": "wiener", "email": "wiener@example.com", "isAdmin": false, }
In addition, send a PATCH
request with an invalid isAdmin
parameter value:
{ "username": "wiener", "email": "wiener@example.com", "isAdmin": "foo", }
If the application behaves differently, this may suggest that the invalid value impacts the query logic, but the valid value doesn't. This may indicate that the parameter can be successfully updated by the user.
You can then send a PATCH
request with the isAdmin
parameter value set to true
, to try and exploit the vulnerability:
{ "username": "wiener", "email": "wiener@example.com", "isAdmin": true, }
If the isAdmin
value in the request is bound to the user object without adequate validation and sanitization, the user wiener may be incorrectly granted admin privileges. To determine whether this is the case, browse the application as wiener to see whether you can access admin functionality.
Some systems contain internal APIs that aren't directly accessible from the internet. Server-side parameter pollution occurs when a website embeds user input in a server-side request to an internal API without adequate encoding.
This means that an attacker may be able to manipulate or inject parameters, which may enable them to, for example:
- Override existing parameters.
- Modify the application behavior.
- Access unauthorized data.
You can test any user input for any kind of parameter pollution. For example, query parameters, form fields, headers, and URL path parameters may all be vulnerable.
To test for server-side parameter pollution in the query string, place query syntax characters like #
, &
, and =
in your input and observe how the application responds.
You can use a URL-encoded #
character to attempt to truncate the server-side request. To help you interpret the response, you could also add a string after the #
character.
For example, you could modify the query string to the following:
GET /userSearch?name=peter%23foo&back=/home
The front-end will try to access the following URL:
GET /users/search?name=peter#foo&publicProfile=true
It's essential that you URL-encode the #
character. Otherwise the front-end application will interpret it as a fragment identifier and it won't be passed to the internal API.
Review the response for clues about whether the query has been truncated. For example, if the response returns the user peter
, the server-side query may have been truncated. If an Invalid name
error message is returned, the application may have treated foo
as part of the username. This suggests that the server-side request may not have been truncated.
If you're able to truncate the server-side request, this removes the requirement for the publicProfile
field to be set to true. You may be able to exploit this to return non-public user profiles.
You can use an URL-encoded &
character to attempt to add a second parameter to the server-side request.
For example, you could modify the query string to the following:
GET /userSearch?name=peter%26foo=xyz&back=/home
This results in the following server-side request to the internal API:
GET /users/search?name=peter&foo=xyz&publicProfile=true
Review the response for clues about how the additional parameter is parsed. For example, if the response is unchanged this may indicate that the parameter was successfully injected but ignored by the application.
To build up a more complete picture, you'll need to test further.
If you're able to modify the query string, you can then attempt to add a second valid parameter to the server-side request.
For information on how to identify parameters that you can inject into the query string, see Finding hidden parameters.
For example, if you've identified the email
parameter, you could add it to the query string as follows:
GET /userSearch?name=peter%26email=foo&back=/home
This results in the following server-side request to the internal API:
GET /users/search?name=peter&email=foo&publicProfile=true
Review the response for clues about how the additional parameter is parsed.
To confirm whether the application is vulnerable to server-side parameter pollution, you could try to override the original parameter. Do this by injecting a second parameter with the same name.
For example, you could modify the query string to the following:
GET /userSearch?name=peter%26name=carlos&back=/home
This results in the following server-side request to the internal API:
GET /users/search?name=peter&name=carlos&publicProfile=true
The internal API interprets two name
parameters. The impact of this depends on how the application processes the second parameter. This varies across different web technologies