Session Hijacking
Introduction¶
This case study reproduces a session hijacking vulnerability against WebGoat v2025.3 by identifying weaknesses in the identifier generation logic and exploiting the keyspace collapse that follows from pairing a global counter with a wall-clock timestamp. The objective is to demonstrate the vulnerability and the mitigations in a controlled environment and shine a light on the importance of modern session management practices in the context of web application security.
Session hijacking is a type of attack where an attacker gains access to a user’s session by hijacking the session identifier 12. This can be done by predicting the session identifier or by intercepting the session identifier and modifying it. The underlying weakness when identifiers are predictable is insufficient entropy in their generation 3, a concern that session management guidance in NIST SP 800-63B-4 addresses directly 4.
A web session is a sequence of requests and responses between a client and a server that lasts for a period of time. During this time, every interaction between the client and the server will include a series of variables that contain the session state and allow for improvements in the user experience, security, and functionality. Usually, these variables are stored in an HTTP cookie and are used to to ensure that the user is authenticated and authorized to access the resources they need without having to re-authenticate for each request 2.
Session hijacking can be classified into two forms: targeted and generic. In a targeted attack, the attacker knows the victim’s session identifier and is able to predict it. In a generic attack, the attacker does not know the victim’s session identifier but is able to intercept the network traffic and modify the identifier. This case study demonstrates a targeted attack that identifies weaknesses in the identifier generation logic by observing behavior during normal login, and exploits the keyspace collapse that follows from pairing a global counter with a wall-clock timestamp.
Mitigation strategies for session hijacking for modern web applications include:
Generic Session ID Name. Using a generic session ID name such as
idinstead of a specific or default name likePHPSESSIDorJSESSIONIDto avoid fingerprinting the application.Sufficient Entropy. Using a cryptographically secure random number generator (CSPRNG) to generate the session identifier to ensure that the identifier is unpredictable and unique.
Session ID Length. Must be sufficiently long to make it difficult for an attacker to guess or enumerate.
Session ID Content. Must be meaningless and opaque to the attacker to prevent information leakage about the session identifier and state.
Additional session management practices include using built-in mechanisms provided by the web application framework or server to manage sessions, and implementing TLS for the entire session to prevent man-in-the-middle attacks.
WebGoat v2025.3
docker run --rm -p 8080:8080 -p 9090:9090 webgoat/webgoat:v2025.3Burp Suite v2025.10.6
java -jar /path/to/burpsuite_community.jar --user-config-file=burp-config.jsonA reference configuration is provided at tools/burp-config.json in the repository.
Methods¶
This case study reproduces a predictable-identifier session hijacking against the WebGoat HijackSession lesson at the pinned tag v2025.3. The investigation proceeds in three phases: observe the structure of the hijack_session cookie issued during normal login, force the server to mint fresh identifiers in sequence to characterize the generation logic, then detect a counter increment that skips a value (indicating another client minted an identifier between two of the attacker’s own) and predict that missing value to hijack the corresponding session. Requests are observed through Burp Suite Community Edition (see Materials). The success criterion is recovery and successful submission of a session identifier the server did not issue to the attacker.
Results¶
The attack hinges on a single observation: the server allocates session identifiers from a shared counter, so a victim’s identifier sits between two of the attacker’s own. The flow is summarized in Figure 1.
Figure 1:Counter-skip observation enabling identifier prediction: the attacker’s two observations bracket the victim’s allocation between at and at .
The login page POSTs to /WebGoat/HijackSession/login. The form source confirms the endpoint and shows no client-side token, nonce, or anti-tampering mechanism. Whatever protects authenticated requests has to live in the session identifier itself.

Figure 2:The WebGoat Session Hijacking lesson login form.

Figure 3:Login form source confirms no client-side token or anti-tampering mechanism; protection relies on the session identifier alone.
Intercepting the login surfaces a Cookie: hijack_session=<a>-<b> header on the request 5. The value is two numeric fields separated by a dash, with the left field noticeably longer than the right. Two questions follow: what generates each field, and which (if either) is predictable.

Figure 4:The intercepted login request showing the hijack_session=<a>-<b> cookie.
The right-hand field is the more legible of the two. Its thirteen-digit magnitude lines up with a Unix timestamp in milliseconds 6, and it advances by roughly the wall-clock interval elapsed between requests. That accounts for half of the identifier.
The left-hand field is more interesting. Replaying the same request, logging in with different credentials, and logging in and out of WebGoat all return the same value. As long as a valid session exists on the server, the cookie is stable. That’s normal session-store behavior and reveals nothing about how the value is allocated.
Forcing the server to mint a fresh identifier is the next move. Deleting the cookie entirely and re-issuing the request returns a new Set-Cookie: the timestamp moves forward by however many seconds have passed, and the left-hand field increments by one.

