CWE-117: Log Injection / Log Forging - C# / ASP.NET
Overview
Log Injection occurs when untrusted data is written to log files without encoding, allowing attackers to forge log entries, hide malicious activity, or inject scripts into log viewing applications.
Primary Defence: Use structured logging with JSON/ECS output (Microsoft.Extensions.Logging + JSON formatter, or Serilog with JSON/ECS sinks) so control characters are encoded within fields and cannot forge log entries. For manual encoding, encode (recommended: \n → \\n, \r → \\r) rather than remove control characters to preserve forensic evidence. Alternatively, remove all control characters using regex pattern [\x00-\x1F\x7F\u0085\u2028\u2029] to protect against both ASCII and Unicode newline injection (but this loses attack visibility).
OWASP Alignment
This guidance implements the OWASP Logging Cheat Sheet recommendation (Event Collection section):
"Perform sanitization on all event data to prevent log injection attacks e.g. carriage return (CR), line feed (LF) and delimiter characters"
See also: OWASP Log Injection Attack demonstrating newline-based log forging.
Enhancement: The encoding patterns in this guidance exceed OWASP's basic examples by covering:
- Standard control characters:
\r(CR),\n(LF),\t(tab) - as OWASP recommends - Unicode newlines:
\u0085(NEL),\u2028(Line Separator),\u2029(Paragraph Separator) - not mentioned in OWASP examples - All ASCII control characters:
\x00-\x1F,\x7F(DEL)
Why Unicode matters: Attackers can bypass naive CR/LF removal (e.g., .Replace("\r", "").Replace("\n", "")) by using Unicode equivalents like \u2028, which also create new log lines but aren't caught by basic string replacement. The regex pattern [\x00-\x1F\x7F\u0085\u2028\u2029] provides comprehensive protection against all control character injection vectors.
Common Vulnerable Patterns
String Concatenation Allows Log Forgery
// VULNERABLE - String concatenation allows log forgery
logger.LogInformation("User " + username + " logged in");
// Attack example:
// username = "admin\r\nUser hacker logged in as admin"
// Result: Two separate log lines, second appears legitimate:
// User admin
// User hacker logged in as admin
Why this is vulnerable:
- User input is concatenated directly into the log message.
- CR/LF sequences (
\r,\n) can split a single log entry into multiple lines. - Attackers can forge entries that look legitimate or hide activity.
- Text-based log layouts write injected lines as separate records.
String Interpolation Without Sanitization
// VULNERABLE - String interpolation without encoding
logger.LogWarning($"Failed login for {username} from {ipAddress}");
// Attack example:
// username = "test\r\nSUCCESS: admin login from 127.0.0.1"
// Result: Creates fake success entry after failed login attempt
Why this is vulnerable:
- String interpolation does not encode control characters.
- Newlines injected into
usernameoripAddresscreate fake log records. - Forged entries can mask real failures with fake successes.
- Log parsers may treat injected lines as valid events.
Unicode Newline Injection (Bypasses Basic Sanitization)
// VULNERABLE - Only removes ASCII newlines, not Unicode
string sanitized = userInput.Replace("\r", "").Replace("\n", "");
logger.LogInformation($"Input: {sanitized}");
// Attack example:
// userInput = "test\u2028CRITICAL: Database breach detected\u2029Emergency shutdown initiated"
// Result: Creates fake critical alerts using Unicode Line Separator (U+2028) and Paragraph Separator (U+2029)
// Log output:
// Input: test
// CRITICAL: Database breach detected
// Emergency shutdown initiated
Why this is vulnerable:
- ASCII-only replacement leaves Unicode newlines intact.
\u0085,\u2028, and\u2029are treated as line breaks by many tools.- Attackers can bypass basic CR/LF stripping to forge entries.
- Developers often test only with
\nand\r, missing these variants.
Secure Patterns
Use Structured Logging with Parameter Binding (Pair with JSON/ECS output)
// SECURE - Structured logging (automatic encoding with JSON/ECS output)
public class UserService
{
private readonly ILogger<UserService> _logger;
public void LogLogin(string username, string ipAddress)
{
_logger.LogInformation(
"User login successful. Username: {Username}, IP: {IPAddress}",
username,
ipAddress
);
}
}
Why this works:
- Structured logging keeps templates and values separate.
- JSON/ECS outputs encode control characters inside fields, preventing new log lines.
- Injected newlines remain within a single log record.
- This blocks log forging while preserving metadata for analysis.
Create Manual Encoding Helper (Unicode-Aware)
This encode control characters to preserve forensic evidence.
// SECURE - Manual encoding helper (Unicode-aware)
using System.Text;
public class SecureLogger
{
private readonly ILogger _logger;
private string EncodeForSingleLineTextLog(string input)
{
if (string.IsNullOrEmpty(input)) return string.Empty;
// Encode full control range to preserve attack evidence
var sb = new StringBuilder(input.Length);
foreach (var ch in input)
{
if (ch == '\r') { sb.Append("\\r"); continue; }
if (ch == '\n') { sb.Append("\\n"); continue; }
if (ch == '\t') { sb.Append("\\t"); continue; }
if (ch == '\\') { sb.Append("\\\\"); continue; }
if (ch == '\u0085' || ch == '\u2028' || ch == '\u2029')
{
sb.Append("\\u").Append(((int)ch).ToString("x4"));
continue;
}
if (ch <= 0x1F || ch == 0x7F || (ch >= 0x80 && ch <= 0x9F))
{
sb.Append("\\u").Append(((int)ch).ToString("x4"));
continue;
}
sb.Append(ch);
}
return sb.ToString();
// Shows "admin\\r\\nFAKE" - preserves evidence of injection attempt
}
public void LogUserAction(string username, string action)
{
_logger.LogInformation(
"User {Username} performed {Action}",
EncodeForSingleLineTextLog(username), // RECOMMENDED: Use encoding
EncodeForSingleLineTextLog(action)
);
}
}
Why this works:
- Encoding approach (RECOMMENDED): Converts the full control range to visible escape sequences, preserving complete forensic evidence.
- Works with text-based log layouts when JSON/ECS output is not available.
- Encoding is strongly preferred to removal because security teams can see exactly what attackers attempted and can perform threat intelligence analysis.
Validate Input Before Logging
// SECURE - Validation before logging
using System.Net;
public void LogSecureEvent(string eventData)
{
string decoded = WebUtility.UrlDecode(eventData) ?? string.Empty;
if (decoded.Contains("\r") || decoded.Contains("\n")
|| decoded.Contains("\u0085") || decoded.Contains("\u2028") || decoded.Contains("\u2029"))
{
_logger.LogWarning("Attempted log injection detected in event data");
return;
}
_logger.LogInformation("Event: {EventData}", decoded);
}
Why this works:
- It checks for CR/LF and Unicode line separators after decoding input.
- Suspicious data is replaced with a safe, generic warning.
- This creates an audit trail without logging attacker-controlled content.
- It provides defense-in-depth alongside encoding/structured logging.
Verification
After implementing the recommended secure patterns, verify the fix through multiple approaches:
- Manual testing: Submit malicious payloads relevant to this vulnerability and confirm they're handled safely without executing unintended operations
- Code review: Confirm all instances use the secure pattern (parameterized queries, safe APIs, proper encoding) with no string concatenation or unsafe operations
- Static analysis: Use security scanners to verify no new vulnerabilities exist and the original finding is resolved
- Regression testing: Ensure legitimate user inputs and application workflows continue to function correctly
- Edge case validation: Test with special characters, boundary conditions, and unusual inputs to verify proper handling
- Framework verification: If using a framework or library, confirm the recommended APIs are used correctly according to documentation
- Authentication/session testing: Verify security controls remain effective and cannot be bypassed (if applicable to the vulnerability type)
- Rescan: Run the security scanner again to confirm the finding is resolved and no new issues were introduced