Skip to content
Field Detail
Platform PortSwigger Web Security Academy
Type Race Conditions / Brute Force
Difficulty Practitioner
Objective Bypass the login rate limiter using the single-packet attack, brute force carlos's password, access the admin panel, and delete carlos

Bypassing Rate Limits via Race Conditions

I logged in as wiener:peter to observe the normal authentication flow:

Screenshot

Logging out and submitting multiple wrong passwords hit the rate limiter:

Screenshot

The rate limiter counts sequential requests — but if all attempts arrive before any failure has been counted, none trigger the limit. The window between "read counter" and "write counter" is the race condition: the check runs against the current counter value for each request, but if all requests arrive simultaneously, none of them sees an incremented counter.

Confirming the bypass by sending 10 duplicates in parallel — none were blocked:

Screenshot

With the bypass confirmed, I sent the login request to Turbo Intruder and loaded the race-single-packet-attack.py template. Engine.BURP2 with concurrentConnections=1 and a gate holds all requests until openGate fires them simultaneously as a single TCP burst — neutralizing network jitter and maximizing the chance that all requests arrive before any response has been processed. wordlists.clipboard reads the password list directly from the clipboard at attack time:

def queueRequests(target, wordlists):
    engine = RequestEngine(endpoint=target.endpoint,
                           concurrentConnections=1,
                           engine=Engine.BURP2
                           )

    passwords = wordlists.clipboard

    for password in passwords:
        engine.queue(target.req, password, gate='race1')

    engine.openGate('race1')


def handleResponse(req, interesting):
    table.add(req)
Screenshot

All passwords fired in one burst. One response returned 302 Found:

Screenshot

Logging in as carlos with the discovered password and navigating to /admin:

Screenshot
Screenshot

Deleted carlos. The fix must be atomic, not sequential — a rate limiter that does "read count → check → increment" is vulnerable regardless of how fast each step runs. An atomic increment with a conditional check (Redis INCR with EXPIRE, or a database transaction with a unique constraint) closes the gap.

Lab solved

Resources