warming up your workspace

How stealing one cookie bypasses multi-factor authentication

Multi-factor authentication is the single best thing most people can do for their security, and it is why phishing a password alone rarely works anymore. So attackers stopped going for the password. The dominant account-takeover technique in 2026 does not touch your password or your second factor at all. It steals the little token your browser gets after you log in, the session cookie, and replays it. From the server's point of view, the attacker is already logged in. This is called session hijacking or "pass the cookie", and infostealer malware and adversary-in-the-middle phishing kits have made it routine. The reason it works is a design detail you can reproduce in a few lines.

The one idea

MFA is a check that happens once, at the door. When you pass it, the server hands your browser a session token and, from then on, trusts that token instead of asking you to log in again on every click. That is not laziness, it is the only way the web is usable. But it means the token is your identity for the life of the session. Whoever holds it is you. If nothing ties the token to your specific device, then a copy of the token, lifted by malware, a malicious browser extension, or a proxy sitting between you and the site, is a working key that never had to pass the door at all.

Build the flaw

Here is the naive session everyone writes first: a random token, stored server-side, that maps to a user.

import secrets
sessions = {}

def login(user):                 # user already passed password + MFA here
    tok = secrets.token_hex(16)
    sessions[tok] = user
    return tok

def handle_request(tok):
    return sessions.get(tok)      # holding the token means you are that user

Now watch the theft. Alice logs in legitimately. An attacker who copies her token value, from a stolen cookie, presents the exact same string.

alice_tok = login("alice")
print("alice's request:", handle_request(alice_tok))
print("attacker replay:", handle_request(alice_tok))   # identical token, full access
alice's request: alice
attacker replay: alice

The server cannot tell them apart, because there is nothing to tell apart. The token is valid, so the request is served. MFA is never re-checked, it already happened, and the token is proof it happened. That is the whole attack.

Build the fix

The fix is to make the token mean "this user on this device" instead of just "this user". At login, you fingerprint the client, coarse network location and user-agent, and bind the token to that fingerprint. Every request re-checks it.

import hashlib, hmac

def fingerprint(ua, ip_net):
    return hashlib.sha256(f"{ua}|{ip_net}".encode()).hexdigest()[:16]

def handle_request(tok, fp):
    s = sessions.get(tok)
    if not s: return None
    if not hmac.compare_digest(s["fp"], fp):   # same token, different device
        return "REJECTED: token/device mismatch"
    return s["user"]
alice's device:  alice
attacker device: REJECTED: token/device mismatch

Three details that matter:

  • Bind to something the attacker cannot trivially copy along with the cookie. A coarse network block plus device signals is a start; the strong version is a hardware-backed key (a passkey or a token-binding secret) that never leaves the device, so a stolen cookie is useless without it.
  • Use hmac.compare_digest, not ==, for the comparison. A normal string compare returns faster on an early-mismatched fingerprint, and that timing difference can leak the value one byte at a time. Constant-time comparison closes it.
  • Binding is not enough by itself. Pair it with short token lifetimes and rotation, issue a fresh token periodically and invalidate the old one, so a stolen token expires fast and a replayed old token stands out.

Where this shows up

This is exactly the arms race behind modern login security. Adversary-in-the-middle phishing kits proxy the real login page to capture the post-MFA cookie in real time; infostealer malware scrapes cookies straight off the disk. The defenses are the ones above, industrialized: device-bound sessions, passkeys that cannot be phished because there is no shared secret to steal, continuous risk checks that notice when a session suddenly jumps continents. Every one of them is an answer to the same flaw you just built: a token that trusts possession alone.

If you want to build the auth server, the session store, and the attacks against them, then harden each one, that is the loop the cybersecurity track on IWTLP runs you through.

Sources