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?
Setup
Launch the challenge instance and open the login page.
You'll need a wordlist of credentials - a credential dump is provided with the challenge.
Solution
Walk me through it- Step 1Understand the lockout mechanismAfter 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-FororX-Real-IPheaders 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. - Step 2Credential stuff with automatic timeout handlingUse 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") EOFLearn 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
requestslibrary makes HTTP automation straightforward. Posting to a login form withrequests.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 arequests.Sessionobject 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.
- Step 3Read the flagThe 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
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 (
haveibeenpwnedPwned 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.