Intermediate levelFree

Rate limit bypasses — IP rotation, header spoofing, casing and alternative endpoints

X-Forwarded-For, IP rotation with cloudfront IPs, path casing, encoding tricks, bucket race condition. How to find the gaps in throttling.

Gorka El BochiMay 9, 202610 min

Quick answer

Rate limiting is often applied poorly: sometimes only on one of several equivalent endpoints, sometimes based on spoofable headers, sometimes only by IP when X-Forwarded-For is accepted. Bypassing it enables OTP brute force, password guessing, mass enumeration, and it's often the missing piece to escalate low-impact findings to critical.


Bypass patterns

1. Spoofable X-Forwarded-For header

Apps behind a CDN/proxy trust X-Forwarded-For to identify the client's IP. If the rate limiter uses this header without validation:

http
POST /login HTTP/1.1
Host: target.tld
X-Forwarded-For: 1.1.1.1

Each request with a different value "looks like" a different IP:

bash
for i in $(seq 1 1000); do
  curl -H "X-Forwarded-For: $((RANDOM%255)).$((RANDOM%255)).$((RANDOM%255)).$((RANDOM%255))" \
       -d 'user=admin&pass=guess' https://target.tld/login
done

Common headers to try:

scss
X-Forwarded-For
X-Real-IP
X-Originating-IP
X-Remote-IP
X-Remote-Addr
X-Client-IP
X-Forwarded
Forwarded-For
True-Client-IP        (Cloudflare/Akamai)
CF-Connecting-IP      (Cloudflare)
Fastly-Client-IP
X-Cluster-Client-IP
Via

2. Real IP rotation (without headers)

If the rate limit is by real IP:

  • AWS Lambda from your own script: each invocation can go out from a different IP depending on the runtime. Free tier is enough.
  • Cloud functions (GCP, Vercel, Netlify) — your own function calling the target.
  • Tor circuits — a new circuit every N requests.
  • List of public proxies (legal, residential or paid datacenter).
  • Cloudfront IPs if the app is behind Cloudfront — bypass with Host: target.tld from another Cloudfront IP.

3. Path casing and encoding

Some rate limiters index by exact path:

bash
/login          (limited)
/Login          (NOT limited)
/LOGIN
/log%69n        (URL-encoded letter)
/login/
/login//
/login;a=1      (path parameters ignorados)
/login?x=1      (query string)
/login#a
/login%20       (trailing space)

If the server normalizes the path before processing but the rate limiter doesn't, bypass.

4. Equivalent endpoints

Large apps frequently have several endpoints that do the same thing:

  • /api/v1/login and /api/v2/login.
  • /login and /auth/login.
  • /api/login and /api/internal/login.
  • /login and /oauth/token (password grant).
  • Mobile API (/mobile/api/...) vs web API.
  • GraphQL endpoint mapping to the same backend.

If only /login has a rate limit but /api/v1/login or the GraphQL mutation login doesn't → bypass.

5. Race condition with rate limit buckets

Some rate limiters reset in windows: 5 attempts per 5 minutes.

  • If timestamping is by exact-hour bucket (60min), try right at the bucket change: 50 attempts at 11:59:30 + 50 at 12:00:30.
  • Some reset on a successful login → if you have a valid account, you can alternate it with guesses against the victim's to reset the counter.

6. HTTP smuggling to evade the WAF/proxy

The CDN/proxy applies the rate limit. The backend server doesn't. If there's HTTP smuggling, a request slips through to the backend without going through the proxy's rate limiter. (See the dedicated article on HTTP request smuggling.)

7. WebSocket fallback

Some endpoints have a REST version and a WebSocket version. If only REST is rate-limited, the WebSocket processes thousands of messages per connection without throttling.

8. GraphQL aliasing

A single GraphQL request can run the same mutation N times:

graphql
mutation {
  attempt1: login(user: "victim", pass: "guess1") { token }
  attempt2: login(user: "victim", pass: "guess2") { token }
  ...
  attempt100: login(user: "victim", pass: "guess100") { token }
}

If the rate limit counts HTTP requests but not operations within each request, 1 request = 100 attempts. Total bypass.

9. Bucket reset by user input

Common pattern: rate limit by (IP, username). If the attacker varies the username:

sql
victim@email.tld
Victim@email.tld         (case → mismo user en algunos backends, distinto bucket en rate limiter)
victim@Email.TLD
victim+x@email.tld

Real case: the 0-click-ato-otp-brute-force bug documented in another Academy article.

If the rate limit uses a cookie/session:

  • Clearing cookies between requests → direct bypass.
  • Creating mass anonymous accounts and rotating.

Quick detection

Burp Repeater + Intruder with 100 fast requests. Measure:

  • Does the status code change? (200 → 429 → ?).
  • After how many requests?
  • Does it reset per minute, per hour, per day?
  • By IP, by user, by session?

Once the mechanism is identified, try the corresponding bypasses.


Frequent impact cases

  • OTP brute force on password reset → mass ATO.
  • Login brute force on accounts with weak passwords.
  • Email enumeration without throttling → list of valid accounts.
  • API keys generation flood → DoS / abuse of free quota.
  • Coupon code brute → stolen discount codes.
  • Phone number enumeration via "send OTP" endpoints.

Hunting checklist

  • Does the rate limiter respond 429 after X requests? Measure it.
  • Does an arbitrary X-Forwarded-For bypass it?
  • Does path capitalization bypass it?
  • Are there equivalent endpoints without a rate limit?
  • Does GraphQL allow aliasing of the same mutation?
  • WebSocket version of the endpoint?
  • Does email casing affect the bucket but not the real auth?
  • Can cookies be rotated to reset it?
  • Does an "internal" API on another path process the same backend?

Correct mitigation

  1. Rate limit by real IP (validated by a trusted proxy, not spoofable via an arbitrary header).
  2. Rate limit by user account (not just IP) when there's an identifiable user.
  3. Shared bucket across all equivalent endpoints.
  4. Counting of operations within each request (matters especially in GraphQL).
  5. Temporary lockout after N consecutive failed attempts on the same account.

Practice IP rotation, header spoof, GraphQL aliasing and rate limit race condition: Rate Limiting labs.

Practice this in a lab

Rate Limiting

Solve

Keep learning · free account

Save your progress, unlock advanced payloads and rank your flags.

Create account

Related articles

hunters training
711

hunters training

labs from real reports
55

labs from real reports

completions
1,205

completions

in bounties practiced
$213,970

in bounties practiced

46 flags captured this week·Real reports from HackerOne · Bugcrowd · Intigriti·No commitment·Free Academy