Session Fixation
Session Fixation at a glance
Overview
Session fixation occurs when an attacker can make a victim use a session identifier chosen by the attacker, and the server later associates that identifier with an authenticated user. This often happens when apps accept session ids from insecure transports, fail to rotate or invalidate sessions on authentication, or preserve pre-auth session ids across privilege changes.
Where it occurs
It occurs in session management logic that reuses or exposes session IDs through URLs or headers, fails to regenerate IDs on reauthentication, or relies on outdated session mechanisms.
Impact
An attacker who successfully fixes a session can immediately access victim data and perform actions as that user. Since no credential theft is necessary, it bypasses password controls and often avoids suspicion until unusual activity is observed.
Prevention
Prevent this by rejecting external session IDs and using secure cookies only; on sign-in invalidate the old session, issue a new ID copying only non-sensitive data, use framework helpers to rotate IDs, and audit middleware that accepts tokens.
Examples
Switch tabs to view language/framework variants.
App accepts session id from query and reuses it after login
If the application uses a session identifier supplied by URL parameters and does not rotate it at authentication, attackers can fix a session.
app.use((req,res,next)=>{
if (req.query.sid) req.cookies['connect.sid'] = req.query.sid; // BUG: accept external session id
next();
});
app.post('/login', (req,res)=>{
// authenticate
req.session.user = { id: 123 };
res.send('ok'); // session id not regenerated
});- Line 2: Trusting session id from query allows an attacker to control session identifier
- Line 6: Session id not regenerated on login
If an attacker can set or guess the session id a victim will adopt, and the server does not issue a fresh session id on login, the attacker can reuse that id to access the victim's account.
app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: true }));
app.post('/login', (req,res)=>{
// authenticate
req.session.regenerate(err=>{ if (err) return res.status(500); req.session.user = { id:123 }; res.send('ok'); });
});- Line 9: Regenerate session id on privilege changes to break fixation
Treat session identifiers as secrets; never accept them from untrusted inputs and create a fresh id at authentication (session regeneration).
Engineer Checklist
-
Do not accept session identifiers from query parameters, POST bodies, or arbitrary headers
-
Invalidate old session and create a new session id on successful authentication
-
Use framework sign-in/login helpers that rotate session ids by default
-
Disable URL-based session transport and canonicalize authentication to secure cookies
-
Audit middleware and third-party libraries for any path that reads or sets session ids
End-to-End Example
A legacy site accepts `_session_id` from URLs for compatibility. An attacker sends a link with a chosen id to a victim. The victim logs in and the server binds the new authenticated state to that id. The attacker replays the id and gains access.
// Node.js/Express + express-session - Vulnerable session fixation
const session = require('express-session');
app.use(session({
secret: 'my-secret',
resave: false,
saveUninitialized: true,
cookie: { secure: false } // Also vulnerable: not using secure cookies
}));
// VULNERABLE: Accepts session ID from query parameter
app.use((req, res, next) => {
// Legacy feature for URL-based sessions
if (req.query.sessionid) {
// DANGEROUS: Allows attacker to fix session ID
req.sessionID = req.query.sessionid;
req.session.id = req.query.sessionid;
}
next();
});
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username });
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// VULNERABLE: Does NOT regenerate session on authentication!
// Session ID remains the same as before login
// If attacker set the session ID via URL, they can now use it
req.session.userId = user._id;
req.session.username = user.username;
req.session.role = user.role;
res.json({ message: 'Login successful' });
});
app.get('/account', (req, res) => {
if (!req.session.userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
// Returns sensitive data using potentially attacker-fixed session
res.json({
userId: req.session.userId,
username: req.session.username,
balance: '$5,000.00',
apiKey: 'sk_live_xyz123'
});
});// Node.js/Express + express-session - SECURE against session fixation
const session = require('express-session');
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false, // Don't create sessions for unauthenticated users
cookie: {
secure: true, // HTTPS only
httpOnly: true, // No JavaScript access
sameSite: 'lax',
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
// SECURE: Do NOT accept session IDs from query parameters or request body
// Session ID should ONLY come from secure, httpOnly cookies
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = await User.findOne({ username });
if (!user || !(await bcrypt.compare(password, user.password))) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// SECURE: Regenerate session ID on authentication
// This prevents session fixation attacks
req.session.regenerate((err) => {
if (err) {
return res.status(500).json({ error: 'Session error' });
}
// Now set authenticated user data in NEW session
req.session.userId = user._id;
req.session.username = user.username;
req.session.role = user.role;
req.session.authenticatedAt = new Date();
// Save session before responding
req.session.save((err) => {
if (err) {
return res.status(500).json({ error: 'Session error' });
}
res.json({ message: 'Login successful' });
});
});
});
app.post('/logout', (req, res) => {
// SECURE: Destroy session on logout
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: 'Logout error' });
}
res.clearCookie('connect.sid'); // Clear session cookie
res.json({ message: 'Logged out successfully' });
});
});
app.get('/account', (req, res) => {
if (!req.session.userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
res.json({
userId: req.session.userId,
username: req.session.username,
balance: '$5,000.00'
// Don't expose API keys in responses
});
});Discovery
This vulnerability is discovered by setting a session ID before authentication (via URL parameter or cookie), then observing whether the application reuses the same session ID after successful login instead of generating a new one.
-
1. Test session ID from URL parameter
httpAction
Check if application accepts session IDs from query parameters
Request
GET https://app.example.com/?sessionid=ATTACKER_CONTROLLED_ID_12345Response
Status: 200Body:{ "headers": { "Set-Cookie": "session_id=ATTACKER_CONTROLLED_ID_12345; Path=/; HttpOnly" }, "note": "Application accepts and sets session ID from URL parameter - vulnerable to fixation!" }Artifacts
session_id_accepted_from_url cookie_set_from_parameter fixation_vector_confirmed -
2. Verify session persistence through authentication
httpAction
Set custom session ID before login and check if it persists after
Request
POST https://app.example.com/api/loginHeaders:Cookie: session_id=FIXED_SESSION_ABC123Content-Type: application/jsonBody:{ "email": "test@example.com", "password": "TestPassword123!" }Response
Status: 200Body:{ "message": "Login successful", "user": { "id": "user_456", "email": "test@example.com" }, "headers": { "Set-Cookie": "session_id=FIXED_SESSION_ABC123; Path=/; HttpOnly" }, "note": "Session ID NOT regenerated after authentication - fixation vulnerability confirmed!" }Artifacts
session_not_regenerated pre_auth_session_persisted authentication_fixation -
3. Test attacker session reuse
httpAction
Use the fixed session ID from different browser after victim authenticates
Request
GET https://app.example.com/api/account/profileHeaders:Cookie: session_id=FIXED_SESSION_ABC123Response
Status: 200Body:{ "user": { "id": "user_456", "email": "test@example.com", "name": "Test User", "role": "user", "api_key": "sk_test_abc123xyz789" }, "note": "Attacker successfully accesses victim's authenticated session using fixed ID!" }Artifacts
session_hijack_successful victim_account_accessed account_takeover
Exploit steps
An attacker exploits this by forcing a victim to use a session ID controlled by the attacker (via phishing link or XSS), then waiting for the victim to authenticate, after which the attacker can use the fixed session ID to access the victim's authenticated session.
-
1. Set attacker-controlled session ID
Craft phishing link with fixed session
httpAction
Create malicious link that sets attacker's session ID on victim
Request
GET https://app.example.com/login?sessionid=ATTACKER_SESSION_999Response
Status: 200Body:{ "html": "<html><head><title>Login</title></head>...", "headers": { "Set-Cookie": "session_id=ATTACKER_SESSION_999; Path=/" }, "note": "Victim's browser now has attacker-controlled session ID" }Artifacts
session_id_fixed victim_cookie_poisoned phishing_vector -
2. Monitor for victim authentication
Poll session status
httpAction
Check if victim has authenticated using the fixed session
Request
GET https://app.example.com/api/auth/statusHeaders:Cookie: session_id=ATTACKER_SESSION_999Response
Status: 200Body:{ "authenticated": true, "user_id": "user_789", "username": "victim@company.com", "login_time": "2024-01-15T14:23:45Z", "note": "Victim has logged in - session is now authenticated!" }Artifacts
authentication_detected session_activated victim_logged_in -
3. Hijack authenticated session
Access victim account with fixed session
httpAction
Use fixed session ID to fully access victim's account
Request
GET https://app.example.com/api/accountHeaders:Cookie: session_id=ATTACKER_SESSION_999Response
Status: 200Body:{ "account": { "id": "user_789", "email": "victim@company.com", "name": "Victim User", "subscription": "enterprise", "payment_method": { "type": "credit_card", "last4": "4242" }, "api_keys": [ "sk_live_xyz789..." ] }, "note": "Full account access via session fixation" }Artifacts
account_hijacked full_access_granted session_fixation_success -
4. Establish persistent access
Change account email for permanent takeover
httpAction
Modify victim's email to attacker-controlled address
Request
PUT https://app.example.com/api/account/emailHeaders:Cookie: session_id=ATTACKER_SESSION_999Content-Type: application/jsonBody:{ "email": "attacker@evil.com" }Response
Status: 200Body:{ "message": "Email updated successfully", "new_email": "attacker@evil.com", "verification_sent": true, "note": "Attacker can now reset password via attacker@evil.com - permanent takeover complete" }Artifacts
email_changed persistent_access_established account_takeover_complete
Specific Impact
The attacker obtains session-level access without stealing credentials. They can view and change personal data, and perform actions available to that user. Detection may be delayed because requests look like a valid session.
Remediation requires rotating session secrets, invalidating known sessions, notifying users, and deploying session regeneration on all auth paths.
Fix
Reject alternative session transports, and regenerate or reset sessions on authentication events. Audit middleware for any route that copies request inputs into session stores and add tests to ensure session regeneration is performed.
Detect This Vulnerability in Your Code
Sourcery automatically identifies session fixation vulnerabilities and many other security issues in your codebase.
Scan Your Code for Free