Log injection can lead to log forging and XSS. Learn how to sanitize user input in Node.js and PHP to secure your logs against CWE-117 vulnerabilities.
Last month, during a routine audit of our production logs, I noticed a series of "successful" login entries that didn't match our actual authentication timestamps. It turned out an attacker was injecting newline characters into the username field, effectively forging log entries to mask their activity and confuse our monitoring tools.
Log injection (often tracked as CWE-117) occurs when an application includes untrusted data in a log file without proper validation or sanitization. If an attacker can inject control characters—specifically carriage returns (\r) and line feeds (\n)—they can write fake log entries.
If your log aggregator or dashboard renders these logs in a web browser, that injected data can also execute scripts, leading to log-based XSS. It's a subtle but dangerous vector that turns your diagnostic tools into an attack surface.
My first instinct was to just strip out \n and \r. We wrote a quick regex utility in Node.js to clean every incoming request body before logging.
It worked for a few days, but then we ran into issues with multiline error stack traces. By aggressively stripping all newlines, we destroyed the readability of legitimate error logs. We were flying blind when production started throwing 500s because the stack traces looked like a single, massive, unreadable line.
We needed a more nuanced approach than just "delete everything."
In Node.js, libraries like winston or pino are industry standards. However, they aren't magic—they don't automatically sanitize user-provided strings. You must handle this before the data hits the logger.
Here is how I recommend handling it:
JAVASCRIPT// A simple sanitizer for log data function sanitizeLog(input) { if (typeof input !== CE9178">'string') return input; // Replace newlines with a safe representation return input.replace(/[\r\n]+/g, CE9178">' [newline] '); } // Usage const username = req.body.username; logger.info(CE9178">`User login attempt: ${sanitizeLog(username)}`);
By replacing the control characters with a literal string like [newline], you preserve the intent of the log entry without allowing the attacker to break the log format.
PHP presents a different set of challenges, especially with legacy codebases. If you're using error_log() or a PSR-3 compliant logger like monolog, you should apply a similar transformation layer.
If you're dealing with Insecure Deserialization: How to Secure Object Hydration in Node.js and PHP, you're already aware that untrusted input is never safe. Logging is just another sink for that input.
PHP#6A9955">// PHP sanitization example function sanitizeLog($data) { return str_replace(["\r", "\n"], ['\\r', '\\n'], $data); } $username = $_POST['username']; error_log("User login attempt: " . sanitizeLog($username));
This approach encodes the characters rather than stripping them entirely. It keeps the log entry on a single line while making it obvious to any human reviewer that the input was tampered with.
Log forging isn't just about confusing developers. If your logs feed into a SIEM (Security Information and Event Management) system, an attacker can inject fake "success" messages or "system maintenance" alerts. This can trigger automated scripts or distract the security team while the attacker performs unauthorized actions elsewhere.
If you don't secure your logs, you’re effectively handing over the keys to your audit trail. When you're managing complex systems, you also need to worry about Preventing Uncontrolled Resource Consumption in Node.js and PHP Apps, but log injection is often overlooked because developers assume logs are "read-only" files.
req.body, $_POST, or headers as potentially malicious.Does JSON logging prevent log injection?
Yes, mostly. Because JSON formatters escape control characters (like \n becomes \\n) when serializing an object, the injected characters are treated as literal text rather than structure-breaking newlines.
Should I use a library for this? Yes. If you're using a logging framework, check if it has a built-in "sanitizer" or "masking" plugin. Don't reinvent the wheel if you don't have to.
Is it really XSS if it happens in the log viewer?
Yes, it's called "Stored XSS in Log Management." If your log dashboard renders HTML, an attacker can inject <script> tags via the log file. Always ensure your log viewer treats log output as plain text.
I'm still not entirely convinced that sanitizing at the application level is enough. In a perfect world, our log aggregators would be hardened enough to ignore control characters entirely. Until then, I'll keep scrubbing my logs. It’s a small bit of boilerplate that saves a massive headache during an incident response.
Learn to prevent integer overflow and underflow in Node.js and PHP. Discover how to handle large numbers securely and avoid silent data corruption today.
Read moreMass assignment vulnerabilities happen when apps blindly map user input to database models. Learn to stop them using explicit DTO allow-lists in your code.