Lion Cipher Lab
Recover a secret token with a timing attack on a non-constant-time comparison — then fix it with a constant-time compare
- AppSec / DevSecOps
- Intermediate
- ~60 min
- Free · no account required
Why it matters
Almost every system that checks a secret — an API token, an HMAC signature on a webhook, a password reset token, a session id — ends up comparing two byte strings and deciding equal or not equal. The obvious way to write that, `submitted == real`, hides a genuine vulnerability: a normal comparison is optimised to stop the instant it finds the first byte that differs. So a guess that is right in the first byte but wrong in the second takes longer to reject than a guess that is wrong in the very first byte, and a guess that is right in the first three bytes takes longer still. The amount of…
What you'll learn
- explaining what a timing side-channel is: when comparing a submitted secret against the real one takes a DIFFERENT amount of time depending on how many leading bytes matched, the response time itself leaks the secret — this is CWE-208, Observable Timing Discrepancy
- seeing exactly why a naive byte-by-byte compare that RETURNS on the first mismatch leaks matched-prefix length: a nearly-right guess runs the loop further before bailing out, so it comes back measurably slower
- writing a tiny offline victim — a stdlib http.server endpoint that checks a submitted token with an insecure early-return compare — with no external packages and nothing leaving 127.0.0.1
- building a STATISTICAL attack harness that, for each byte position, tries all candidate values many times and aggregates the timings, and explaining WHY the MINIMUM (a low percentile) beats the mean for timing attacks — noise only ever adds time, so the floor is the truest signal
- recovering the secret token ONE BYTE AT A TIME from timing alone and watching the recovered string build up left to right — the BEFORE measurement
- applying the fix: swapping the insecure compare for hmac.compare_digest (constant-time by design), re-running the EXACT same attack, and watching the per-byte timing signal collapse into noise so the recovery fails
- the honest negative: over a real network, jitter usually swamps the tiny per-byte difference, which is why remote timing attacks are hard and need enormous sample counts — so the durable lesson is defense in depth, use a constant-time compare for secrets ALWAYS
- learning where these vulnerable compares hide in real code: API-token checks, HMAC/signature verification, password-digest checks, and any `==` on a secret
Steps in this lab
- First: this is your machine, and what a timing side-channel actually is
- Why an early-return compare leaks how many bytes you got right
- Set up Python and write the insecure comparison
- Build the tiny offline victim endpoint
- The attack harness — and why the MINIMUM beats the mean
- Run the attack: recover the secret one byte at a time (the BEFORE)
- The fix: hmac.compare_digest, then re-run the exact same attack (the AFTER)
- The honest negative: why remote timing attacks are hard, and where the compare hides
- The judgment: always constant-time for secrets, and how a pro thinks about it