Session management flaws occur when applications fail to properly create, maintain, and destroy user sessions. In this demo, the vulnerable version uses a hardcoded secret key, lacks session security configurations, and doesn’t implement proper session lifecycle management, making it vulnerable to session hijacking, fixation, and replay attacks.
The vulnerable implementation has multiple session security issues:
app = Flask(__name__)app.secret_key = 'clave_super_secreta_123' # VULNERABLE: Hardcoded# Problems:# 1. Visible in source code (committed to git)# 2. Same across all environments# 3. Never rotated# 4. Easy to guess/discover
Result: Attacker receives victim’s session cookie and can impersonate them
2
Session Forgery with Known Secret
With the hardcoded secret key, forge admin sessions:
from itsdangerous import URLSafeTimedSerializer# Known secret from source codesecret_key = 'clave_super_secreta_123'# Create serializerserializer = URLSafeTimedSerializer(secret_key)# Forge admin sessionsession_data = { 'user_id': 1, 'username': 'admin', 'role': 'admin'}# Generate forged cookieforged_cookie = serializer.dumps(session_data)print(f"session={forged_cookie}")# Use this cookie in browser to become admin
def regenerate_session(new_data): """Safely regenerate session ID""" # Save data we want to keep temp_data = {k: v for k, v in session.items()} # Clear old session session.clear() # Update with new data temp_data.update(new_data) session.update(temp_data)@app.route('/login', methods=['POST'])def login(): if authenticate(username, password): # Regenerate session on login regenerate_session({ 'user_id': user.id, 'username': user.username, 'login_time': datetime.now() })@app.route('/elevate_privileges', methods=['POST'])def elevate_privileges(): if verify_admin_password(): # Regenerate session on privilege escalation regenerate_session({'is_elevated': True})
4. Server-Side Session Storage
Don’t store sensitive data in cookies:
from flask_session import Sessionimport redis# Configure server-side sessionsapp.config['SESSION_TYPE'] = 'redis'app.config['SESSION_REDIS'] = redis.from_url('redis://localhost:6379')app.config['SESSION_USE_SIGNER'] = Trueapp.config['SESSION_KEY_PREFIX'] = 'session:'Session(app)# Now session data stored in Redis, not cookie# Cookie only contains session ID
Benefits:
Cookie only contains random session ID
Can invalidate sessions server-side
Can track concurrent sessions
Can implement “logout all devices”
5. Implement Session Monitoring
Track and detect suspicious session activity:
@app.before_requestdef track_session_activity(): if 'user_id' in session: current_ip = request.remote_addr current_ua = request.headers.get('User-Agent') # First request in session - record fingerprint if 'session_ip' not in session: session['session_ip'] = current_ip session['session_ua'] = current_ua session['created_at'] = datetime.now() else: # Check for suspicious changes if session['session_ip'] != current_ip: # IP changed - possible hijacking logger.warning(f"Session IP changed: {session['session_ip']} -> {current_ip}") # Optional: Force re-authentication session.clear() flash('Session expired due to security check', 'warning') return redirect('/login') if session['session_ua'] != current_ua: # User agent changed - possible hijacking logger.warning(f"Session UA changed") # Update last activity session['last_activity'] = datetime.now()
6. Absolute and Idle Timeouts
Implement both types of session expiration:
from datetime import datetime, timedelta@app.before_requestdef check_session_timeout(): if 'user_id' not in session: return now = datetime.now() # Absolute timeout: Session must end after X hours regardless if 'created_at' in session: absolute_timeout = timedelta(hours=8) if now - session['created_at'] > absolute_timeout: session.clear() flash('Sesión expirada. Por favor inicie sesión nuevamente.', 'warning') return redirect('/login') # Idle timeout: Session expires after X minutes of inactivity if 'last_activity' in session: idle_timeout = timedelta(minutes=30) if now - session['last_activity'] > idle_timeout: session.clear() flash('Sesión expirada por inactividad.', 'warning') return redirect('/login') # Update last activity session['last_activity'] = now
# 1. Test cookie flagscurl -I https://target.com/login# Look for: HttpOnly; Secure; SameSite=Lax# 2. Test session fixation# Get pre-login sessioncurl -c cookies.txt https://target.com/login# Login with this sessioncurl -b cookies.txt -d "user=admin&pass=admin123" https://target.com/login# Check if session ID changed# 3. Test session timeout# Login and get sessioncurl -c cookies.txt -d "user=admin&pass=admin123" https://target.com/login# Wait 31 minutessleep 1860# Try accessing protected pagecurl -b cookies.txt https://target.com/dashboard# Should redirect to login# 4. Test concurrent sessions# Login multiple times from different locations# Check if old sessions are invalidated
Sequencer - Analyze session token randomness:
Capture 10,000+ session tokens
Burp analyzes entropy and patterns
Should show “excellent” randomness
Repeater - Test session timeout:
Send authenticated request
Wait past timeout period
Replay request
Should receive 401/redirect
Intruder - Test session fixation:
Set pre-login session cookie
Login with fixed session
Check if session ID changes
import requestsimport timedef test_session_security(base_url): results = {} # Test 1: Check cookie flags r = requests.get(f"{base_url}/login") cookie = r.cookies.get('session') if cookie: # Check flags in Set-Cookie header set_cookie = r.headers.get('Set-Cookie', '') results['httponly'] = 'HttpOnly' in set_cookie results['secure'] = 'Secure' in set_cookie results['samesite'] = 'SameSite' in set_cookie # Test 2: Session fixation session = requests.Session() # Get pre-login session r1 = session.get(f"{base_url}/login") pre_login_sid = session.cookies.get('session') # Login r2 = session.post(f"{base_url}/login", data={'username': 'test', 'password': 'test'}) post_login_sid = session.cookies.get('session') results['session_regeneration'] = (pre_login_sid != post_login_sid) # Test 3: Session timeout session2 = requests.Session() session2.post(f"{base_url}/login", data={'username': 'test', 'password': 'test'}) # Wait 31 minutes (in real test) # time.sleep(1860) r3 = session2.get(f"{base_url}/dashboard") results['timeout_enforced'] = (r3.status_code in [401, 403] or '/login' in r3.url) return results# Run testresults = test_session_security('https://auth-vulnerable.onrender.com')print(results)