Safeguard
Application Security•

CWE-93 CRLF Injection: Headers, Logs and How to Stop It

CRLF injection happens when a carriage return and line feed in user input are treated as structure. Real CVE examples and a fix you can copy.

Safeguard Security Team
4 min read

CWE-93, Improper Neutralization of CRLF Sequences, covers a simple mistake with wide reach. A product uses carriage return and line feed (CRLF) as a special element, for example to separate lines or records, and then fails to neutralize those sequences when they arrive in input.

Any text protocol that separates fields with line breaks is exposed: email headers, HTTP headers, log files, config formats, and line based command channels. If your code takes a user value and puts it on a line of its own output, you are in scope.

How it happens

Picture a contact form that sets a Reply-To header from a field the user fills in.

# Vulnerable: the value goes into a header line unchecked
def build_headers(reply_to):
    return "Subject: Contact form\r\nReply-To: " + reply_to + "\r\n"

# reply_to = "a@example.com\r\nBcc: victim@example.net"
# The output now has an extra Bcc header the developer never wrote.

The attacker typed a line break, and the receiving system treated everything after it as a new line with its own meaning. The input was supposed to be data. It became structure.

MITRE's observed examples show how varied the targets are:

  • CVE-2002-1771: CRLF injection enables a spam proxy by adding mail headers using an email address or name.
  • CVE-2002-1783: CRLF injection in API function arguments modifies headers for outgoing requests.
  • CVE-2004-1513: spoofed entries in a web server log file via carriage returns.
  • CVE-2006-4624: a chain that injects fake log entries with fake timestamps using CRLF injection.

Two of the four are about logs, and that is not a coincidence. Logs are line oriented, and few teams think of them as an output that needs encoding.

What an attacker gains

MITRE lists the consequence as integrity: the attacker can modify application data. In practice that means forged headers, added recipients, rewritten requests, and log lines that tell a story that never happened. The last one matters in an investigation, because a forged entry can cover real activity or point blame somewhere else.

How to find it

MITRE says static analysis can find some instances by tracing data flow. You can also test by hand.

  1. List every place where a user controlled value is written into a line based output: headers, log calls, mail fields, generated config.
  2. Submit a value containing an encoded CRLF, both as raw characters and as percent encoded %0d%0a, and inspect the output for an extra line.
  3. Read your own logs after a test. If you can create a line that looks like a genuine entry, so can an attacker.

How to fix it

MITRE gives two mitigations: avoid using CRLF as a special sequence, and appropriately filter or quote CRLF sequences in user controlled input.

Avoiding the sequence usually means choosing a format where the library owns the structure. Structured logging that writes each event as one encoded JSON object, for example, escapes line breaks inside values for you. For headers, use the mail or HTTP library's header API rather than assembling text.

Where you must write a line yourself, reject or strip the characters before they reach the output.

def safe_header_value(value):
    if "\r" in value or "\n" in value:
        raise ValueError("line breaks not allowed in header values")
    return value

def build_headers(reply_to):
    return "Subject: Contact form\r\nReply-To: " + safe_header_value(reply_to) + "\r\n"

Rejecting is better than silently removing, because a rejection shows up in your error rate and tells you someone is probing. Do the check at the point of output as well as at input, since data often reaches a sink through paths you did not plan for.

For logs, the same idea applies. Encode or replace line breaks in any external value before it is written, or use a logger that emits structured records.

Quick check

Why are log files a common target for CRLF injection?

Logs are line oriented. An attacker who can insert a line break inside a logged value can start a new line that looks like a genuine entry, as in the fake timestamp example in CVE-2006-4624.

Is it enough to validate the input once at the form?

No. Check at the output point too. Data can reach the same header or log call from other code paths, imports, or upstream services.

Reject or strip CR and LF?

Either meets MITRE's "filter or quote" advice, but rejecting makes probing visible. Pick one and apply it consistently.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.