SC: HTTP Header Injection / Response Splitting
Purpose
Detects HTTP header injection vulnerabilities where user-controlled input is placed into HTTP response headers without CRLF character sanitization, enabling response splitting, cookie injection, cache poisoning, and XSS via injected headers. Also covers Host header injection used in password reset poisoning.
Activation
Called by sc-orchestrator during Phase 2 when HTTP response handling is detected.
Phase 1: Discovery
File Patterns to Search
**/*.py, **/*.js, **/*.ts, **/*.go, **/*.php, **/*.java, **/*.cs,
**/controllers/*, **/routes/*, **/handlers/*, **/middleware/*,
**/*redirect*, **/*header*, **/*cookie*, **/*response*
Keyword Patterns to Search
# Setting response headers with potential user input
"res.setHeader(", "res.header(", "response.addHeader(",
"response.setHeader(", "w.Header().Set(", "header(",
"Response.Headers.Add(", "Response.Cookies.Append(",
"Set-Cookie", "Location:", "Content-Disposition",
# Redirect with user input
"res.redirect(", "response.sendRedirect(", "redirect(",
"HttpResponse(.*status=302", "http.Redirect(",
"Response.Redirect("
# Host header usage
"request.getHeader(\"Host\")", "req.headers.host", "req.Host",
"$_SERVER['HTTP_HOST']", "request.META['HTTP_HOST']"
Vulnerability Pattern: CRLF in Headers
// VULNERABLE: User input in Set-Cookie header
app.get('/lang', (req, res) => {
res.setHeader('Set-Cookie', `lang=${req.query.lang}`);
// Attack: ?lang=en%0d%0aSet-Cookie:%20admin=true
// Injects additional header
});
// SAFE: Validate/encode header values
app.get('/lang', (req, res) => {
const lang = req.query.lang.replace(/[\r\n]/g, '');
res.setHeader('Set-Cookie', `lang=${encodeURIComponent(lang)}`);
});
Host Header Injection
# VULNERABLE: Using Host header for password reset URL
def password_reset(request):
host = request.META['HTTP_HOST'] # Attacker-controlled!
reset_url = f"https://{host}/reset?token={token}"
send_email(user.email, f"Reset here: {reset_url}")
# SAFE: Use configured server name
def password_reset(request):
reset_url = f"https://{settings.ALLOWED_HOSTS[0]}/reset?token={token}"
Content-Disposition Injection
// VULNERABLE: User filename in Content-Disposition
func download(w http.ResponseWriter, r *http.Request) {
filename := r.URL.Query().Get("file")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
// Attack: ?file=test%0d%0aContent-Type:%20text/html%0d%0a%0d%0a<script>alert(1)</script>
}
// SAFE: Sanitize filename
filename = strings.Map(func(r rune) rune {
if r == '\r' || r == '\n' || r == '"' { return -1 }
return r
}, filename)
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
Phase 2: Verification
Modern Framework Protections
Most modern HTTP libraries and frameworks reject CRLF characters in header values:
- Node.js (v14+):
http.ServerResponsethrows on CRLF in header values - Go net/http: Rejects headers containing
\ror\n - Python/Django: WSGI servers typically strip CRLF
- Java Servlet (Tomcat 7+): Rejects CRLF in response headers
- ASP.NET Core: Rejects CRLF in header values
However: Some configurations, proxies, or older versions may not enforce this.
Sanitization Check
- Are
\rand\nstripped from user input before header insertion? - Is the value URL-encoded or otherwise escaped?
- Does the HTTP framework/server reject CRLF in headers?
Severity Classification
- Critical: Response splitting enabling XSS or cache poisoning on shared caches
- High: Host header injection in password reset leading to token theft
- Medium: Cookie injection via CRLF, or header injection in less-sensitive headers
- Low: Header injection in modern framework that auto-rejects CRLF (informational)
Output Format
Finding: HDR-{NNN}
- Title: {CRLF Header Injection | Host Header Injection | Response Splitting} in {endpoint}
- Severity: Critical | High | Medium | Low
- Confidence: 0-100
- File: file/path:line
- Vulnerability Type: CWE-113 (HTTP Response Splitting)
- Description: User input from {source} is placed into HTTP {header name} without CRLF sanitization.
- Proof of Concept: Injecting
%0d%0asequences could add arbitrary headers to the response. - Impact: {Cache poisoning / XSS / session fixation / password reset poisoning}
- Remediation: Strip CR/LF characters from user input. Use framework-provided safe header methods. For Host header: use server configuration instead of request Host header.
- References: https://cwe.mitre.org/data/definitions/113.html
Common False Positives
- Modern frameworks — Node.js 14+, Go, ASP.NET Core, Tomcat 7+ reject CRLF automatically
- Static header values — headers set from constants or configuration, not user input
- Content-Type headers — typically set by framework based on response type, not user input
- Framework redirect methods —
res.redirect()in Express URL-encodes the location - Reverse proxy normalization — nginx/Apache strip malformed headers before they reach the app