Insufficient Logging
Description
Insufficient Logging occurs when a security-critical event occurs but the software either does not record the event or omits important details about the event. Adequate logging is essential for detecting attacks, investigating incidents, and meeting compliance requirements. Without proper logging, organizations cannot detect breaches in progress, understand what happened during an incident, or provide evidence for legal proceedings. This includes logging failures, missing security events, and logs without sufficient context.
Risk
Insufficient logging significantly increases the damage from security breaches. Attackers can operate undetected for extended periods—the average breach detection time is 197 days. Without logs, organizations cannot perform forensic analysis, identify compromised accounts, or determine the scope of breaches. Compliance frameworks (PCI-DSS, HIPAA, SOX) require comprehensive security logging. Missing logs can result in regulatory penalties and litigation challenges.
Solution
Log all security-relevant events: authentication attempts (success and failure), authorization failures, input validation failures, application errors, and administrative actions. Include essential context: timestamp, user identity, source IP, action performed, and target resource. Protect log integrity through append-only storage and centralized logging. Implement real-time monitoring and alerting. Retain logs for appropriate periods based on compliance requirements. Never log sensitive data like passwords or credit card numbers.
Common Consequences
| Impact | Details |
|---|---|
| Non-Repudiation | Scope: Lack of Accountability Without logs, malicious actions cannot be attributed to specific users or processes. |
| Detection | Scope: Delayed Breach Detection Attacks proceed undetected without proper logging and monitoring. |
| Forensics | Scope: Incomplete Investigation Incident response is hampered by missing or incomplete log data. |
Example Code + Solution Code
Vulnerable Code
# VULNERABLE: No logging of security events
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
user = authenticate(username, password)
if user:
session['user_id'] = user.id
return redirect('/dashboard')
# No logging of failed login attempt!
return 'Invalid credentials', 401
# VULNERABLE: Logging sensitive data
import logging
logger = logging.getLogger(__name__)
@app.route('/payment', methods=['POST'])
def process_payment():
card_number = request.form['card_number']
cvv = request.form['cvv']
# DON'T log sensitive data!
logger.info(f"Processing payment for card {card_number} with CVV {cvv}")
return process_card(card_number, cvv)
# VULNERABLE: No error logging
@app.route('/api/data')
def get_data():
try:
return fetch_sensitive_data()
except Exception:
# Silent failure - no logging!
return 'Error', 500
// VULNERABLE: No security event logging
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
User user = userService.authenticate(
request.getUsername(),
request.getPassword()
);
if (user != null) {
return ResponseEntity.ok(generateToken(user));
}
// No logging of authentication failure!
return ResponseEntity.status(401).body("Invalid credentials");
}
// VULNERABLE: Inadequate context in logs
@DeleteMapping("/users/{id}")
public ResponseEntity<?> deleteUser(@PathVariable Long id) {
userRepository.deleteById(id);
// Useless log - who did this? When? From where?
System.out.println("User deleted");
return ResponseEntity.ok().build();
}
// VULNERABLE: Logs only to console in production
@Service
public class OrderService {
public void processOrder(Order order) {
System.out.println("Processing order: " + order.getId());
// Console output is lost in production
}
}
// VULNERABLE: No audit trail
app.put('/api/settings', async (req, res) => {
const oldSettings = await Settings.findOne();
await Settings.updateOne({}, req.body);
// No record of who changed what!
res.json({ success: true });
});
// VULNERABLE: Swallowing exceptions
app.post('/api/transfer', async (req, res) => {
try {
await performTransfer(req.body);
res.json({ success: true });
} catch (error) {
// Silently fails without logging
res.status(500).json({ error: 'Transfer failed' });
}
});
Fixed Code
# SAFE: Comprehensive security logging
import logging
from datetime import datetime
import json
# Configure structured logging
logging.basicConfig(
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO
)
logger = logging.getLogger('security')
def log_security_event(event_type, user=None, success=True, details=None):
"""Log security events with full context."""
event = {
'timestamp': datetime.utcnow().isoformat(),
'event_type': event_type,
'success': success,
'user': user or 'anonymous',
'ip_address': request.remote_addr,
'user_agent': request.headers.get('User-Agent'),
'request_id': g.get('request_id'),
'details': details or {}
}
logger.info(json.dumps(event))
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
user = authenticate(username, password)
if user:
log_security_event(
'authentication',
user=username,
success=True,
details={'method': 'password'}
)
session['user_id'] = user.id
return redirect('/dashboard')
log_security_event(
'authentication',
user=username,
success=False,
details={'reason': 'invalid_credentials'}
)
return 'Invalid credentials', 401
# SAFE: Mask sensitive data
def mask_card_number(card_number):
"""Return masked card number for logging."""
return f"****{card_number[-4:]}"
@app.route('/payment', methods=['POST'])
def process_payment():
card_number = request.form['card_number']
cvv = request.form['cvv']
# Log with masked data
log_security_event(
'payment_attempt',
user=current_user.username,
details={
'card_last_four': card_number[-4:],
'amount': request.form['amount']
}
)
try:
result = process_card(card_number, cvv)
log_security_event(
'payment_success',
user=current_user.username,
details={'transaction_id': result.id}
)
return jsonify(result)
except PaymentError as e:
log_security_event(
'payment_failure',
user=current_user.username,
success=False,
details={'error': str(e)}
)
raise
# SAFE: Error logging with context
@app.route('/api/data')
def get_data():
try:
return fetch_sensitive_data()
except Exception as e:
logger.error(
'Data fetch error',
exc_info=True,
extra={
'user': current_user.username if current_user else None,
'endpoint': request.endpoint,
'request_id': g.get('request_id')
}
)
return 'Error', 500
// SAFE: Comprehensive security logging
@Service
public class AuditService {
private static final Logger auditLogger = LoggerFactory.getLogger("audit");
public void logSecurityEvent(SecurityEvent event) {
MDC.put("requestId", RequestContext.getCurrentRequestId());
MDC.put("userId", SecurityContext.getCurrentUser());
MDC.put("ipAddress", RequestContext.getClientIP());
auditLogger.info("{}", objectMapper.writeValueAsString(event));
MDC.clear();
}
}
@RestController
public class SecureAuthController {
@Autowired
private AuditService auditService;
@PostMapping("/login")
public ResponseEntity<?> login(
@RequestBody LoginRequest request,
HttpServletRequest httpRequest) {
User user = userService.authenticate(
request.getUsername(),
request.getPassword()
);
if (user != null) {
auditService.logSecurityEvent(SecurityEvent.builder()
.type("AUTHENTICATION_SUCCESS")
.username(request.getUsername())
.ipAddress(getClientIP(httpRequest))
.userAgent(httpRequest.getHeader("User-Agent"))
.build());
return ResponseEntity.ok(generateToken(user));
}
auditService.logSecurityEvent(SecurityEvent.builder()
.type("AUTHENTICATION_FAILURE")
.username(request.getUsername())
.ipAddress(getClientIP(httpRequest))
.userAgent(httpRequest.getHeader("User-Agent"))
.reason("INVALID_CREDENTIALS")
.build());
return ResponseEntity.status(401).body("Invalid credentials");
}
}
// SAFE: Detailed audit logging for admin actions
@DeleteMapping("/users/{id}")
@PreAuthorize("hasRole('ADMIN')")
public ResponseEntity<?> deleteUser(
@PathVariable Long id,
Principal principal) {
User targetUser = userRepository.findById(id)
.orElseThrow(() -> new NotFoundException("User not found"));
// Log before action with full context
auditService.logSecurityEvent(SecurityEvent.builder()
.type("USER_DELETION")
.actor(principal.getName())
.target(targetUser.getUsername())
.targetId(id)
.details(Map.of(
"targetEmail", targetUser.getEmail(),
"targetRole", targetUser.getRole()
))
.build());
userRepository.deleteById(id);
return ResponseEntity.ok().build();
}
// SAFE: Structured logging with Winston
const winston = require('winston');
const auditLogger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
transports: [
new winston.transports.File({ filename: 'audit.log' }),
new winston.transports.Console()
]
});
function logSecurityEvent(eventType, req, details = {}) {
auditLogger.info({
eventType,
timestamp: new Date().toISOString(),
user: req.user?.id || 'anonymous',
ipAddress: req.ip,
userAgent: req.get('User-Agent'),
requestId: req.id,
path: req.path,
method: req.method,
...details
});
}
// SAFE: Full audit trail for settings changes
app.put('/api/settings', authenticate, async (req, res) => {
const oldSettings = await Settings.findOne();
const changes = {};
// Track what changed
for (const [key, value] of Object.entries(req.body)) {
if (oldSettings[key] !== value) {
changes[key] = {
old: oldSettings[key],
new: value
};
}
}
await Settings.updateOne({}, req.body);
logSecurityEvent('SETTINGS_CHANGED', req, {
changes,
success: true
});
res.json({ success: true });
});
// SAFE: Error logging with context
app.post('/api/transfer', authenticate, async (req, res) => {
try {
const result = await performTransfer(req.body);
logSecurityEvent('TRANSFER_SUCCESS', req, {
amount: req.body.amount,
toAccount: req.body.toAccount,
transactionId: result.id
});
res.json({ success: true, transactionId: result.id });
} catch (error) {
logSecurityEvent('TRANSFER_FAILURE', req, {
amount: req.body.amount,
toAccount: req.body.toAccount,
error: error.message,
success: false
});
auditLogger.error('Transfer failed', {
error: error.message,
stack: error.stack,
user: req.user?.id
});
res.status(500).json({ error: 'Transfer failed' });
}
});
Exploited in the Wild
Equifax Breach (2017)
The Equifax breach went undetected for 76 days partly due to insufficient logging and monitoring. Better security monitoring could have detected the data exfiltration earlier.
Target Data Breach (2013)
Target had alerts from their security tools but lacked proper logging infrastructure to correlate and act on them, contributing to a 40-million card breach.
SolarWinds Supply Chain Attack (2020)
The sophistication of the attack was aided by organizations' inability to detect anomalous behavior due to insufficient logging of security-relevant events.
Tools to test/exploit
-
ELK Stack — centralized logging and analysis.
-
Splunk — security information and event management.
-
OWASP ZAP — can identify logging deficiencies.
-
Graylog — log management platform.
CVE Examples
-
CVE-2021-44228 — Log4Shell (logging library vulnerability).
-
CVE-2018-0147 — Cisco insufficient logging.
References
-
MITRE. "CWE-778: Insufficient Logging." https://cwe.mitre.org/data/definitions/778.html
-
OWASP. "A09:2021 – Security Logging and Monitoring Failures." https://owasp.org/Top10/A09_2021-Security_Logging_and_Monitoring_Failures/