0x01 Fundamentals: Capturing Credentials and Session Cookies via AiTM
9. 10. 2026
9. 10. 2026
This lab investigates how an Adversary-in-the-Middle (AiTM) reverse proxy intercepts an authentication flow in real time: capturing submitted credentials and, more importantly, the session cookie that keeps a user authenticated after login. The goal was to build a working AiTM configuration against a target application (Foo Corp Mail), run a self-managed campaign, capture both the credentials and the session cookie during a live login, and then replay that session in a separate browser profile to confirm the hijack actually works end to end.
Hypervisor: VMware Workstation Pro
OS: Kali Linux (VM)
AiTM platform: Phishing Club, admin interface at https://<LAB_IP>:40795
Target application: Foo Corp Mail, reachable at https://l1-labs.phishing.club
Testing browser: Brave, two separate profiles (attacker role / victim role)
Session replay extension: Session Sushi
Local DNS: wildcard zone *.pclabs resolved via dnsmasq, set up in a previous article in this series
Topology: single VM — the testing browser and the Phishing Club server share the same host, so no routing between network segments was required
Before writing any proxy configuration, I opened the real target directly in a normal browser tab (https://l1-labs.phishing.club, no proxy involved yet) with DevTools' Network panel open and "Preserve log" enabled, then logged in with the provided test credentials to see exactly what the authentication flow looked like on the wire.
Inspecting the login request itself showed:
Request: POST /login, Content-Type: application/x-www-form-urlencoded
Response: 303 See Other, Location: /dashboard
Response header: Set-Cookie: l1_session=...; Path=/; HttpOnly; Secure; SameSite=Lax
This is exactly the information needed to write the capture rules: the path to match (^/login$), the request body format to parse (urlencoded), the field names to pull out (email, password), and the exact cookie name to capture from the response (l1_session). Everything in the proxy configuration below is a direct translation of this one request/response pair into Phishing Club's capture syntax.
I started from the lab's provided YAML skeleton and adapted it to my own domain naming. The configuration maps the target host to a proxy domain and defines two capture rules: one for the submitted login form (email, password as URL-encoded form data), and one for the l1_session cookie set in the /login response.
The Start URL was set to https://l1-labs.phishing.club/dashboard, which is the page the proxied session should land on after a successful login.
version: "0.0"
l1-labs.phishing.club:
to: pl1.pclabs
tls:
mode: self-signed
capture:
- name: credentials
engine: urlencoded
from: request_body
path: ^/login$
find: ["email", "password"]
required: true
event: submit
- name: session
engine: cookie
path: ^/login$
find: l1_session
required: true
Phishing Club distinguishes between the domain the recipient actually sees (the lure/hosting domain) and the domain that does the real reverse-proxying. I created two domain entries for this: a hosting domain (dl1.pclabs) and a proxy domain (pl1.pclabs), both self-signed and lowercase for reasons explained in the Troubleshooting section below.
In the template's Page Flow, the Landing step was pointed directly at the proxy domain, with no "Before Landing" decoy page configured — this lab's objective is purely to validate the AiTM capture mechanism, not to add a pretext page in front of it.
With a self-managed campaign created and the recipient's lure URL opened in the testing browser, I reached a convincing login page for "Foo Corp Mail" proxied live through Phishing Club.
After submitting the provided credentials, the campaign's event log confirmed two distinct "Submitted Data" events — one for the credential capture rule, one for the session capture rule.
I exported the captured l1_session cookie and imported it into Session Sushi inside a fresh private/incognito browser window, scoped to the target's real domain.
Navigating to /dashboard in that private window landed directly on the authenticated dashboard, signed in as the recipient — without ever entering a password in that browser profile.
Why the proxy needs its own TLS certificate. The proxy domain terminates TLS itself before forwarding the request to the real target, so it needs a certificate of its own — it cannot simply reuse or forward the target's certificate. Managed TLS (Let's Encrypt-style ACME issuance) requires the domain to be publicly resolvable and reachable, which a local .pclabs zone pointing at 127.0.0.1 is not. Self-signed certificates are the correct fit for an isolated lab: confirmed by testing with curl -vk, which completed a full TLS 1.3 handshake against the proxy once self-signed certificates were enabled, returning a certificate whose CN matched the proxy domain.
Why credential capture targets the request body, not the response. The login form posts email and password as application/x-www-form-urlencoded data in the POST /login request body — this is the only place those values exist in plaintext on the wire, so the urlencoded capture engine reading from: request_body is the correct mechanism, confirmed directly in the campaign's event log.
Why the session capture targets the response's Set-Cookie header. The l1_session cookie is issued by the target application in the response to a successful POST /login, not in the request — the cookie capture engine reads it from there. This is the value that actually keeps the browser authenticated, independent of the credentials that produced it; it is what Session Sushi later needs to replay the session.
access: mode: private as an anti-scanning control. Confirmed through direct curl testing: a request to the proxy's root path with no valid recipient identifier returned a generic 404 page not found from Phishing Club itself, not from the proxied target. This indicates the AiTM proxy will not reveal the phished page at all unless the request carries a valid, campaign-bound recipient token — a deliberate control to keep the live phishing page hidden from automated scanners, link-preview bots, and anyone probing the domain without a valid lure link.
Symptom: The proxy domain returned ERR_SSL_PROTOCOL_ERROR in the browser instead of a certificate warning.
Cause: The domain had Managed TLS, Self-Signed Certificates, and Custom Certificates all disabled, so the server had no TLS material to present at all on port 443.
Resolution: Enabled Self-Signed Certificates on the proxy domain. Managed TLS was left disabled, since ACME issuance cannot succeed against a locally-resolved .pclabs domain anyway.
Lesson: A protocol-level TLS error (rather than a certificate-trust warning) is a sign that no certificate is being served at all, not that an existing certificate is untrusted — check which TLS mode is actually enabled before assuming it's a trust/CA issue.
Symptom: After fixing TLS, the proxied page still failed to load correctly.
Cause: The proxy's Start URL had reverted to the target's bare root domain, without the /dashboard path the lab's instructions specified.
Resolution: Re-set the Start URL to the full path: https://l1-labs.phishing.club/dashboard.
Lesson: Re-check the Start URL after any edit made through a different UI view (YAML vs. visual editor) — it can silently reset.
Symptom: Both the browser and curl against the proxy's bare root path (/, no parameters) returned a plain-text 404 page not found coming from Phishing Club itself.
Cause: The domain's global configuration included access: mode: private, which only serves the proxied page to requests carrying a valid, campaign-bound recipient identifier (?id=...). A bare request with no identifier is intentionally rejected.
Resolution: Confirmed by testing the same path with a valid recipient id from the current campaign, which successfully returned the proxied login page.
Lesson: A 404 directly from the AiTM platform (not the target application) during manual testing often means the access-control layer is rejecting the request shape, not that the proxy mapping is broken — test with a valid campaign link before debugging the proxy configuration itself.
Symptom: The same proxy domain returned 200 OK with a real curl request using uppercase characters in the Host header, but a plain 404 from the browser and from curl using lowercase.
Cause: The domain had been created in the admin UI with mixed-case characters (PL1.pclabs), and Phishing Club appears to match the configured domain against the incoming Host header as an exact, case-sensitive string. Browsers always send the Host header in lowercase, so a mixed-case domain name can never match in real browser traffic.
Resolution: Renamed the domain (and the corresponding YAML to: value and template references) to fully lowercase: pl1.pclabs.
Lesson: Always create AiTM domains in lowercase from the start — this is easy to overlook because admin UIs and YAML editors don't enforce or normalize case, but real HTTP traffic will never match a mixed-case configuration.
Credentials and session cookies are captured from different parts of the HTTP exchange. Credentials live in the request body of the login POST; the session cookie lives in the response headers after a successful login. A capture configuration needs both directions covered.
A session cookie is a complete authentication bypass on its own. Once l1_session was imported into a clean browser profile, no password was needed at all — the cookie alone was sufficient to reach the authenticated dashboard.
Domain names in AiTM tooling should always be lowercase. Browsers never send a Host header in anything but lowercase, so any case mismatch in the proxy configuration silently breaks matching.
A "page not found" from the AiTM platform can be a feature, not a bug. Before assuming a broken proxy mapping, it's worth checking whether an access-control setting (like private mode) is intentionally hiding the page from non-campaign traffic.
curl --resolve is a fast way to isolate DNS, TLS, and routing issues from browser-specific behavior — it let me confirm TLS handshake success, response codes, and exact response bodies independently of browser caching or extension interference.
Session cookie attributes alone do not stop AiTM. HttpOnly, Secure, and SameSite=Lax prevent JavaScript-based theft and most cross-site attacks, but they do nothing against a reverse-proxy AiTM attack, because the victim is talking directly to the attacker's server, which legitimately receives the Set-Cookie response and can simply read it.
Phishing-resistant MFA is the actual mitigation. FIDO2/WebAuthn binds the authentication ceremony to the origin the browser is actually talking to, so a proxied domain cannot complete it on the victim's behalf — unlike OTP or push-based MFA, which an AiTM proxy can relay through transparently.
Monitor for anomalous domains resembling internal or SaaS login portals. Certificate Transparency log monitoring (e.g. via crt.sh) can surface newly issued certificates for lookalike domains before a campaign goes live.
Session/token lifetime and binding matter more than cookie flags. Shortening session lifetimes and binding sessions to additional signals (IP consistency checks, device fingerprinting, re-authentication for sensitive actions) reduces the value of a stolen session cookie even if one is captured.
This first lab established the full AiTM loop end to end: a proxy configuration that captures both credentials and a live session cookie, and a replay step that proves the stolen session is immediately usable without ever touching the victim's password again. Most of the real learning came from the troubleshooting along the way — TLS configuration, Start URL correctness, access-mode behavior, and domain case-sensitivity — all things that don't show up until you actually run the attack against a live proxy. The next lab in this series builds on this foundation with a more complex authentication flow.