Description
The login page now rate-limits attempts per IP address. You must brute-force the password from a provided wordlist while rotating IP headers to bypass the lockout.
Setup
Download the provided wordlist.
Launch Burp Suite or prepare a curl loop.
Solution
Want to try it yourself first?
The guided walkthrough reveals hints one step at a time.
Step 1
Understand the rate limitingObservationI noticed the challenge description explicitly mentioned per-IP rate limiting that blocks attempts after a small number of failures, which meant any brute-force tool running from a single IP would be locked out almost immediately and the bypass had to target the IP identification mechanism itself.The server blocks an IP after approximately 5 failed attempts. Standard tools like Hydra will be locked out quickly.Learn more
Rate limiting is a defense against brute-force attacks that restricts the number of requests a client can make within a time window. IP-based rate limiting is the most common form: once an IP address triggers too many failed logins, subsequent requests from that IP are rejected (with HTTP 429 Too Many Requests) or silently ignored for a period of time.
IP-based rate limiting has a fundamental weakness: it relies on the server's ability to accurately identify the client's IP address. When a server sits behind a load balancer or reverse proxy, the real client IP must be forwarded in a header. If the server trusts these forwarded IP headers from the client itself (rather than only from trusted proxies), the attacker can supply any IP they choose - effectively resetting the rate limit counter on each request.
Robust rate limiting must also consider account-level lockout (not just per-IP), CAPTCHA challenges after a threshold, and only trust forwarded IP headers from verified infrastructure. Relying solely on client-reported IP is a well-known anti-pattern in authentication systems.
Step 2
Rotate X-Forwarded-For headersObservationI noticed the server was sitting behind a proxy layer and trusting the X-Forwarded-For header without validation, which suggested that supplying a unique spoofed IP on every login request would reset the per-IP counter and allow unlimited password attempts from the wordlist.The server trusts the X-Forwarded-For header to identify client IPs without validation. By sending a different spoofed IP on each request, you bypass the per-IP limit entirely.bashfor pw in $(cat wordlist.txt); do curl -s -X POST https://<host>/login -H "X-Forwarded-For: $RANDOM.$RANDOM.0.1" -d "email=ctf-player@picoctf.org&password=$pw" | grep -i 'flag\|correct' && break; doneWhat didn't work first
Tried: Run Hydra directly against the login endpoint with no header rotation
Hydra sends all requests from the same source IP, so after roughly 5 failed attempts the server returns HTTP 429 and blocks the rest of the wordlist. The lockout is per-IP, not per-account, so the correct fix is to supply a different X-Forwarded-For value on every request - something Hydra cannot do natively without a custom module.
Tried: Send X-Forwarded-For: 127.0.0.1 as a static value on every request to appear as localhost
Using a fixed spoofed IP means all requests still share the same rate-limit bucket - just under the fake address 127.0.0.1 instead of the real one. The counter still reaches the lockout threshold after the same number of failures. The rotation must produce a unique IP per request so the server never sees more than one or two attempts from any single advertised address.
Learn more
X-Forwarded-For is an HTTP header used by proxies and load balancers to pass along the original client's IP address. Its format is a comma-separated list:
X-Forwarded-For: client, proxy1, proxy2. When a web application reads this header to determine the real client IP, it must only do so if the request arrives from a trusted intermediary - never from an arbitrary client.In this shell loop,
$RANDOMgenerates a pseudo-random integer between 0 and 32767 each time it is evaluated. Concatenating two such values produces a plausible-looking but unique IP on every iteration. The server, believing each request comes from a different IP, never accumulates enough failures from any single "client" to trigger the lockout.Related headers that servers sometimes trust without validation include
X-Real-IP,True-Client-IP,CF-Connecting-IP(from Cloudflare), andForwarded(the standardized RFC 7239 version). Any of these can be spoofed if the application does not verify they arrive from a trusted proxy. This is documented in OWASP Testing Guide as WSTG-ATHN-03.Step 3
Or use Burp Suite IntruderObservationI noticed that the curl loop approach required generating random IPs in sync with each password attempt, which suggested Burp Suite Intruder in Pitchfork mode as a cleaner alternative that natively synchronizes two payload sets (the password list and a rotating IP list) across each request.In Burp Intruder, set two payload positions: one for the password (from your wordlist) and one for the X-Forwarded-For value (a rotating list of IP addresses). This automates the IP rotation.Learn more
Burp Suite Intruder is a tool for automating customized attacks against web applications. In "Pitchfork" attack mode, it iterates multiple payload sets in parallel - one position advances per-request alongside another. This makes it ideal for synchronized brute-force attacks where two values must change together (password + spoofed IP).
Intruder payload options include: simple lists (paste from a file), numbers (auto-incrementing IPs), and custom iterators. The "Grep - Match" feature highlights responses that contain success indicators like "flag" or "Welcome," making it easy to spot the successful attempt among hundreds of failures.
For automated credential testing, Hydra and ffuf are also popular tools but lack built-in header rotation. Burp Intruder's fine-grained control over every part of the HTTP request makes it the preferred choice when custom headers or complex request structures are involved.
Interactive tools
- SQL Injection Payload GeneratorGenerate SQL injection payloads for auth bypass, UNION extraction, blind SQLi, NoSQL operator injection, and sqlmap commands. Supports MySQL, PostgreSQL, SQLite, and MSSQL.
Flag
Reveal flag
picoCTF{xff_byp4ss_brut3_...}
Per-instance flag. Prefix confirmed across 3 independent instances (hashes seen: 1c447e47, ff36dbbc, 1cc3b76e). The hash suffix varies per team/instance.