Figure 5:Deleting and re-issuing the request returns a new Set-Cookie with the left field incremented by one.
Repeating the deletion-and-reissue loop confirms the pattern. The left-hand field is a counter, not a random value, and it increments globally rather than per-client.

Figure 6:Sequential increment of the hijack_session counter across repeated reissues.
Every ten requests or so, the counter increments by two instead of one. The server has effectively handed out an identifier in between two of the attacker’s own, and the only plausible recipient is another client logging on during that gap.

Figure 7:Every ten requests or so the counter skips by two, indicating another client received the missing value.
That observation collapses the keyspace. After seeing at timestamp and at timestamp , the missing identifier is exactly , and its timestamp lies in . The candidate space is one counter value paired with the millisecond values in that window, on the order of a few thousand candidates for a sub-second gap and well within reach of automated submission. Submitting hijack_session=(N+1)-T_0 as the first guess lands the victim’s session: happened to equal , meaning the missing identifier was minted within the same millisecond as the attacker’s first observation. When that first guess misses, iterating (N+1)-T across the remaining milliseconds in converges within a small number of attempts.

Figure 8:Submitting the inferred (N+1)-T identifier returns the victim’s session.
Discussion¶
The vulnerable components live under src/main/java/org/owasp/webgoat/lessons/hijacksession/ at the v2025.3 tag 7, split across the assignment controller and a cas/ sub-package containing the authentication abstractions. Three pieces are load-bearing: a static-field-and-supplier pair that mints identifiers, a queue that validates them, and an auto-login routine that exposes minted identifiers to external observation.
Identifier generation is concentrated in two lines of HijackSessionAuthenticationProvider:
private static long id = new Random().nextLong() & Long.MAX_VALUE;
private static final Supplier<String> GENERATE_SESSION_ID =
() -> ++id + "-" + Instant.now().toEpochMilli();Program 1:Session identifier generation in HijackSessionAuthenticationProvider.
The counter id is a process-global static long, seeded once at class load with new Random().nextLong() masked to non-negative values. Each invocation of the supplier pre-increments id and concatenates it with the current Unix epoch in milliseconds, producing the <counter>-<epochMillis> wire format observed in Results. Three weaknesses follow directly from this construction. The counter portion is sequential, providing no entropy in the per-identifier output; the timestamp portion is observable from any wall clock; and the seed itself is drawn from java.util.Random rather than java.security.SecureRandom, so once any single value is observed and the increment pattern recognized, all subsequent values are derivable without recovering the seed. The increment is also non-atomic: ++id on a non-volatile, non-AtomicLong static is a torn-write hazard under concurrent invocation, though predictability rather than consistency is the dominant failure.
The login entry point is HijackSessionAssignment.login(...), the @PostMapping("/HijackSession/login") handler. When the incoming hijack_cookie is empty, the handler builds an Authentication carrying the submitted credentials, passes it through provider.authenticate(...), and writes the resulting identifier back to the response via setCookie(...). Inside authenticate(...), the supplier fires only when authentication.getId() is empty; the just-minted identifier is then returned to the caller but is not itself added to the queue used for validation. Instead, authorizedUserAutoLogin() runs immediately after and, with probability ~0.25 per call, mints a separate authenticated session via AUTHENTICATION_SUPPLIER (which itself invokes GENERATE_SESSION_ID) and enqueues that identifier into an instance-level Queue<String> sessions of capacity 50. Validation of an incoming cookie is then a literal sessions.contains(cookieValue) membership check, with no binding to the requesting client’s IP, fingerprint, or prior session state.
The intuitive reading recorded in Results (a counter that occasionally skips a value, with the missing identifier behaving as if another client had received it) is faithful to how this generation logic would fail in a deployed system. A real concurrent user logging in between two of the attacker’s observations would produce the same counter skip, and the attacker’s predicted value would remain valid against that user’s session. The WebGoat implementation synthesizes the concurrent traffic deterministically with authorizedUserAutoLogin so a single-operator exercise can reproduce the pattern, but the predictability of GENERATE_SESSION_ID is independent of that scaffolding. The empirical demonstration in Results therefore generalizes: any deployment of this supplier exposes counter increments to any external observer who can request a new cookie, and the lab’s autologin produces the same observable pattern that real user traffic would.
Three counterfactual controls would close the demonstrated path. First, replacing the sequential-plus-timestamp construction with a 128-bit value drawn from java.security.SecureRandom, URL-safe-base64 encoded, removes the predictability that the counter-skip observation exploits; the cookie format on the wire is independent of the supplier, so only GENERATE_SESSION_ID needs to change 24. Second, the identifier should be rotated immediately after successful authentication and bound to the authenticated principal, so that membership in a server-side set is necessary but not sufficient for authorization. This is the second-layer failure that survives even an entropy fix. A stronger identifier accepted on possession alone is still hijackable by any party who comes to possess it 8. Third, identifiers should never be minted along paths the authentication flow does not exercise. The literal authorizedUserAutoLogin is a teaching device, but the underlying anti-pattern (populating a session store from a code path the legitimate user never traverses) recurs in production systems as fixture loaders, admin tooling, and session-replay debug aids that quietly broaden the keyspace an attacker can target.
Taxonomically the case sits at the intersection of two CWE classes governing identifier predictability: insufficient randomness in the seed and counter 3, and predictability of the resulting numeric output 9. The validation-by-membership failure shades into the broader category of authentication failures captured by the OWASP A07:2025 entry 1. Mitigation guidance for each of the counterfactuals above is consolidated in the Session Management Cheat Sheet 2 and in the identity-and-session sections of NIST SP 800-63B-4 4.
Conclusion¶
The case study reproduces a targeted session hijacking against WebGoat v2025.3 by predicting a single missing identifier in a globally-incrementing counter paired with a millisecond timestamp. The keyspace collapse is empirical, not theoretical: two observations bracketing a counter skip reduce the candidate space to one counter value and the milliseconds in , which automated submission resolves within a small number of attempts. The vulnerability does not depend on the lab’s synthetic concurrent traffic. Any deployment of the same supplier exposes the same pattern to any client that can request a fresh cookie.
Read against the mitigations named in the Introduction, the failure is structural. The identifier carries neither sufficient entropy (the counter is sequential and the seed is drawn from java.util.Random rather than a CSPRNG) nor opaque content (the <counter>-<epochMillis> format reveals its own allocation order). Length is incidental once content is predictable: a thirteen-digit timestamp concatenated with a sequential counter is no harder to guess than the counter itself. Closing the demonstrated path requires a 128-bit opaque value drawn from java.security.SecureRandom, rotated immediately after successful authentication and bound to the authenticated principal, with no identifier minting along code paths the legitimate user does not traverse. The Session Management Cheat Sheet and NIST SP 800-63B-4 specify these properties together because each one is necessary and none is sufficient on its own.
- OWASP Foundation. (2025). A07:2025 Authentication Failures. OWASP Top 10:2025.
- OWASP Foundation. (n.d.). Session Management Cheat Sheet. OWASP Cheat Sheet Series. Retrieved May 24, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html
- MITRE Corporation. (n.d.). CWE-330: Use of Insufficiently Random Values. Common Weakness Enumeration. Retrieved May 24, 2026, from https://cwe.mitre.org/data/definitions/330.html
- National Institute of Standards and Technology. (2024). Digital Identity Guidelines: Authentication and Authenticator Management (Techreport NIST SP 800-63B-4). U.S. Department of Commerce. https://pages.nist.gov/800-63-4/sp800-63b.html
- Mozilla Developer Network. (n.d.). Set-Cookie. MDN Web Docs. Retrieved May 24, 2026, from https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie
- Wikipedia. (n.d.). Unix time: Representing the number. Wikipedia, The Free Encyclopedia. Retrieved May 24, 2026, from https://en.wikipedia.org/wiki/Unix_time#Representing_the_number
- WebGoat Project Contributors. (2025). WebGoat: A deliberately insecure web application. GitHub repository, tag v2025.3. https://github.com/WebGoat/WebGoat/tree/v2025.3
- OWASP Foundation. (n.d.). Authorization Cheat Sheet. OWASP Cheat Sheet Series. Retrieved May 24, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/Authorization_Cheat_Sheet.html
- MITRE Corporation. (n.d.). CWE-340: Generation of Predictable Numbers or Identifiers. Common Weakness Enumeration. Retrieved May 26, 2026, from https://cwe.mitre.org/data/definitions/340.html