Fool the Lockout picoCTF 2026 Solution

Published: March 20, 2026

Description

Your friend is building a simple website with a login page. To stop brute forcing and credential stuffing, they've added an IP-based rate limit. Can you bypass the rate limit, log in, and capture the flag?

Launch the challenge instance and open the login page.

You'll need a wordlist of credentials - a credential dump is provided with the challenge.

  1. Step 1Understand the lockout mechanism
    After roughly 10 failed attempts the server returns 'Rate Limit Exceeded' for some cooldown window. Don't assume exactly 30s - sleep, then re-probe and only continue once the rate-limit text is gone. Bump to 45-60s if 30s isn't enough.
    Learn more

    Rate limiting is a defensive measure that restricts how many requests a client can make within a time window. IP-based rate limiting tracks the client's source IP address and counts attempts; once a threshold is exceeded, all further requests from that IP are rejected until the window resets. This defends against automated brute-force and credential stuffing attacks (where attackers replay username/password pairs leaked from other data breaches).

    A fixed window rate limiter resets its counter at the start of each time period (e.g., every 30 seconds). This is the weakest form: an attacker who detects the reset time can make a full burst of attempts at the start of every window. More robust approaches include sliding window limiters (which track a rolling time range) and token bucket or leaky bucket algorithms that smooth out burst traffic.

    Bypasses beyond sleeping through the window include: rotating through multiple source IP addresses (proxy pools, Tor), spoofing the X-Forwarded-For or X-Real-IP headers if the server trusts them for rate-limiting purposes, using different HTTP clients with different fingerprints, or exploiting race conditions in the counter logic. This challenge uses the simplest bypass: wait out the window.

  2. Step 2Credential stuff with automatic timeout handling
    Use split(':', 1) so passwords containing colons aren't mangled. Detect rate-limit responses, sleep, and re-probe before retrying. Match success on a case-insensitive substring against 'picoctf' or 'flag'. The correct credentials are 'emely / tyrant'.
    python
    python3 << 'EOF'
    import requests
    import time
    
    URL = "http://<HOST>:<PORT_FROM_INSTANCE>/login"
    SLEEP = 30  # bump to 45-60 if rate-limit text persists after the wait
    
    # split(":", 1) handles passwords that contain colons.
    credentials = [line.strip().split(":", 1) for line in open("creds.txt") if ":" in line]
    
    def is_rate_limited(text):
        return "rate limit exceeded" in text.lower()
    
    def is_success(text):
        body = text.lower()
        return "picoctf" in body or "flag" in body
    
    for username, password in credentials:
        while True:
            r = requests.post(URL, data={"username": username, "password": password}, timeout=10)
            if is_rate_limited(r.text):
                print(f"Rate limited - sleeping {SLEEP}s...")
                time.sleep(SLEEP)
                # Probe with a known-bad credential to confirm the window cleared
                probe = requests.post(URL, data={"username": "_", "password": "_"}, timeout=10)
                if is_rate_limited(probe.text):
                    SLEEP = min(SLEEP + 15, 90)  # back off
                    continue
                continue  # retry the same real credential
            break
    
        if is_success(r.text):
            print(f"Success! {username}:{password}")
            print(r.text)
            break
        print(f"Tried {username}:{password} - failed")
    EOF
    Learn more

    Credential stuffingdiffers from brute-force attacks: instead of guessing passwords randomly, attackers use real username/password pairs obtained from previous data breaches. Because many users reuse passwords across sites, stuffing attacks are highly effective against any service that doesn't rate-limit or require multi-factor authentication. The credential dump in this challenge simulates a real leaked database.

    The Python requests library makes HTTP automation straightforward. Posting to a login form with requests.post(url, data=dict) mimics what a browser sends as an application/x-www-form-urlencoded body. The response text can be checked for keywords like the flag, "Welcome", or "Incorrect" to determine success. Maintaining a requests.Session object preserves cookies across requests, which is important for sites that use session-based authentication.

    In real engagements, tools like Hydra, Burp Suite Intruder, and ffufhandle credential stuffing and brute-force attacks with built-in rate-limit handling, proxy support, and multi-threading. Writing a custom script (as here) helps understand exactly what is being sent and how the server responds, which is valuable when the off-the-shelf tools don't handle the server's specific response format correctly.

  3. Step 3Read the flag
    The correct credentials are emely / tyrant. Once logged in, the flag is displayed on the page.
    Learn more

    The fact that valid credentials appear in a credential dump highlights a core risk of password reuse. A user who uses the same password on a breached site and on a secure site effectively gives attackers access to both. Defences against credential stuffing at the service level include: rate limiting (as here), CAPTCHA challenges, anomaly detection on login patterns, and requiring multi-factor authentication for all accounts.

    For users, the defence is simple: use a unique, randomly generated password for every site (managed by a password manager) and enable MFA wherever possible. Even if a credential dump is leaked, unique passwords mean the attacker has access to only one service, not all of them. Services like Have I Been Pwned (haveibeenpwned.com) let users check whether their email appears in known breaches.

    See Web Challenges and Real-World Bug Patterns for adjacent auth bugs.

Flag

picoCTF{r4t3_l1m1t_byp4ss3d_...}

The rate limiter resets every 30 seconds. Detect 'Rate Limit Exceeded' and sleep 30s to let the window reset, then continue credential stuffing. Credentials: emely / tyrant.

How to prevent this

Rate limiting per IP and on a fixed window is the bare minimum, and it is easy to bypass. Layer the defenses.

  • Limit per account, not just per IP. An attacker rotates IPs cheaply; an account is a fixed identifier. After 10 failed attempts, throw a 24-hour lockout that requires email-confirmed reset.
  • Use a sliding-window or token-bucket limiter (Redis, express-rate-limit, Vercel BotID). Fixed windows let an attacker burst at every reset boundary.
  • Check incoming credentials against breach lists (haveibeenpwned Pwned Passwords API uses k-anonymity). Reject known-leaked combinations before the password ever hits your hash function. Pair with mandatory MFA on accounts that hold real value.

Want more picoCTF 2026 writeups?

Useful tools for Web Exploitation

Related reading

What to try next