Session Hijacking Detection
Session hijacking detection after MFA. Refresh-token reuse, device drift, and impossible travel, with SPL, KQL, and Sigma analytics plus a triage runbook.
Session hijacking detection is the discipline of catching an attacker who already holds a valid session. There is no failed login to alert on, no password spray to threshold, and no MFA prompt to deny, because authentication already succeeded and the attacker simply inherited the result. The stolen artifact is a cookie, a bearer token, or a refresh token, and to your application it is indistinguishable from the real user. This guide ships the analytics that make it distinguishable anyway.
Session hijacking maps to MITRE ATT&CK T1539 — Steal Web Session Cookie and T1550.004 — Use Alternate Authentication Material: Web Session Cookie. It is the technique that makes “we deployed MFA” an incomplete answer, and it is why phishing detection beyond DMARC matters even in an MFA-enforced estate.
What is session hijacking?
Session hijacking is the theft and reuse of an authenticated session artifact so that an attacker inherits an identity without ever learning its password. The victim authenticated correctly, satisfied MFA, and received a session. The attacker steals that session and replays it.
The defining property, and the reason it defeats most identity monitoring, is that there is no new login event to detect. Detection stacks are overwhelmingly built around authentication: failed logins, password spraying, impossible-travel-between-logins, new-device sign-ins. A hijacked session generates none of those. It generates ordinary API traffic from an already-trusted principal.
The artifact varies by architecture, and each variety has a different theft path and a different useful lifetime:
| Artifact | Typical lifetime | How it is stolen | Why it matters |
|---|---|---|---|
| Session cookie | Hours to weeks | Infostealer malware, XSS, physical access | Long-lived, often no binding at all |
| Access token (JWT) | Minutes to an hour | Proxy phishing, log leakage, SSRF | Short life limits damage window |
| Refresh token | Days to months | Infostealer, token-store compromise | The real prize — mints new access tokens |
| OAuth grant / consent | Until revoked | Illicit consent phishing | Survives password reset entirely |
| Cloud session token | Hours | Instance metadata SSRF, CI log leak | Inherits full workload permissions |
Note the fourth row in particular. An OAuth grant obtained through consent phishing survives a password reset and an MFA re-enrollment, because it is not a credential at all. It is a standing authorization. That is the case handled in OAuth misconfiguration defensive review.
How do attackers steal sessions?
Four vectors dominate, and they demand different telemetry. Knowing which one you are exposed to determines which detection below earns its keep.
Infostealer malware is now the volume leader. Commodity stealers harvest browser cookie stores and token caches from an endpoint, then sell them in bulk. The victim notices nothing because nothing on their machine breaks. The stolen cookie appears from a different IP, different device, and different browser build, which is precisely what the device-drift analytic below is built to catch.
Adversary-in-the-middle phishing proxies the real login page in real time. The victim sees a genuine site, enters credentials, and completes MFA against the legitimate identity provider. The proxy relays everything and keeps the resulting session cookie. This is the vector that makes phishing-resistant authentication necessary but not sufficient.
Token leakage through logs and telemetry is the self-inflicted one. Bearer tokens land in access logs, error traces, APM payloads, or CI job output, and anyone with log-read access inherits every session recorded there. This overlaps directly with secrets detection and is the vector most likely to be discovered internally rather than exploited externally.
Authorization bypass in the session-validation layer is rarer but severe, because it forges rather than steals. CVE-2025-29927, a Next.js flaw rated CVSS 9.1, allowed authorization checks implemented in middleware to be bypassed outright with a crafted header. Where session validity is decided in a single middleware layer, a bypass there is equivalent to holding every session at once. This is T1606 — Forge Web Credentials territory, and it argues for validating authorization at the resource, not only at the edge.
What telemetry do you need to detect session hijacking?
Every analytic in this post fails silently without the right fields, and this is the step most teams skip. Before writing a rule, confirm you are collecting the following.
| Requirement | Why it is load-bearing | Common failure |
|---|---|---|
| Stable session ID on every event | Correlates auth to later resource access | Only logged at login, not on API calls |
| Token issuance and redemption records | Makes reuse detection possible at all | Refresh redemptions not logged |
jti or equivalent token identifier | Distinguishes reissue from replay | Tokens logged without a unique ID |
| Device fingerprint at auth and on requests | Detects mid-session device change | Captured at login only |
| Source IP plus resolved ASN and geo | Powers impossible travel and ASN drift | Behind a proxy that overwrites the client IP |
| Explicit re-authentication events | Distinguishes legitimate context change | Step-up auth not emitted as its own event |
| Retention of at least 30 days | Sessions outlive short retention windows | 7-day retention on identity logs |
The two that break most deployments are the third and the fifth. Without a per-token identifier you cannot tell a legitimate reissue from a replay, and the reuse detection becomes impossible rather than merely noisy. And if your load balancer or CDN overwrites the client IP without populating a forwarded-for header you trust, every geo and ASN analytic silently evaluates your own edge nodes and never fires.
How to detect session hijacking
Three analytics, in descending order of fidelity. Deploy the first one even if you deploy nothing else.
Refresh-token reuse after rotation
This is the highest-fidelity identity detection available, and it deserves more attention than it gets. When refresh tokens rotate on every use, each token is strictly single-use: redeeming it issues a replacement and invalidates the original. A correctly behaving client therefore never presents a redeemed token twice.
So if a redeemed token is presented again, exactly two readings exist, and both are actionable. Either a legitimate client raced or crashed mid-rotation and retried with a stale copy, or somebody holds a copy they should not. You cannot distinguish them from the token alone, which is why the correct response is not to alert and investigate but to revoke the entire token family immediately. Every holder re-authenticates. The legitimate user experiences one re-login. The attacker loses persistence.
index=identity sourcetype=auth:token_redemption
| stats count AS redemptions,
dc(src_ip) AS distinct_ips,
dc(device_id) AS distinct_devices,
earliest(_time) AS first_use,
latest(_time) AS last_use,
values(user_agent) AS agents
by token_jti, user_id, token_family_id
| where redemptions > 1
| eval seconds_apart = last_use - first_use
| eval verdict = if(distinct_devices > 1 OR seconds_apart > 60,
"THEFT_LIKELY", "CLIENT_RACE_POSSIBLE")
| table user_id, token_family_id, token_jti, redemptions,
distinct_ips, distinct_devices, seconds_apart, verdict, agents
| sort - redemptions The verdict field encodes the one genuine ambiguity. A client race resolves in under a second
from the same device, because the retry is the same process recovering from a dropped response.
Two redemptions minutes apart from different devices is theft. Revoke the family in both cases
and use the verdict to decide whether to page someone.
Device-binding drift mid-session
The second-strongest signal is a session that changes device without changing identity. A stolen cookie is replayed from the attacker’s browser on the attacker’s machine, so the fingerprint moves while the session ID stays constant.
SigninLogs
| where TimeGenerated > ago(24h)
| project TimeGenerated, UserPrincipalName, SessionId,
DeviceFingerprint = tostring(DeviceDetail.deviceId),
Browser = tostring(DeviceDetail.browser),
OS = tostring(DeviceDetail.operatingSystem),
IPAddress, ASN = tostring(NetworkLocationDetails)
| where isnotempty(SessionId)
| summarize FirstSeen = min(TimeGenerated),
Fingerprints = make_set(DeviceFingerprint, 10),
Browsers = make_set(Browser, 10),
OSes = make_set(OS, 10),
IPs = make_set(IPAddress, 20),
ASNs = make_set(ASN, 10)
by SessionId, UserPrincipalName
| extend FingerprintCount = array_length(Fingerprints),
OSCount = array_length(OSes)
| where FingerprintCount > 1 or OSCount > 1
| join kind=leftanti (
SigninLogs
| where TimeGenerated > ago(24h)
| where AuthenticationRequirement == "multiFactorAuthentication"
| project SessionId
) on SessionId
| order by FingerprintCount desc The leftanti join is the part that makes this usable rather than noisy. It drops any session
that saw a genuine re-authentication, because a user who legitimately re-authenticated on a new
laptop is not an incident. What remains is device change without a corresponding auth event,
which is the actual anomaly. An operating-system change mid-session is especially strong; users
switch browsers, they do not switch from macOS to Linux inside one session.
Impossible travel within a single session
Impossible travel is a familiar analytic, but it is almost always applied between login events. Applied within one session, it becomes considerably more meaningful, because a single session legitimately belongs to a single physical person.
title: Impossible Travel Within a Single Session Lifetime
id: 8c1f4a26-7d3e-4b91-9a02-3f6d5e8b7c41
status: experimental
description: >
Detects a single authenticated session presenting from two geographic locations
whose separation cannot be covered in the elapsed time, indicating the session
artifact is held by more than one party.
references:
- https://attack.mitre.org/techniques/T1539/
- https://attack.mitre.org/techniques/T1550/004/
author: Colson
date: 2026/07/28
logsource:
category: application
product: identity_provider
detection:
selection:
event_type: 'resource_access'
session_id: '*'
timeframe: 1h
condition: selection | count(distinct_country) by session_id > 1
fields:
- session_id
- user_id
- src_ip
- src_country
- src_asn
- user_agent
falsepositives:
- Corporate VPN egress in a different country than the user
- Mobile carrier CGNAT assigning geographically distant egress addresses
- Users legitimately switching between VPN and local network mid-session
level: high Treat the Sigma rule as the portable statement of intent and implement the velocity maths in your SIEM, since Sigma’s correlation grammar cannot express great-circle distance over elapsed time. The threshold that works in practice is roughly 900 km/h, which sits above commercial aviation cruise speed and therefore treats a genuine flight as plausible rather than impossible.
Which false positives will you actually see?
This is the section that determines whether your detection survives its first month. Every rule above fires on legitimate behaviour in specific, predictable ways. Handle them deliberately.
| False positive | Which rule it hits | Why it happens | Resolution |
|---|---|---|---|
| Client retry after dropped response | Refresh reuse | Network failure mid-rotation | Allow a sub-second same-device grace window; still revoke the family |
| Corporate VPN egress abroad | Impossible travel | Egress node in another country | Allowlist known corporate ASNs and egress ranges |
| Mobile CGNAT geo jitter | Impossible travel | Carrier NAT pools span regions | Exclude known mobile ASNs from geo velocity |
| Browser auto-update | Device drift | User-agent string changes | Fingerprint on stable attributes, not full UA string |
| Multi-profile browser use | Device drift | Different profile, same machine | Include a hardware-stable component in the fingerprint |
| Legitimate device migration | Device drift | User genuinely got a new laptop | The leftanti re-auth join already suppresses this |
| Shared service accounts | All three | Many humans, one identity | Exclude and migrate to per-principal identities |
| Security scanner traffic | Device drift | Scanner replays session tokens | Allowlist scanner source ranges explicitly |
The last row on shared service accounts is worth acting on rather than suppressing. A service account used by six engineers is indistinguishable from a hijacked session by construction, and no analytic will fix that. Migrating to per-principal identity is the real remediation, and the detection noise is simply telling you the truth about your identity model.
How do you triage a suspected hijacked session?
Detection without a runbook produces alerts nobody can action. Work these in order, because the order is chosen to contain first and investigate second.
- Revoke the token family immediately. Do not investigate first. Revocation costs the legitimate user one re-login; delay costs you the attacker’s entire dwell time. This is the rare case where containment genuinely precedes analysis.
- Pull every action taken under the session ID. You logged the session ID on resource events, so scope is a single query rather than an archaeology project.
- Separate the two device fingerprints. Establish which was the user and which was not by comparing against the user’s 30-day baseline, then attribute each action to one of them.
- Check for persistence established under the session. Look specifically for new OAuth grants, new API keys, new MFA methods enrolled, mail-forwarding rules, and new registered devices. This is the step that determines whether revocation actually ended the intrusion.
- Check for lateral movement. A hijacked session on one platform frequently precedes lateral movement elsewhere via SSO.
- Determine the theft vector. Endpoint infostealer, phishing proxy, or leaked token in logs. The answer changes the remediation entirely and is worth the time.
- Force re-enrollment if the endpoint is implicated. If a stealer took the cookie, it took everything else in that browser profile too. Treat the whole profile as compromised.
- Rotate anything the session could reach. Application secrets, API keys, and credentials accessible to that identity.
Step 4 is the one most often skipped and most often decisive. An attacker who used a hijacked session to enroll their own MFA method or mint an API key retains access after you revoke every token, and you will conclude the incident is closed while it is open.
How to test your session hijacking detection
In a lab environment, against systems you own or are explicitly authorized to test:
- Authenticate, capture the refresh token, redeem it once legitimately, then present the redeemed token a second time. Confirm the reuse analytic fires and the family revokes.
- Copy a valid session cookie into a second browser on a different operating system. Confirm
device-drift fires and that the
leftantijoin does not suppress it. - Re-authenticate legitimately on a genuinely new device. Confirm this does not alert, so you know the suppression works in the direction you intended.
- Route a session through a VPN egress in another country mid-session. Confirm impossible travel fires, then confirm your corporate-ASN allowlist suppresses the known-good case.
- Verify the analytics still fire when the client is behind a proxy, which is where the client-IP field most often turns out to be your own load balancer.
Item 3 matters more than it looks. A detection that fires on everything is operationally identical to no detection, and confirming the negative case is how you know the rule is discriminating rather than merely alerting.
How to prevent session hijacking
Detection is the backstop. The architecture is what actually constrains the attack, and the controls below are ordered by how much they reduce the value of a stolen artifact.
Beyond the token mechanics, four controls carry disproportionate weight:
- Validate authorization at the resource, not only at the edge. CVE-2025-29927 is the argument. Where a single middleware layer decides every session’s validity, a bypass there is equivalent to holding every session simultaneously. Defence in depth means the resource re-checks.
- Set cookies correctly and boringly.
HttpOnlyprevents script access,Secureprevents cleartext transmission, andSameSite=LaxorStrictblunts cross-site replay. These are free and still frequently missing. - Bind sensitive operations to re-authentication, not to session age. “Authenticated within the last 30 minutes” is weaker than “prove possession of the key right now.”
- Constrain OAuth grant scope and expire grants. A standing grant outlives password resets; a scoped, expiring one does not.
Which session control should you deploy first?
| Control | Stops | Cost to implement | User friction | Deploy when |
|---|---|---|---|---|
| Short access-token TTL | Long-lived replay | Low | None | Always, first |
| Refresh rotation + family revocation | Persistent theft | Medium | One re-login on trigger | Always, second |
| Device binding | Cross-device replay | Medium | Occasional re-auth | You have a stable client |
| Step-up on sensitive actions | Damage from any hijack | Medium | Prompt on high-risk actions | You hold money or data |
| Full token binding (DPoP/mTLS) | Nearly all replay | High | None once working | Regulated or high-value |
| Continuous access evaluation | Post-issuance revocation lag | High | None | You are on a supporting IdP |
Start at the top. Short lifetimes plus rotation with family revocation deliver most of the available risk reduction for a fraction of the effort of token binding, and they are the two that make the detections in this post possible in the first place.
Common session hijacking detection mistakes
- Assuming MFA covers it. It covers the authentication event and nothing after it.
- Alerting on refresh reuse instead of revoking. Reuse is proof, not a lead. Revoke first.
- Logging session IDs only at login. This severs the correlation every analytic needs.
- Fingerprinting on the raw user-agent. Guarantees mass false positives on browser update day.
- Running impossible travel between logins only. Within-session is the stronger form.
- Trusting the client IP behind a proxy. Verify you are not geolocating your own edge.
- Closing the incident at revocation. Check for MFA enrollment, API keys, and OAuth grants.
- Ignoring service accounts. They defeat the model rather than merely adding noise.
Session hijacking detection checklist
- Carry a stable session identifier onto every resource-access event, not just authentication.
- Log token issuance and redemption with a unique per-token identifier.
- Deploy refresh-token rotation and alert on any reuse of a redeemed token.
- Revoke the whole token family automatically on reuse rather than queuing an investigation.
- Capture a device fingerprint built from update-stable attributes, on every request.
- Detect device or OS change mid-session, suppressed by a genuine re-authentication join.
- Run impossible travel within a single session at roughly a 900 km/h velocity threshold.
- Allowlist corporate VPN and mobile-carrier ASNs before enabling geo analytics.
- Verify the client IP is the real client and not your load balancer.
- Set access-token TTL to fifteen minutes and bind refresh tokens to device plus IP range.
- Require step-up re-authentication for MFA changes, API-key creation, and payment changes.
- Validate authorization at the resource layer, not only in edge middleware.
- Retain identity telemetry for at least 30 days so sessions do not outlive their evidence.
- Rehearse the triage runbook, especially the persistence check at step 4.
The takeaway
Session hijacking detection works because a stolen artifact cannot carry its original context. The token can be copied; the device, the location, and the single-use guarantee cannot. Detect refresh-token reuse first, because it is proof rather than suspicion, then layer device drift and within-session impossible travel. Revoke on evidence instead of investigating toward certainty. Then remove most of the value from theft with short lifetimes, rotation, binding, and step-up authentication. Continue with OAuth misconfiguration defensive review, JWT misconfiguration detection, and credential stuffing detection, or browse the full Detection Engineering pillar.
Training & tools referenced
Disclosure: Some links below are affiliate links. If you buy through them, darkpwn may earn a commission at no extra cost to you. We only recommend training and tools we actually use in our own lab, and affiliate links never influence editorial coverage.
- TryHackMeAuthorized labs to practice identity attack analysis and session securitySecurity TrainingStart training
- YubiKeyPhishing-resistant hardware keys for step-up re-authenticationHardware SecurityView hardware keys
Frequently asked questions
What is session hijacking?
Session hijacking is the theft and reuse of an authenticated session artifact — a cookie, bearer token, or refresh token — so the attacker inherits an already-authenticated identity without ever knowing the password. Because authentication already happened, the stolen session sails past MFA entirely. The login event looks normal because there is no new login event.
How do you detect session hijacking?
Detect it on the signals a stolen token cannot carry with it. The strongest is refresh-token reuse after rotation, which is mathematically impossible for a legitimate client and therefore near-zero false positive. Then layer device-fingerprint drift mid-session, impossible travel within one session lifetime, and ASN or user-agent changes without a re-authentication event.
Does MFA stop session hijacking?
No. MFA protects the authentication event, and session hijacking happens after that event has already succeeded. A stolen session cookie or refresh token represents completed authentication, so replaying it never triggers an MFA prompt. Only token binding, short token lifetimes with rotation, and step-up re-authentication on sensitive actions meaningfully constrain a stolen session.
What is refresh-token reuse detection?
When refresh tokens rotate on every use, each token is single-use and its replacement invalidates it. If a token that was already redeemed is presented again, either the client or an attacker holds a stale copy — and in both readings the token family is compromised. The correct response is to revoke the entire family, forcing everyone holding any copy to re-authenticate.
How long should access tokens live?
Short enough that a stolen one expires before it is useful. Fifteen minutes is a practical floor for access tokens on interactive platforms, paired with refresh tokens that rotate on every use and carry device plus IP-range binding. The refresh token, not the access token, is the artifact worth defending, because it is the one with long-lived value.