Logging is a concept that most developers already use for debugging and diagnostic purposes. Security logging is an equally basic concept: to log security information during the runtime operation of an application.
Monitoring is the live review of application and security logs using various forms of automation. The same tools and patterns can be used for operations, debugging and security purposes.
The goal of security logging is to detect and respond to potential security incidents.
Real-World Attack Scenarios
Scenario 1: Log Injection via Unvalidated Input (CWE-117)
An application logs user login attempts without sanitization:
import loggingdeflog_login_attempt(username,success):# VULNERABLE - User input not sanitizedif success: logging.info(f"User {username} logged in successfully")else: logging.info(f"Failed login attempt for user {username}")
The vulnerability:
User-controlled input written directly to logs:
Newline characters (\n) not filtered
Attacker can inject fake log entries
Can hide malicious activity
Can frame other users
Can corrupt log files
The attack:
Attacker enters username with embedded newlines:
Resulting log entry:
Looks like admin logged in successfully, hiding the failed attempt.
Or inject malicious entries:
Resulting log:
Attacker hides real attack by injecting fake "all clear" message.
Advanced attack - Log forging:
Attacker frames another user for malicious activity.
Result:
Forged audit trail
Hidden attack evidence
Framing innocent users
Corrupted forensic data
Investigation misdirection
Finding it: Test login forms and any logged input. Try \n, \r\n, null bytes. Check if log entries can be injected.
Exploit:
The fix:
Sanitize all user input before logging:
Or use structured logging (JSON):
Scenario 2: Passwords in Log Files (CWE-532)
Application logs all HTTP requests including authentication:
2024-01-15 10:23:45 Failed login attempt for user admin
2024-01-15 10:25:00 CRITICAL Security scan completed - no issues found
Username: admin\n[2024-01-15 10:30:00] User victim performed unauthorized action\n[2024-01-15 10:30:01] Suspicious activity detected from IP 192.168.1.100\n
# Test log injection
curl -X POST http://target.com/login \
-d "username=admin%0AUser admin logged in successfully%0A" \
-d "password=test"
# Check logs for injected entry
import logging
import re
def sanitize_log_input(text):
# Remove newlines and control characters
return re.sub(r'[\n\r\t\x00-\x1f]', '', text)
def log_login_attempt(username, success):
safe_username = sanitize_log_input(username)
if success:
logging.info(f"User {safe_username} logged in successfully")
else:
logging.info(f"Failed login attempt for user {safe_username}")
# Search logs for passwords
grep -r "password" /var/log/app/*.log
# Output: Hundreds of plaintext passwords
john:SecretPass123!
admin:AdminPass456!
alice:MyP@ssw0rd!
grep -r "Bearer" /var/log/app/*.log
# Output: All API keys ever used
Bearer sk_live_4eC39HqLyjWDarht8Kda
Bearer pk_test_51JxK9pE4cNwD2f
# Common log locations
/var/log/application.log
/var/log/apache2/access.log
/var/log/nginx/access.log
~/.pm2/logs/
/opt/app/logs/
# Search for credentials
grep -rE "password|apikey|token|bearer|authorization" /var/log/
# Search for credit cards
grep -rE "\b[0-9]{13,16}\b" /var/log/
import logging
import re
SENSITIVE_FIELDS = ['password', 'api_key', 'token', 'credit_card', 'ssn']
def sanitize_request_data(data):
sanitized = data.copy()
for field in SENSITIVE_FIELDS:
if field in sanitized:
sanitized[field] = '[REDACTED]'
return sanitized
@app.before_request
def log_request():
# Sanitize before logging
safe_data = sanitize_request_data(request.get_json() or {})
logging.info(f"Request: {request.method} {request.path}")
logging.info(f"Body: {safe_data}")
def log_request():
# Only log safe fields
safe_fields = ['username', 'email', 'user_agent', 'ip_address']
log_data = {k: v for k, v in request.get_json().items() if k in safe_fields}
logging.info(f"Request data: {log_data}")
@app.route('/admin/delete-user/<user_id>', methods=['POST'])
def delete_user(user_id):
# VULNERABLE - No logging!
user = User.query.get(user_id)
db.session.delete(user)
db.session.commit()
return "User deleted"
curl -X POST http://target.com/admin/delete-user/1 \
-H "Authorization: Bearer stolen_token"
# User 1 deleted - no trace in logs
# No record of who deleted it
# No timestamp of deletion
# No evidence of compromise
# Search logs for deletion
grep "delete" /var/log/app.log
# No results
# Search for user access
grep "user/1" /var/log/app.log
# No results
# Completely blind to the attack
# Try 10,000 stolen passwords against admin account
for password in $(cat leaked_passwords.txt); do
curl -X POST http://target.com/login \
-d "username=admin&password=$password"
done
from collections import defaultdict
from datetime import datetime, timedelta
failed_attempts = defaultdict(list)
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
ip = request.remote_addr
# Check failed attempts
recent_failures = [
t for t in failed_attempts[ip]
if t > datetime.now() - timedelta(minutes=15)
]
if len(recent_failures) >= 5:
logging.critical(
f"BRUTE_FORCE_DETECTED - IP: {ip}, User: {username}, "
f"Attempts: {len(recent_failures)}"
)
return "Too many failed attempts", 429
if verify_password(username, password):
# Clear failed attempts
failed_attempts[ip] = []
logging.info(f"LOGIN_SUCCESS - User: {username}, IP: {ip}")
return "Welcome"
else:
# Record failed attempt
failed_attempts[ip].append(datetime.now())
logging.warning(
f"LOGIN_FAILED - User: {username}, IP: {ip}, "
f"Total failures: {len(recent_failures) + 1}"
)
return "Invalid credentials"
-rw-rw-rw- 1 root root 1.2G Jan 15 10:23 application.log
# Remove all login failures
sed -i '/LOGIN_FAILED/d' /var/log/application.log
# Remove specific IP
sed -i '/192.168.1.100/d' /var/log/application.log
# Remove time range
sed -i '/2024-01-15 10:2[0-9]:/d' /var/log/application.log
# Or delete entire log
rm /var/log/application.log
grep "LOGIN_FAILED" /var/log/application.log
# No results - attacker deleted them
# Linux
chattr +a /var/log/application.log
# Now file can only be appended to, not modified or deleted
# Remove flag (requires root)
chattr -a /var/log/application.log
import logging
from logging.handlers import SysLogHandler
# Send to remote syslog
handler = SysLogHandler(address=('logs.company.com', 514))
logging.getLogger().addHandler(handler)
# Now logs stored remotely where attacker can't delete them
# Send logs to AWS S3 with object lock
aws s3 cp /var/log/application.log s3://logs-bucket/ \
--storage-class GLACIER \
--metadata "retention=7years"
# Send to central logging server
from logging.handlers import SysLogHandler
handler = SysLogHandler(address=('logs.company.com', 514))
logging.getLogger().addHandler(handler)
# Or use ELK stack, Splunk, Datadog, etc.
# Set up automated monitoring
# Alert on:
# - Multiple failed logins
# - Privilege escalation attempts
# - Unusual access patterns
# - Missing logs (gaps in timeline)
# Example: Alert on 5 failed logins in 5 minutes
if failed_login_count > 5:
send_alert(f"Brute force detected from {ip}")