Case Study Distributed Rate Limiting Bypass
- erik biserovv
- 4 hours ago
- 2 min read
During an authentication security assessment, we discovered that the login
endpoint's rate limiting was configured with per_edge counting on the CDN/WAF
layer. The protection only triggered when concurrent connections exceeded a
threshold (~30-50 simultaneous requests). By sending sequential requests—one
at a time, waiting for each response—an authenticated attacker could perform
unlimited login attempts without triggering any security controls.
Escalation to Credential Stuffing & Account Takeover
Although the rate limit appeared functional during burst testing, the
sequential bypass created a realistic brute-force path:
1. The attacker sends sequential requests with zero delay, one credential pair
at a time, receiving full responses before sending the next attempt.
2. Requests distributed across edge servers — Each CDN edge node maintains
separate counters; sequential requests hit different nodes, never accumulating
enough to trigger limits.
3. Unlimited credential testing enabled — Testing showed 200+ sequential
requests with 0% blocking. Extrapolated: ~1,200 attempts/hour per IP,
28,800/day, 201,600/week.
4. Scaled with minimal resources — Using 10 IPs: 2+ million attempts per week.
Leaked credential databases contain billions of pairs; this rate makes
stuffing attacks viable.
In other words, a "functional" rate limit that only detected burst attacks
became a credential stuffing and brute-force enablement risk, providing false
confidence while offering no real protection.
The Flow
Sequential Requests → Per-Edge Counter Bypass → Credential Stuffing → Account
Takeover
Why This Is Key in Modern Web Apps
- CDN/WAF rate limiting has blind spots — per_edge counting distributes
request counters across thousands of edge servers; no single server sees the
full attack volume.
- Burst detection ≠ brute-force protection — Blocking 100 concurrent requests
means nothing if attackers simply wait 100ms between attempts.
- Credential databases are massive — Billions of leaked credentials exist;
even "slow" attacks (1,200/hour) crack accounts within days.
- No per-account lockout compounds the risk — Without account-level
protections, attackers can target the same user indefinitely.
That's why Identification and Authentication Failures (OWASP A07:2021) and
Lack of Resources & Rate Limiting (OWASP API4:2023) remain critical risks.
Recommended Fix (technical detail)
Switch to centralized rate counting
- Change WAF/CDN configuration from per_edge to per_policy or centralized
counting:
// BEFORE (vulnerable)
{ "counterType": "per_edge" }
// AFTER (secure)
{ "counterType": "per_policy" }
- This aggregates requests across all edge nodes into a single counter.
Implement per-account lockout
- Track failed attempts per account, not just per IP:
def check_login(email, password):
failed_attempts = get_failed_attempts(email)
if failed_attempts >= 5:
raise AccountLocked("Try again in 15 minutes")
if not validate_credentials(email, password):
increment_failed_attempts(email)
raise InvalidCredentials()
reset_failed_attempts(email)
return create_session(email)
Add progressive challenges
- After 3 failed attempts: Require CAPTCHA
- After 5 failed attempts: Temporary lockout (15-30 minutes)
- After 10 failed attempts: Require email/SMS verification to unlock
Implement behavioral detection
- Flag sequential patterns: same IP, same timing intervals, iterating through
passwords
- Alert on credential stuffing signatures: high unique-email-to-IP ratio,
known leaked password patterns
Monitor and alert
- Real-time dashboards for authentication failure rates
- Alert when failed logins exceed baseline by 2x+
- Log all authentication attempts for forensic analysis
Our public findings - https://hackerone.com/azer_man?type=user




Comments