top of page

Case Study Distributed Rate Limiting Bypass

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





 
 
 

Comments


bottom of page