CWE-93: Improper Neutralization of CRLF Sequences ('CRLF Injection')
Overview
CRLF Injection occurs when untrusted input is written into an HTTP header, a log line, an email header or another line-based protocol field without being checked for carriage return (CR) and line feed (LF) characters. Those characters end a line, so the injected value becomes new lines that the receiving parser reads as structure rather than as data.
Relationship to Other CWEs
CWE-93 is a child of CWE-74 (Injection) and the parent of CWE-113 (HTTP Request/Response Splitting), which covers the narrower, HTTP-header-specific case of CRLF injection. If a finding is specifically about a value injected into an HTTP response header that causes response splitting, see CWE-113 for header-focused guidance and testing payloads. Use this page for the broader weakness, which also covers CRLF injection into logs, email headers, or other line-based protocol fields.
OWASP Classification
A05:2025 - Injection
Risk
High: A value carrying CR/LF lets an attacker add headers to the response, or close the header block early and supply a body of their own. That is HTTP response splitting; what follows from it is cache poisoning, a forged Set-Cookie that fixes the victim's session, or XSS from the injected body.
Remediation Steps
Core Principle: Never allow untrusted data to inject CRLF or protocol delimiters; validate/encode before generating structured text (headers, logs, protocols).
Trace the Data Path
Follow the untrusted value from where it enters to the header, log line or protocol field it ends up in:
- Source: Where untrusted data enters (user input, external file, database, network request)
- Sink: Any line-based protocol or text output that treats CR/LF as a delimiter - HTTP header setting functions (
response.headers[],setHeader(),addHeader()), log statements, or email/SMTP header construction - String concatenation: Look for untrusted data being directly inserted into header values
Reject Values Containing CR/LF (Primary Defense)
Refuse the value rather than repairing it. A newline arriving in a header value, a redirect target or an email field is either an attack or a bug in the caller, and neither is improved by silently rewriting it:
- Reject and return an error - a 400, a thrown exception, a fixed fallback destination - and log the attempt. The evidence of what was sent survives, which a strip destroys
- Check the value as it arrived, then use the value you checked. Stripping is what produces the second class of bug: removing the newline changes the string every later check runs against, so a
returnUrlof/+ CRLF +/evil.examplebecomes//evil.exampleafter the strip and passes a local-path test the original would have failed - Cover the percent-encoded forms (
%0d,%0a) in the same check where the value will be decoded again downstream - Where the value may legitimately contain newlines - a message body, a free-text comment - the answer is not to put it in a header. Encode it for the format it is going into instead: a JSON log field, an RFC 6266 filename parameter, an email body rather than an email header
- Prefer the framework's own header, cookie and redirect builders over assembling the line as text. They apply the encoding the field calls for, and reject what cannot be encoded
Use Framework Security Features
Build headers, cookies and redirects with the framework's own APIs rather than by concatenating strings. Typed header and response objects apply the encoding each field needs and refuse values that cannot be encoded, so the check in the previous step is done for you.
Validate Input for Protocol Fields (Defense in Depth)
The CR/LF check above is the fix. Validation on top of it narrows what an attacker can send in the first place:
- Enforce type, length and format checks on every untrusted header value, so an oversized or malformed value never reaches the header
- Validate against the pattern the field expects: a URL, a token, a content type
- Use an allowlist for enumerated values such as redirect destinations
- Reject values with characters outside the expected set rather than repairing them
Monitor and Test
Send the payloads below at the fixed endpoint and confirm each one is rejected rather than reflected into the response:
- CRLF injection:
%0d%0aSet-Cookie:admin=true,\r\nLocation:http://evil.com - Header splitting:
value\r\nX-Injected-Header:malicious - Response splitting:
\r\n\r\n<script>alert(1)</script> - Log header values and alert on CR/LF in them, so an attempt is visible after the fix as well as blocked by it
- Check that the input from the original finding is now refused, and that legitimate use of the same field, such as a normal redirect, still works
- Re-scan with the security scanner to confirm the issue is resolved
Common Vulnerable Patterns
The shape is the same on every stack: an untrusted value written into a header without being checked for CR or LF.
Unsanitized User Input in HTTP Header (Pseudocode)
Why this is vulnerable: The header block ends at the first blank line, so a value carrying \r\n lets the caller add headers and then a body of their own - which is what turns a single parameter into response splitting, cache poisoning or a forged Set-Cookie. The sketch is language-neutral because the flaw is in the protocol's framing rather than in any API: anything that writes an untrusted value into a header without rejecting control characters has it.
Language pages cover the part that differs, which is how much the platform already does. Most current HTTP servers reject CR and LF in field values, so the same code fails loudly on one stack and injects on another.
Secure Patterns
Rejecting a CR/LF-Bearing Header Value (Pseudocode)
# Safe: refuse the value, do not repair it
if contains_any(user_input, [CR, LF, '%0d', '%0a']):
return http_400('invalid redirect target')
# The value that was checked is the value that is used
response.headers['Location'] = user_input
Why this works:
- The header block ends at the first blank line, so a value with no CR or LF in it cannot add a header, close the block, or supply a body - which removes response splitting, the injected
Set-Cookieand the cache-poisoning primitive together - Nothing is rewritten between the check and the sink, so there is no repaired string that could pass a test the original would have failed
- The encoded forms are refused too, which matters wherever the value is decoded again by a proxy or by later code
- The request fails loudly, so the attempt is visible in logs rather than being silently normalized into a legitimate-looking value
- This is the CRLF half only. Where the value is a redirect target, an attacker-chosen destination is a separate weakness that survives every newline check - see CWE-601 for the local-path or host allowlist
Common Pitfalls
- Stripping only the literal
\r\npair: Removing the exact two-character sequence but leaving a bare\ror bare\nuntouched - many HTTP stacks and intermediaries treat a lone CR or LF as a line terminator on its own, so a strip that only matches the pair still leaves a working injection vector. - Sanitizing before decoding, not after: Stripping CRLF from the raw input while a later step in the request pipeline, or a proxy in front of it, URL-decodes the value again - an encoded
%0d%0apasses the sanitizer untouched and only becomes literal CRLF after the check has already run. - Using a general-purpose HTML or URL encoder as a stand-in: Reaching for an HTML-escaping function because it is already imported elsewhere in the codebase - HTML escaping neutralizes
<,>,&, and quotes, but does nothing to\ror\n, so the header injection still succeeds even though the value looks encoded. - Validating with an unanchored allowlist: Using a regex like
[a-zA-Z0-9]+with a "contains" match instead of requiring the entire value to match - a value with a legitimate prefix followed by injected CRLF and header content still passes the check.
Language-Specific Guidance
For framework-specific code examples, see:
- Python - Flask, Django, FastAPI examples validating before the header is set
- Java - Spring Boot, Servlets with header validation
- JavaScript/Node.js - Express, Koa, Next.js validating redirect targets and header values
- C# - ASP.NET Core with header security