2

Detection

Token Randomness

🔎

Detection

Session ID randomness is harder to verify conclusively without source access, but there are still useful black-box checks.

The basic approach is to collect a sample of session identifiers issued by the application. Log in repeatedly or, where appropriate, create, reset, or re-authenticate sessions and compare the values that are generated.

Look for obvious signs that the identifiers may be predictable:

  • Sequential or incrementing values.

  • Timestamps or dates embedded in the token.

  • Repeating prefixes or predictable components that change in an obvious way.

  • Values that decode from formats such as Base64 or hex into recognizable application data, counters, timestamps, usernames, or other structured information.

Collect multiple session IDs
        ↓
Session 1: abc...
Session 2: def...
Session 3: ghi...
Session 4: jkl...
        ↓
Look for patterns

   Sequential values?
   Embedded timestamps?
   Counters?
   Predictable components?
   Recognizable decoded structure?
        ↓
If yes:
Investigate token generation
        ↓
If no obvious pattern:
 No immediate evidence of predictability

It is important not to overinterpret this test.

A session ID encoded as hexadecimal or Base64 is not inherently weak. Those formats are commonly used simply to represent random bytes as text that can safely be transported in a cookie.

Likewise, a token that appears completely random is not proof that it was generated securely. A weak generator can produce values that look random to a human observer while still being predictable to an attacker.

"It looks random" is useful evidence, but it is not a guarantee.

Review the code responsible for generating session identifiers and confirm that the value comes from the language or platform's cryptographically secure random number generator, rather than a general-purpose or predictable random function.

Also verify that the identifier is not constructed from predictable inputs such as:

  • Username or account ID.

  • Current timestamp.

  • IP address.

  • Process or request counters.

  • Other deterministic application state.

Good session ID generation

Cryptographically secure random byte source
        ↓
High-entropy random bytes
        ↓
Encoded if necessary (Base64, hex, etc.)
        ↓
Session identifier

Compare that with a predictable construction:

Predictable application data

Username
    +
Timestamp
    +
Counter
    +
Other known values
        ↓
Encoded or hashed
        ↓
 May look complex without actually being unpredictable

Encoding or hashing predictable inputs does not automatically create randomness. If an attacker can predict or reconstruct the underlying inputs, the resulting session identifier may still be predictable.

3

Detection

Fixation Testing

🔎

Detection

The basic idea is simple: capture the session identity before authentication, authenticate normally, and then verify that the pre-authentication identity has been completely replaced.

Step 1: Obtain a Pre-Authentication Session ID

Visit the application's login page or another page that establishes a pre-authentication session, then record the session cookie issued by the server.

Visit login page
        ↓
Server issues pre-authentication session
        ↓
Record: Session ID = A

A more aggressive variation is to manually set a session cookie to a value of your own choosing before visiting the login form.

This can help test whether the application accepts attacker-supplied session identifiers rather than recognizing only identifiers it generated itself. A secure application may simply reject an unknown value and issue a fresh session instead, which is itself useful information.

Step 2: Authenticate Normally

Log in using valid credentials

Before login:

Session ID = A
        ↓
Victim authenticates
        ↓
Check the session ID again

The first question is:

Did the session ID change?

Before authentication:

Session ID = A
        ↓
After authentication:
Session ID = B
        ↓
A ≠ B

✓ Session identifier rotated

If the identifier is identical before and after login, the application is reusing the pre-authentication session identity and may be vulnerable to session fixation.

But checking that the cookie value changed is not enough.

Step 3: Test the Old Session ID

After authentication, take the pre-login session ID and present it in a separate, isolated request or browser context.

The question is:

Does Session ID A still provide access to the authenticated session?

Pre-login:

Session ID = A
        ↓
User logs in
        ↓
Application issues: Session ID = B
        ↓
Test the old credential separately: Present Session ID = A
        ↓
Correct behavior: Old ID is rejected or represents only non-authenticated state
        ↓
Authenticated access is unavailable

A correct implementation must satisfy both conditions:

1. Session ID changes at authentication:   A → B
        +
2. The old session identity loses authority
   A → Invalid / unauthenticated
        ↓
✓ Fixation defense working correctly

The important test is therefore not merely:

"Did the browser receive a new cookie?"

Test Every Authentication Path:

Do not perform this test only against the primary username-and-password login form.

Any flow that transitions a user from an unauthenticated state into an authenticated session must apply the same session rotation behavior.

Test every relevant authentication path, including:

Authentication paths

   ├── Username / password login
   ├── Password reset followed by auto-login
   ├── OAuth / SSO callback
   ├── "Remember me" session restoration
   ├── Magic-link authentication
   └── Any other flow that establishes
       authenticated session state

        ↓
For each path:

Capture old ID
        ↓
Authenticate
        ↓
Confirm: Old ID ≠ New ID
        AND
Old ID no longer provides authenticated access

These secondary authentication paths are especially important because they are often implemented separately from the primary login flow and may bypass the centralized session-rotation logic.

The complete test can therefore be summarized as:

Capture the session ID before authentication. Authenticate. Confirm that the ID changed. Then actively reuse the old ID and confirm that the server no longer accepts it as an authenticated credential.

4

Detection

Logout Testing

🔎

Detection

This test verifies that logout actually invalidates the session server-side, rather than merely removing the cookie from the user's browser and redirecting them to the login page.

The procedure is straightforward.

First, log in normally and capture the active session cookie value. This can be done through the browser's developer tools or by observing the authenticated request in an intercepting proxy.

User logs in
        ↓
Authenticated session established
        ↓
Capture: Session ID = A

Next, log out through the application's normal user interface.

