Description
Log injection occurs when untrusted input is written directly to logs. An attacker may insert newlines (\n, \r), tabs, ANSI escapes, or HTML to split records or make text appear to be a separate event. Forged messages may hide real events or mislead operators. Logs rendered as HTML may also expose a web viewer to XSS.
Potential impact
- Newlines and control characters may forge records or change a log entry's apparent meaning.
- Broken records may interfere with line-based SIEM parsing and alerting.
- Forged logs may delay investigation and incident response.
- HTML-based log viewers may execute injected scripts.
- Corrupted audit trails may undermine compliance and forensic analysis.
Remediation
- Remove or escape
\n,\r, and other relevant control characters for the log format, for example withvalue.replace(/[\r\n]/g, ''). - Keep the format string fixed, as in
console.log('%s', value).%sdoes not itself remove newlines or control characters; handle them separately. - Display web logs as text, or encode them for the relevant HTML context.
- Limit input length, for example to 256 characters, to reduce log flooding.
- Centralize normalization in a helper such as
sanitizeForLog. - Do not log passwords, tokens, or other secrets.
Examples
Before
javascript
const express = require('express');
const app = express();
app.get('/audit', (req, res) => {
const agent = req.headers['user-agent']; // Untrusted input
const q = req.query.q; // Untrusted input
// BAD: insert input directly into the log template
console.info(`[AUDIT] agent=${agent} query=${q}`);
res.send('ok');
});
app.listen(3001);
After
javascript
const express = require('express');
const app = express();
function sanitizeForLog(v) {
if (typeof v !== 'string') return v;
// Remove CR/LF and Unicode line separators; limit length
const noCtl = v.replace(/[\r\n]/g, '').replace(/[\u2028\u2029]/g, '');
return noCtl.length > 256 ? noCtl.slice(0, 256) + '…' : noCtl;
}
app.get('/audit', (req, res) => {
const rawAgent = req.headers['user-agent'];
const rawQ = req.query.q;
const agent = sanitizeForLog(String(rawAgent || 'unknown'));
const q = sanitizeForLog(String(rawQ || ''));
// GOOD: use a fixed format with normalized values
console.info('[AUDIT] agent=%s query=%s', agent, q);
res.send('ok');
});
app.listen(3001);
Explanation:
- Before: Input (
agent,q) is inserted directly into the message. Newlines, HTML, or ANSI escapes may distort records and interfere with analysis. - After:
sanitizeForLogremoves CR/LF and Unicode line separators and limits length. It does not remove every control character or HTML. Together with a fixed format, this reduces line-based forgery. Terminal ANSI handling and web-viewer output encoding remain separate requirements.