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 1Understand the rate limiting
ObservationThe description mentions per-IP rate limiting that blocks after a handful of failures. Any brute-force tool from a single IP gets locked out almost immediately, so the bypass has to target how the server identifies an IP.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 2Rotate X-Forwarded-For headers
ObservationThe server sits behind a proxy layer and trusts X-Forwarded-For without validating it. Supplying a unique spoofed IP on every login request resets the per-IP counter, which allows unlimited 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 every request from the same source IP, so after about 5 failures the server returns HTTP 429 and the rest of the wordlist is blocked. The lockout is per-IP rather than per-account, so the fix is a different X-Forwarded-For on every request, which Hydra cannot do without a custom module.
Tried: Send X-Forwarded-For: 127.0.0.1 as a static value on every request to appear as localhost
A fixed spoofed IP still puts every request in one rate-limit bucket, just under the fake address instead of the real one, and the counter hits the lockout threshold after the same number of failures. The rotation has to produce a unique IP per request, so the server never sees more than an attempt or two from any single 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 3Or use Burp Suite Intruder
ObservationThe curl loop has to generate a random IP in step with each password attempt. Burp Suite Intruder in Pitchfork mode is cleaner: it synchronizes two payload sets, the password list and a rotating IP list, across every 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.