// VULNERABLE: Complete session fixation vulnerability demonstration
const express = require('express');
const session = require('express-session');
const app = express();
// VULNERABLE: Basic session configuration without security measures
app.use(session({
secret: 'weak-secret',
resave: false,
saveUninitialized: true,
name: 'sessionid', // Predictable name
cookie: {
secure: false, // Allows HTTP transmission
httpOnly: false, // Accessible via JavaScript
maxAge: null, // No expiration
sameSite: false // No CSRF protection
}
}));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// VULNERABLE: Accept session ID from multiple sources
app.use((req, res, next) => {
// VULNERABLE: Allow session ID from URL parameter
if (req.query.sessionid) {
req.sessionID = req.query.sessionid;
console.log('Session ID set from URL:', req.sessionID);
}
// VULNERABLE: Allow session ID from custom header
if (req.headers['x-session-id']) {
req.sessionID = req.headers['x-session-id'];
console.log('Session ID set from header:', req.sessionID);
}
next();
});
// VULNERABLE: Pre-login session setup that attackers can abuse
app.get('/initialize-session', (req, res) => {
// VULNERABLE: Creates session before authentication
if (!req.session.initialized) {
req.session.initialized = true;
req.session.guestId = Math.random().toString(36);
req.session.preferences = {};
}
// VULNERABLE: Exposes session ID to client
res.json({
message: 'Session initialized',
sessionId: req.sessionID,
guestId: req.session.guestId
});
});
// VULNERABLE: Login endpoint without session regeneration
app.post('/login', async (req, res) => {
const { username, password } = req.body;
try {
// Mock authentication
const user = await authenticateUser(username, password);
if (user) {
// VULNERABLE: No session regeneration - critical flaw!
req.session.userId = user.id;
req.session.username = user.username;
req.session.role = user.role;
req.session.isAuthenticated = true;
req.session.loginTime = new Date();
// VULNERABLE: Preserves pre-login session data
// This allows attackers to maintain their fixed session
console.log('User logged in with existing session ID:', req.sessionID);
res.json({
success: true,
message: 'Login successful',
sessionId: req.sessionID, // Exposing session ID
user: {
id: user.id,
username: user.username,
role: user.role
}
});
} else {
res.status(401).json({ error: 'Invalid credentials' });
}
} catch (error) {
res.status(500).json({ error: 'Server error' });
}
});
// VULNERABLE: Authentication middleware without proper validation
function requireAuth(req, res, next) {
if (req.session && req.session.isAuthenticated) {
// VULNERABLE: No additional session security checks
// No IP validation, no session age checks, etc.
next();
} else {
res.status(401).json({ error: 'Authentication required' });
}
}
// VULNERABLE: Session info endpoint that exposes too much
app.get('/session-info', (req, res) => {
res.json({
sessionId: req.sessionID,
sessionData: req.session,
cookies: req.headers.cookie,
headers: req.headers
});
});
// VULNERABLE: Protected endpoint using weak session validation
app.get('/profile', requireAuth, (req, res) => {
res.json({
userId: req.session.userId,
username: req.session.username,
role: req.session.role,
sessionId: req.sessionID, // Still exposing session ID
loginTime: req.session.loginTime
});
});
// VULNERABLE: Session transfer endpoint (extremely dangerous)
app.post('/transfer-session', (req, res) => {
const { fromSessionId, toSessionId } = req.body;
// VULNERABLE: Allows arbitrary session transfer
if (req.sessionID === fromSessionId) {
req.sessionID = toSessionId;
res.json({
message: 'Session transferred',
newSessionId: toSessionId
});
} else {
res.status(400).json({ error: 'Invalid session transfer' });
}
});
// VULNERABLE: Logout without proper session cleanup
app.post('/logout', requireAuth, (req, res) => {
// VULNERABLE: Only marks as not authenticated, doesn't destroy session
req.session.isAuthenticated = false;
// Session data remains accessible
res.json({ message: 'Logged out' });
});
// VULNERABLE: Admin endpoint to view all sessions (debugging feature)
app.get('/admin/sessions', (req, res) => {
// VULNERABLE: No authentication, exposes all session data
const sessions = [];
// In a real app, this would iterate through session store
// This exposes all user sessions to anyone
res.json({ sessions });
});
// Attack demonstration endpoints
app.get('/attack-demo', (req, res) => {
res.send(`
Session Fixation Attack Demo
Attacker can craft malicious links like:
After victim logs in with the fixed session, attacker can access:
`);
});
app.listen(3000, () => {
console.log('Vulnerable session fixation demo running on port 3000');
console.log('Visit /attack-demo to see attack examples');
});
// SECURE: Comprehensive session fixation protection implementation
const express = require('express');
const session = require('express-session');
const RedisStore = require('connect-redis')(session);
const redis = require('redis');
const crypto = require('crypto');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const winston = require('winston');
const app = express();
// Environment validation
if (!process.env.SESSION_SECRET || process.env.SESSION_SECRET.length < 32) {
console.error('SESSION_SECRET must be at least 32 characters long');
process.exit(1);
}
// Redis client for secure session storage
const redisClient = redis.createClient({
url: process.env.REDIS_URL || 'redis://localhost:6379',
password: process.env.REDIS_PASSWORD
});
redisClient.on('error', (err) => {
console.error('Redis Client Error', err);
});
// Security middleware
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
connectSrc: ["'self'"]
}
}
}));
// Rate limiting
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // 5 login attempts
message: { error: 'Too many login attempts' },
standardHeaders: true
});
const sessionLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 50, // 50 session operations
message: { error: 'Too many session requests' }
});
// Secure logger
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'security.log' }),
new winston.transports.Console()
]
});
// SECURE: Session configuration with maximum security
app.use(session({
store: new RedisStore({ client: redisClient }),
secret: process.env.SESSION_SECRET,
name: process.env.SESSION_COOKIE_NAME || 'secure_app_session',
resave: false,
saveUninitialized: false, // SECURE: Don't create sessions for unauthenticated users
rolling: true, // Reset expiration on activity
cookie: {
secure: process.env.NODE_ENV === 'production', // HTTPS only in production
httpOnly: true, // SECURE: Prevent JavaScript access
maxAge: 30 * 60 * 1000, // 30 minutes
sameSite: 'strict' // SECURE: CSRF protection
},
// SECURE: Cryptographically secure session ID generation
genid: () => {
return crypto.randomBytes(32).toString('hex');
}
}));
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
// SECURE: Security middleware to prevent session fixation
app.use((req, res, next) => {
// SECURE: Reject session IDs from URL parameters
if (req.query.sessionid || req.query.session_id) {
logger.warn('Session fixation attempt detected', {
ip: req.ip,
userAgent: req.get('User-Agent'),
query: req.query,
timestamp: new Date().toISOString()
});
return res.status(400).json({
error: 'Session ID cannot be provided via URL',
code: 'SESSION_FIXATION_BLOCKED'
});
}
// SECURE: Reject session IDs from headers (except official cookie)
if (req.headers['x-session-id'] || req.headers['session-id']) {
logger.warn('Session fixation attempt via headers', {
ip: req.ip,
userAgent: req.get('User-Agent'),
headers: {
'x-session-id': req.headers['x-session-id'],
'session-id': req.headers['session-id']
},
timestamp: new Date().toISOString()
});
return res.status(400).json({
error: 'Session ID cannot be provided via headers',
code: 'SESSION_FIXATION_BLOCKED'
});
}
next();
});
// SECURE: Session validation and security checks
class SecureSessionManager {
constructor() {
this.maxConcurrentSessions = 5;
this.sessionTimeout = 30 * 60 * 1000; // 30 minutes
}
// SECURE: Proper login with session regeneration
async secureLogin(req, res) {
const { username, password } = req.body;
try {
const user = await this.authenticateUser(username, password);
if (!user) {
logger.warn('Failed login attempt', {
username,
ip: req.ip,
userAgent: req.get('User-Agent')
});
return res.status(401).json({ error: 'Invalid credentials' });
}
const oldSessionId = req.sessionID;
// SECURE: Regenerate session ID after successful authentication
req.session.regenerate((err) => {
if (err) {
logger.error('Session regeneration failed', {
error: err.message,
userId: user.id,
ip: req.ip
});
return res.status(500).json({ error: 'Session creation failed' });
}
// Store user data in new session
req.session.userId = user.id;
req.session.username = user.username;
req.session.role = user.role;
req.session.isAuthenticated = true;
// SECURE: Add security tracking data
req.session.loginTime = new Date().toISOString();
req.session.ipAddress = this.getClientIp(req);
req.session.userAgent = req.get('User-Agent');
req.session.securityLevel = this.calculateSecurityLevel(user);
req.session.save((err) => {
if (err) {
logger.error('Session save failed', {
error: err.message,
userId: user.id
});
return res.status(500).json({ error: 'Session save failed' });
}
// SECURE: Log successful login with session regeneration
logger.info('Secure login successful', {
userId: user.id,
username: user.username,
oldSessionId: oldSessionId,
newSessionId: req.sessionID,
ipAddress: req.session.ipAddress,
userAgent: req.session.userAgent
});
res.json({
success: true,
message: 'Login successful',
user: {
id: user.id,
username: user.username,
role: user.role
},
expiresAt: new Date(Date.now() + this.sessionTimeout).toISOString()
// SECURE: Never expose session ID in response
});
});
});
} catch (error) {
logger.error('Login error', {
error: error.message,
ip: req.ip
});
res.status(500).json({ error: 'Internal server error' });
}
}
// SECURE: Comprehensive session validation
validateSession(req, res, next) {
if (!req.session || !req.session.isAuthenticated) {
return res.status(401).json({
error: 'Authentication required',
code: 'NOT_AUTHENTICATED'
});
}
// SECURE: Validate session integrity
const currentIp = this.getClientIp(req);
const currentUserAgent = req.get('User-Agent');
// IP address validation for high-security sessions
if (req.session.securityLevel === 'high' &&
req.session.ipAddress !== currentIp) {
logger.warn('Session IP mismatch detected', {
userId: req.session.userId,
sessionId: req.sessionID,
originalIp: req.session.ipAddress,
currentIp: currentIp
});
this.destroySession(req, res, 'IP address mismatch detected');
return;
}
// User agent change detection
if (req.session.userAgent !== currentUserAgent) {
logger.warn('Session user agent change', {
userId: req.session.userId,
sessionId: req.sessionID,
originalUserAgent: req.session.userAgent,
currentUserAgent: currentUserAgent
});
}
// Session age validation
const sessionAge = Date.now() - new Date(req.session.loginTime).getTime();
if (sessionAge > this.sessionTimeout) {
this.destroySession(req, res, 'Session expired due to age');
return;
}
// Update last activity
req.session.lastActivity = new Date().toISOString();
next();
}
// SECURE: Complete session destruction
destroySession(req, res, reason = 'Logout') {
const userId = req.session?.userId;
const sessionId = req.sessionID;
req.session.destroy((err) => {
if (err) {
logger.error('Session destruction failed', {
error: err.message,
sessionId,
userId
});
return res.status(500).json({ error: 'Logout failed' });
}
// SECURE: Clear session cookie
res.clearCookie(process.env.SESSION_COOKIE_NAME || 'secure_app_session', {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
sameSite: 'strict'
});
logger.info('Session destroyed', {
userId,
sessionId,
reason
});
res.json({
message: 'Session terminated successfully',
reason: reason
});
});
}
getClientIp(req) {
return req.ip ||
req.headers['x-forwarded-for']?.split(',')[0] ||
req.connection.remoteAddress;
}
calculateSecurityLevel(user) {
return user.role === 'admin' ? 'high' : 'normal';
}
async authenticateUser(username, password) {
// Implement secure authentication logic
// This is a placeholder for actual authentication
return { id: 1, username, role: 'user' };
}
}
const sessionManager = new SecureSessionManager();
// SECURE: No pre-login session initialization
// Sessions are only created after successful authentication
// SECURE: Login endpoint with proper protection
app.post('/login', authLimiter, (req, res) => {
sessionManager.secureLogin(req, res);
});
// SECURE: Protected routes with comprehensive validation
app.get('/profile', sessionManager.validateSession.bind(sessionManager), (req, res) => {
res.json({
user: {
id: req.session.userId,
username: req.session.username,
role: req.session.role
},
sessionInfo: {
loginTime: req.session.loginTime,
lastActivity: req.session.lastActivity,
securityLevel: req.session.securityLevel
}
// SECURE: Never expose session ID
});
});
// SECURE: Session information with minimal exposure
app.get('/session-status', sessionManager.validateSession.bind(sessionManager), (req, res) => {
const sessionAge = Date.now() - new Date(req.session.loginTime).getTime();
const timeRemaining = sessionManager.sessionTimeout - sessionAge;
res.json({
isAuthenticated: true,
timeRemaining: Math.max(0, timeRemaining),
securityLevel: req.session.securityLevel,
lastActivity: req.session.lastActivity
// SECURE: No sensitive session data exposed
});
});
// SECURE: Proper logout with complete cleanup
app.post('/logout', sessionManager.validateSession.bind(sessionManager), (req, res) => {
sessionManager.destroySession(req, res, 'User logout');
});
// SECURE: Admin endpoints with proper authentication
app.get('/admin/sessions',
sessionManager.validateSession.bind(sessionManager),
(req, res) => {
// Only allow admin users
if (req.session.role !== 'admin') {
return res.status(403).json({ error: 'Admin access required' });
}
// Return only non-sensitive session statistics
res.json({
message: 'Session statistics would be available here',
note: 'Individual session data is never exposed for security'
});
}
);
// SECURE: Security demonstration
app.get('/security-info', (req, res) => {
res.json({
security_features: [
'Session ID regeneration after authentication',
'Cryptographically secure session ID generation',
'HttpOnly and Secure cookie flags',
'SameSite CSRF protection',
'IP address validation for high-security sessions',
'Session timeout enforcement',
'Comprehensive security logging',
'Protection against session fixation attacks'
],
session_fixation_protection: [
'Sessions only created after authentication',
'URL-based session ID injection blocked',
'Header-based session ID injection blocked',
'Session regeneration on every login',
'No session data preserved from pre-auth state'
]
});
});
// Error handling
app.use((error, req, res, next) => {
logger.error('Unhandled error', {
error: error.message,
stack: error.stack,
url: req.url,
ip: req.ip
});
res.status(500).json({ error: 'Internal server error' });
});
// Start server
app.listen(3000, () => {
logger.info('Secure session management server started', {
port: 3000,
environment: process.env.NODE_ENV || 'development'
});
});