User clicks: [ Log Out ]
        ↓
Application redirects to login page
        ↓
Browser appears logged out

At this point, do not assume logout succeeded simply because the browser was redirected or the session cookie disappeared from its cookie storage.

The important test is what happens when the captured pre-logout credential is used again.

Manually send a request to an authenticated endpoint while presenting the original session ID.

Before logout:

Session ID = A
        ↓
Capture Session ID = A
        ↓
User logs out normally
        ↓
Replay a request using: Session ID = A
        ↓
Correct behavior: Request is rejected
        ↓
Session no longer provides authenticated access

If the request still succeeds, the logout operation did not actually invalidate the session.

The application may have removed the cookie from the legitimate user's browser or redirected them to a login page, but the server-side session record remained valid.

User clicks: [Log Out]
        ↓
Browser deletes cookie or redirects to login page
        ↓
But: Server-side Session A remains valid
        ↓
Anyone holding Session ID = A
        ↓
Can continue making authenticated requests
        ↓
 Logout was only cosmetic

This matters because the legitimate user's browser is not necessarily the only place where the session credential exists.

Someone may already have captured the token through a shared or public device, an exposed proxy or debugging log, an earlier session-theft vulnerability, or another form of credential leakage.

If logout only affects the legitimate user's local browser, every copied instance of that credential remains usable.

A correctly implemented logout must therefore invalidate the session at the server.

User clicks: [ Log Out]
        ↓
Application identifies Session ID = A
        ↓
Server-side session record is destroyed or invalidated
        ↓
Browser clears its local cookie
        ↓
Any future request using A
        ↓
✗ Rejected

The test can be summarized as:

Capture the session credential. Log out normally. Replay the captured credential against an authenticated endpoint.

The result should be unambiguous:

Pre-logout session ID
        ↓
Logout
        ↓
Replay old credential
        ↓
Expected:
401 Unauthorized
or equivalent unauthenticated response
        ↓
✓ Session invalidated server-side

The key principle is:

A logout page, redirect, or deleted browser cookie does not prove that a session has been terminated.

5

Detection

Expiration Testing

🔎

Detection

Session expiration needs to be tested against the two separate expiration models discussed in section Session Lifecycle: idle expiration and absolute expiration.

These controls solve different problems, so verifying one does not automatically verify the other.

Testing Idle Expiration:

For idle expiration, the test is straightforward.

Authenticate normally, then stop interacting with the application and wait longer than the documented idle timeout.

After the timeout has passed, attempt to use the session again.

Authenticate
        ↓
Session becomes active
        ↓
No activity for longer than the configured idle timeout
        ↓
Attempt an authenticated request
        ↓
Expected: ✗ Session rejected
        ↓
Re-authentication required

The important detail is that the application should reject the expired session server-side.

A browser redirect to the login page or a client-side countdown reaching zero is not sufficient evidence that the session has expired.

The captured session credential should no longer authenticate a request.

Idle timeout reached
        ↓
Present existing session credential
        ↓
Server checks last activity time
        ↓
Idle timeout exceeded
        ↓
✗ Request rejected

Testing Absolute Expiration:

Absolute expiration is different.

An absolute session lifetime places a maximum age on the session regardless of how actively it is being used.

Session created
        ↓
Activity continues
        ↓
Activity continues
        ↓
Activity continues
        ↓
Absolute lifetime reached
        ↓
 Session expires anyway

This is often harder to test purely from the outside because the configured lifetime may be long 24 hours or more, for example.

Where source code or configuration is available, it is usually more practical to verify directly that the application enforces a maximum session lifetime independently of idle activity.

The key question is:

Can continuous activity keep the same session alive indefinitely?

If the answer is yes, the application may have idle expiration but no absolute expiration.

Idle expiration only:

Request → resets inactivity timer
Request → resets inactivity timer
Request → resets inactivity timer
Request → resets inactivity timer
        ↓
Potentially remains active indefinitely as long as activity continues

Under absolute expiration, continued activity does not change the session's maximum lifetime.

Absolute expiration:

Session created at T0
        ↓
User remains continuously active
        ↓
Maximum lifetime reached
        ↓
Session expires
        ↓
Re-authentication required

The expiration check should be enforced by the server, using the session's actual creation time or another server-controlled lifetime value not by trusting a timestamp supplied by the client.

Test the Expiration Boundary:

It is also worth testing what happens when a request occurs at or immediately around the expiration boundary.

For example:

Session expires at 12:00:00
        ↓
Request begins at 11:59:59
        ↓
Request reaches the server as the expiration boundary passes

The application's behavior should be consistent with its defined expiration policy.

Verify the Check Happens on Every Authenticated Request:

Finally, confirm that session expiration is enforced by the server for authenticated requests.

A client-side timer might do this:

30 minutes pass
        ↓
JavaScript redirects browser to /login
        ↓
Browser appears logged out

But that does not prove the session itself has expired.

If the original session cookie can still be manually presented to an authenticated endpoint and the server accepts it, the session remains valid.

Client-side timer expires
        ↓
Browser redirects to login
        ↓
Captured session cookie is replayed
        ↓
Server still accepts it
        ↓
 Session never actually expired

The correct implementation looks like this:

Authenticated request arrives
        ↓
Server checks: Is session valid?
        ↓
Has idle timeout expired?
        ↓
Has absolute lifetime expired?
        ↓
If either limit has been exceeded: Reject session

The complete testing principle is:

Idle expiration verifies that an inactive session eventually dies. Absolute expiration verifies that even an actively used session cannot live forever.

You've completed Session Management

Great work — explore other topics to keep learning.