Lion Cipher Lab
Close username and account enumeration oracles
- Identity & Access Management
- Intermediate
- ~60 min
- Free · no account required
Why it matters
Before an attacker can steal an account they usually want to know it exists. ACCOUNT ENUMERATION is the leak that tells them: a login that says 'no such user' for one email and 'wrong password' for another, a password-reset form that says 'we sent a link' only for real addresses, a signup that rejects an email as 'already registered', or — subtler and often missed — a login that is measurably SLOWER for real accounts because the expensive password hash only runs when the user exists. Each of these turns a guess into a confirmed hit. With a confirmed list of real accounts an attacker stuffs…
What you'll learn
- defining an ACCOUNT (username) ENUMERATION ORACLE: any observable difference in an app's response that reveals whether a given email/username exists, BEFORE anyone has proven the password
- naming the three channels an auth endpoint leaks through: different response MESSAGES (no such user vs wrong password), different HTTP STATUS/redirects (404 vs 401), and a TIMING side channel (the slow password hash runs only when the account exists)
- building a tiny stdlib-only Python auth server (http.server, no pip installs) with /login, /signup and /reset that each leak account existence all three ways
- writing a Python attacker that probes a list of candidate usernames and MEASURES the leak: message/status classification accuracy, and a median login-time delta over many iterations
- reading the BEFORE numbers honestly: 10/10 usernames classified correctly by message/status, and a present-vs-absent timing gulf of hundreds of milliseconds because the hash only runs for real users
- fixing all three oracles: one uniform message, one uniform status code, and ALWAYS running a dummy password hash of identical cost even when the account does not exist, so present and absent requests take the same time
- fixing signup and password reset by returning an identical neutral response either way (if that account exists, we have sent an email) so neither path confirms membership
- re-running the SAME attacker and measuring the collapse: message/status now yields no signal, and the timing delta falls into the noise (single-digit ms, often negative)
- knowing the honest limits: the timing fix only holds if the dummy hash uses the SAME algorithm and cost factor; registration is the hardest path to fully close; and a patient attacker can still enumerate via rate-of-registration and other side channels, so enumeration is reduced, not abolished
Steps in this lab
- The vocabulary: an enumeration oracle, and its three channels
- Set up: confirm Python and make a folder (no installs, no internet)
- Build the VULNERABLE auth server (leaks all three ways)
- Build the attacker that MEASURES the leak
- Run the attack against the vulnerable server (measure BEFORE)
- Fix all three oracles (uniform message, uniform status, dummy hash)
- Re-run the SAME attacker against the fix (measure AFTER)
- Judgment: what this fixes, what it does NOT, and the traps
- What you did — and the reflex to keep