Session Fixation
Description
Session Fixation occurs when authenticating a user, or otherwise establishing a new user session, without invalidating any existing session identifier. This gives an attacker the opportunity to steal authenticated sessions. The attacker first obtains a valid session identifier (through various means), then tricks a victim into authenticating using that session identifier. Since the application doesn't generate a new session ID upon authentication, the attacker now possesses a valid, authenticated session token and can impersonate the victim.
Risk
Session fixation enables attackers to hijack user sessions without needing to capture credentials or session tokens through network interception. CVE-2025-4644 in Payload CMS demonstrated a novel variant where JWT tokens remained valid after account deletion, allowing attackers to hijack sessions of newly created accounts that reused the same identifier. ABB ASPECT-Enterprise, NEXUS, and MATRIX Series products had high-severity session fixation vulnerabilities in 2025. Jenkins OpenId Connect Authentication plugin was also affected. Unlike session hijacking which requires intercepting an active session, session fixation allows pre-authentication attacks where the attacker sets up the session before the victim even logs in.
Solution
Always generate a new session identifier upon successful authentication. Invalidate the old session completely. Bind sessions to additional user attributes (IP address, user agent) as secondary verification. Implement session timeout policies. Use secure, HttpOnly, and SameSite cookie flags. Reject session identifiers from URL parameters—only accept them from cookies. Implement session rotation at privilege level changes. Monitor for session anomalies such as geographic impossibilities or concurrent sessions from different locations.
Common Consequences
| Impact | Details |
|---|---|
| Access Control | Scope: Session Hijacking Attackers gain full access to victim's authenticated session and can perform any action as the victim. |
| Authentication | Scope: Identity Theft The attacker effectively assumes the victim's identity for the duration of the session. |
| Non-Repudiation | Scope: Action Attribution Malicious actions are logged under the victim's account, potentially framing them for the attacker's activities. |
Example Code + Solution Code
Vulnerable Code
<?php
// VULNERABLE: Session ID not regenerated after login
session_start();
if ($_POST['username'] && $_POST['password']) {
if (authenticate($_POST['username'], $_POST['password'])) {
// Session ID remains the same - attacker's pre-set ID is now authenticated!
$_SESSION['authenticated'] = true;
$_SESSION['username'] = $_POST['username'];
header('Location: /dashboard');
}
}
?>
<!-- VULNERABLE: Session ID in URL -->
<a href="login.php?PHPSESSID=attacker_controlled_id">Login</a>
# VULNERABLE: Flask without session regeneration
from flask import Flask, session, redirect
app = Flask(__name__)
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
if authenticate(username, password):
# Session ID stays the same after authentication!
session['authenticated'] = True
session['user'] = username
return redirect('/dashboard')
return 'Login failed'
// VULNERABLE: Java Servlet without session invalidation
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
if (authenticate(username, password)) {
// Old session retained - vulnerable to fixation!
HttpSession session = request.getSession();
session.setAttribute("authenticated", true);
session.setAttribute("username", username);
response.sendRedirect("/dashboard");
}
}
}
Fixed Code
<?php
// SAFE: Regenerate session ID after authentication
session_start();
// Prevent session ID in URLs
ini_set('session.use_only_cookies', 1);
ini_set('session.use_trans_sid', 0);
if ($_POST['username'] && $_POST['password']) {
if (authenticate($_POST['username'], $_POST['password'])) {
// Destroy old session and create new one
session_regenerate_id(true); // true = delete old session
$_SESSION['authenticated'] = true;
$_SESSION['username'] = $_POST['username'];
$_SESSION['ip_address'] = $_SERVER['REMOTE_ADDR'];
$_SESSION['user_agent'] = $_SERVER['HTTP_USER_AGENT'];
$_SESSION['created_at'] = time();
header('Location: /dashboard');
exit;
}
}
// Validate session integrity on each request
function validate_session() {
if (!isset($_SESSION['authenticated'])) {
return false;
}
// Check session binding
if ($_SESSION['ip_address'] !== $_SERVER['REMOTE_ADDR']) {
session_destroy();
return false;
}
// Check session age
if (time() - $_SESSION['created_at'] > 3600) { // 1 hour
session_destroy();
return false;
}
return true;
}
?>
# SAFE: Flask with session regeneration
from flask import Flask, session, redirect, request
from flask_session import Session
import secrets
app = Flask(__name__)
app.config['SECRET_KEY'] = secrets.token_hex(32)
app.config['SESSION_COOKIE_HTTPONLY'] = True
app.config['SESSION_COOKIE_SECURE'] = True
app.config['SESSION_COOKIE_SAMESITE'] = 'Lax'
@app.route('/login', methods=['POST'])
def login_safe():
username = request.form['username']
password = request.form['password']
if authenticate(username, password):
# Clear existing session data
session.clear()
# Generate new session (Flask-Session handles ID regeneration)
session['authenticated'] = True
session['user'] = username
session['ip_address'] = request.remote_addr
session['user_agent'] = request.user_agent.string
session['created_at'] = time.time()
# For extra security, regenerate session ID explicitly
session.modified = True
return redirect('/dashboard')
return 'Login failed', 401
@app.before_request
def validate_session():
if 'authenticated' in session:
# Validate session binding
if session.get('ip_address') != request.remote_addr:
session.clear()
return redirect('/login')
# Rotate session ID periodically
if time.time() - session.get('created_at', 0) > 900: # 15 minutes
session['created_at'] = time.time()
session.modified = True
// SAFE: Java Servlet with session invalidation and regeneration
@WebServlet("/login")
public class SecureLoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
if (authenticate(username, password)) {
// Invalidate existing session completely
HttpSession oldSession = request.getSession(false);
if (oldSession != null) {
oldSession.invalidate();
}
// Create new session with fresh ID
HttpSession newSession = request.getSession(true);
newSession.setAttribute("authenticated", true);
newSession.setAttribute("username", username);
newSession.setAttribute("ip_address", request.getRemoteAddr());
newSession.setAttribute("user_agent", request.getHeader("User-Agent"));
newSession.setAttribute("created_at", System.currentTimeMillis());
// Set session timeout
newSession.setMaxInactiveInterval(3600); // 1 hour
response.sendRedirect("/dashboard");
} else {
response.sendRedirect("/login?error=invalid");
}
}
}
// Session validation filter
@WebFilter("/*")
public class SessionValidationFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpSession session = request.getSession(false);
if (session != null && session.getAttribute("authenticated") != null) {
String storedIP = (String) session.getAttribute("ip_address");
if (!request.getRemoteAddr().equals(storedIP)) {
session.invalidate();
((HttpServletResponse) res).sendRedirect("/login?error=session");
return;
}
}
chain.doFilter(req, res);
}
}
Exploited in the Wild
Payload CMS JWT Reuse (Payload CMS, 2025)
CVE-2025-4644 in Payload CMS before 3.44.0 allowed attackers to capture JWTs from accounts they created, delete those accounts, and then reuse the still-valid JWTs to hijack sessions of new users who were assigned the same identifier.
ABB Industrial Control Systems (ABB, 2025)
Session fixation vulnerabilities in ABB ASPECT-Enterprise, NEXUS, and MATRIX Series industrial control products allowed attackers to hijack operator sessions in critical infrastructure environments.
Jenkins OpenId Connect Plugin (Jenkins, 2024)
High-severity session fixation vulnerability in Jenkins OpenId Connect Authentication plugin enabled attackers to hijack authenticated sessions in CI/CD environments.
Tools to test/exploit
-
Burp Suite — test session management and token handling.
-
OWASP ZAP — automated session management testing.
-
Session Fixation Tester — specialized session fixation testing.
CVE Examples
-
CVE-2025-4644 — Payload CMS JWT session fixation.
-
CVE-2014-0050 — Apache Commons FileUpload session fixation.
-
CVE-2011-1472 — Joomla session fixation vulnerability.
References
-
MITRE. "CWE-384: Session Fixation." https://cwe.mitre.org/data/definitions/384.html
-
OWASP. "Session Fixation." https://owasp.org/www-community/attacks/Session_fixation