Insertion of Sensitive Information into Log File
Description
Insertion of Sensitive Information into Log File occurs when software writes sensitive information to a log file that is available to attackers or unauthorized parties. Log files often have weaker access controls than application data and may be transmitted to centralized logging systems, stored in backups, or viewed by support personnel. Sensitive data in logs can include passwords, API keys, credit card numbers, personal information, session tokens, and cryptographic secrets.
Risk
Sensitive information in logs creates multiple attack vectors. Logs may be accessible to developers, operators, or third-party services who shouldn't see production data. Log aggregation services may store data in less secure environments. Logs are often included in backups and may be retained for extended periods. Attackers who gain read access to logs (through directory traversal, SSRF, or log injection) obtain sensitive data directly. Compliance frameworks prohibit logging certain data types.
Solution
Never log passwords, API keys, credit card numbers, or authentication tokens. Mask or truncate sensitive data before logging. Implement logging levels that exclude sensitive debug information in production. Use structured logging with explicit fields to control what's logged. Review logs regularly for sensitive data exposure. Configure log frameworks to redact known sensitive patterns. Encrypt logs at rest and in transit. Apply access controls to log files.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Credential Exposure Logged passwords, tokens, and API keys can be used for unauthorized access. |
| Privacy | Scope: PII Disclosure Personal information in logs violates privacy regulations (GDPR, CCPA). |
| Compliance | Scope: Regulatory Violations Logging credit card data violates PCI-DSS; logging health data may violate HIPAA. |
Example Code + Solution Code
Vulnerable Code
# VULNERABLE: Logging passwords
import logging
logger = logging.getLogger(__name__)
def authenticate(username, password):
logger.info(f"Login attempt: username={username}, password={password}")
# Password logged in plaintext!
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
logger.info(f"Successful login for {username} with password {password}")
return user
return None
# VULNERABLE: Logging API keys
def call_external_api(api_key, endpoint):
logger.debug(f"Calling {endpoint} with API key: {api_key}")
response = requests.get(endpoint, headers={'Authorization': api_key})
return response
# VULNERABLE: Logging credit card data
def process_payment(card_number, cvv, amount):
logger.info(f"Processing payment: card={card_number}, cvv={cvv}, amount={amount}")
# Full card number and CVV logged!
# VULNERABLE: Logging full request/response
def proxy_request(request):
logger.debug(f"Request headers: {dict(request.headers)}") # May contain auth tokens
logger.debug(f"Request body: {request.get_data()}") # May contain passwords
// VULNERABLE: Logging user credentials
public class AuthService {
private static final Logger logger = LoggerFactory.getLogger(AuthService.class);
public User authenticate(String username, String password) {
// DON'T LOG PASSWORDS!
logger.info("Authenticating user {} with password {}", username, password);
User user = userRepository.findByUsername(username);
if (user != null && passwordEncoder.matches(password, user.getPasswordHash())) {
logger.info("User {} logged in successfully with password {}", username, password);
return user;
}
return null;
}
}
// VULNERABLE: Logging tokens
public class TokenService {
public String generateToken(User user) {
String token = jwtService.generate(user);
logger.debug("Generated token for user {}: {}", user.getUsername(), token);
// Token can be used to impersonate user!
return token;
}
}
// VULNERABLE: Logging PII
public class UserService {
public void updateProfile(UserProfileRequest request) {
logger.info("Updating profile: {}", request); // May log SSN, DOB, etc.
}
}
// VULNERABLE: Logging sensitive request data
app.post('/register', (req, res) => {
console.log('Registration request:', JSON.stringify(req.body));
// Logs passwords, personal information!
// Process registration...
});
// VULNERABLE: Logging session tokens
app.post('/login', (req, res) => {
const token = generateToken(req.user);
console.log(`Generated session token for ${req.user.email}: ${token}`);
// Token logged - can be stolen from logs
res.json({ token });
});
// VULNERABLE: Catch-all error logging
app.use((err, req, res, next) => {
console.error('Error:', err);
console.error('Request:', req.body); // May contain sensitive data
res.status(500).send('Error');
});
Fixed Code
# SAFE: Never log sensitive data
import logging
import re
logger = logging.getLogger(__name__)
# Patterns to redact
SENSITIVE_PATTERNS = [
(r'password["\s:=]+["\']?[\w]+["\']?', 'password=***REDACTED***'),
(r'\b\d{13,16}\b', '***CARD_REDACTED***'), # Credit card
(r'\b\d{3}-\d{2}-\d{4}\b', '***SSN_REDACTED***'), # SSN
]
def sanitize_log_message(message):
"""Redact sensitive patterns from log messages."""
for pattern, replacement in SENSITIVE_PATTERNS:
message = re.sub(pattern, replacement, message, flags=re.IGNORECASE)
return message
class SanitizedFormatter(logging.Formatter):
def format(self, record):
record.msg = sanitize_log_message(str(record.msg))
return super().format(record)
def authenticate(username, password):
# Log only non-sensitive info
logger.info(f"Login attempt for user: {username}")
# NEVER log password!
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
logger.info(f"Successful login for user: {username}")
return user
logger.warning(f"Failed login attempt for user: {username}")
return None
# SAFE: Mask sensitive data
def mask_card_number(card_number):
"""Show only last 4 digits."""
return f"***{card_number[-4:]}"
def mask_api_key(api_key):
"""Show only first 4 characters."""
return f"{api_key[:4]}***"
def call_external_api(api_key, endpoint):
logger.debug(f"Calling {endpoint} with API key: {mask_api_key(api_key)}")
response = requests.get(endpoint, headers={'Authorization': api_key})
return response
def process_payment(card_number, cvv, amount):
# Only log masked data
logger.info(f"Processing payment: card={mask_card_number(card_number)}, amount={amount}")
# Never log CVV!
# SAFE: Structured logging with explicit fields
import structlog
log = structlog.get_logger()
def process_order(order):
log.info(
"processing_order",
order_id=order.id,
amount=order.total,
# Don't include sensitive customer data
customer_id=order.customer_id
)
// SAFE: Never log credentials
public class SecureAuthService {
private static final Logger logger = LoggerFactory.getLogger(SecureAuthService.class);
public User authenticate(String username, String password) {
// Log only username, NEVER password
logger.info("Authentication attempt for user: {}", username);
User user = userRepository.findByUsername(username);
if (user != null && passwordEncoder.matches(password, user.getPasswordHash())) {
logger.info("Successful authentication for user: {}", username);
return user;
}
logger.warn("Failed authentication attempt for user: {}", username);
return null;
}
}
// SAFE: Mask tokens in logs
public class SecureTokenService {
public String generateToken(User user) {
String token = jwtService.generate(user);
// Only log that a token was generated, not the token itself
logger.info("Generated token for user: {}", user.getUsername());
// Or mask it
logger.debug("Token generated: {}...", token.substring(0, 10));
return token;
}
}
// SAFE: Custom toString that excludes sensitive fields
public class UserProfileRequest {
private String name;
private String ssn;
private String dateOfBirth;
@Override
public String toString() {
return String.format("UserProfileRequest[name=%s, ssn=***REDACTED***, dob=***REDACTED***]", name);
}
}
// SAFE: Annotation-based field exclusion
@ToString(exclude = {"password", "ssn", "creditCard"})
public class User {
private String username;
private String password;
private String ssn;
private String creditCard;
}
// SAFE: Custom log sanitizer
public class LogSanitizer {
private static final Pattern[] SENSITIVE_PATTERNS = {
Pattern.compile("password[\"\\s:=]+[\"']?[\\w]+[\"']?", Pattern.CASE_INSENSITIVE),
Pattern.compile("\\b\\d{13,16}\\b"), // Credit card
Pattern.compile("bearer\\s+[A-Za-z0-9\\-._~+/]+=*", Pattern.CASE_INSENSITIVE)
};
public static String sanitize(String message) {
String result = message;
for (Pattern pattern : SENSITIVE_PATTERNS) {
result = pattern.matcher(result).replaceAll("***REDACTED***");
}
return result;
}
}
// SAFE: Sanitized logging
const sensitiveFields = ['password', 'token', 'apiKey', 'creditCard', 'ssn', 'cvv'];
function sanitizeObject(obj) {
if (!obj || typeof obj !== 'object') return obj;
const sanitized = Array.isArray(obj) ? [] : {};
for (const [key, value] of Object.entries(obj)) {
if (sensitiveFields.some(field =>
key.toLowerCase().includes(field.toLowerCase()))) {
sanitized[key] = '***REDACTED***';
} else if (typeof value === 'object') {
sanitized[key] = sanitizeObject(value);
} else {
sanitized[key] = value;
}
}
return sanitized;
}
const logger = {
info: (msg, data) => console.log(msg, data ? sanitizeObject(data) : ''),
error: (msg, data) => console.error(msg, data ? sanitizeObject(data) : '')
};
// SAFE: Explicit logging of non-sensitive fields
app.post('/register', (req, res) => {
logger.info('Registration request', {
email: req.body.email,
username: req.body.username
// Don't log password!
});
// Process registration...
});
// SAFE: Token handling without logging
app.post('/login', (req, res) => {
const token = generateToken(req.user);
logger.info('Login successful', { userId: req.user.id });
// Token NOT logged
res.json({ token });
});
// SAFE: Sanitized error logging
app.use((err, req, res, next) => {
logger.error('Request error', {
error: err.message,
path: req.path,
method: req.method,
userId: req.user?.id
// Don't log request body
});
res.status(500).send('Error');
});
Exploited in the Wild
Twitter Internal Logs (2018)
Twitter discovered that passwords were being logged in plaintext to internal systems. While not exploited externally, this violated security best practices and required mass password resets.
GitHub OAuth Tokens (2018)
GitHub accidentally logged OAuth tokens in plaintext, requiring revocation of potentially compromised tokens.
Facebook Password Logging (2019)
Facebook stored hundreds of millions of user passwords in plaintext logs accessible to employees, discovered during internal security review.
Tools to test/exploit
-
Logcheck tools — analyze logs for sensitive data.
-
truffleHog — find secrets in logs and code.
-
GitLeaks — detect secrets in repositories.
-
Custom regex patterns — identify sensitive data patterns.
CVE Examples
-
CVE-2019-11358 — jQuery logging sensitive data.
-
CVE-2020-5410 — Spring Cloud Config sensitive data in logs.
-
CVE-2021-22112 — Spring Security logging credentials.
References
-
MITRE. "CWE-532: Insertion of Sensitive Information into Log File." https://cwe.mitre.org/data/definitions/532.html
-
OWASP. "Logging Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Logging_Cheat_Sheet.html