This vulnerability occurs when an application logs sensitive data in cleartext to files, the console, or a log management system. This data can include passwords, session tokens, API keys, credit card numbers, personal user information (PII), or detailed debugging information that reveals internal application state. If an attacker gains access to these logs (e.g., through a server misconfiguration (CWE-548), file traversal (CWE-22), or by compromising an administrator’s account), they can easily harvest these secrets. 📜🔑
Storing sensitive data in logs is a critical information disclosure vulnerability:
Credential Theft: Leaked passwords or session tokens lead directly to account takeover.
Supply Chain Compromise: Leaked API keys can be used to attack third-party services integrated with the application.
Massive Data Breaches: Leaked PII or financial data can result in large-scale data breaches.
Compliance Violations: Logging sensitive data like credit card numbers or personal health information in cleartext is a major violation of standards like PCI-DSS and HIPAA, leading to severe fines.
Hindered Remediation: Even after a breach, if logs contain credentials, attackers might use them to regain access.
Reference Details
CWE ID:CWE-532OWASP Top 10 (2021): A09:2021 - Security Logging and Monitoring Failures
Severity: High
This is a common implementation flaw. Developers often log entire objects, request bodies, or exception messages for debugging purposes, forgetting that these may contain sensitive data.Key Remediation Principles:
Never Log Credentials: Ensure passwords, API keys, tokens, and other secrets are never logged in cleartext.
Filter/Mask Sensitive Data: Configure logging frameworks to automatically filter or mask sensitive fields. Most frameworks support this by listing parameter names (e.g., password, token) whose values should be replaced with [FILTERED] or ****.
Do Not Log Full Objects: Avoid logging entire request objects, user objects, or data models. Explicitly log only the specific, non-sensitive properties needed for debugging.
Control Exception Logging: Be careful when logging full exception objects. Ensure toString() methods or exception properties don’t include sensitive data. Log the stack trace, but be wary of exception messages that might contain user input.
Secure Log Storage: Ensure log files themselves are protected with strict file permissions, are not served over HTTP, and are rotated/deleted according to a retention policy.
An exception handler logs the full exception, which might contain sensitive details.
# views.pydef some_view(request): try: # ... code that might fail ... perform_action(request.POST['api_key']) # Example except Exception as e: # DANGEROUS: Logging the full exception 'e' might include # the value of 'api_key' if the exception message contains it. logger.error(f"Error in some_view: {e}") # ...
Django: Use django.views.debug.SensitivePostParameters decorator on login/registration views or sensitive_variables to prevent sensitive data from being included in error reports.
Logging: In settings.py, configure logging filters (e.g., django.utils.log.CallbackFilter) to filter sensitive keys. Manually filter data before logging.
# settings.py (Secure Logging Filter)from django.utils.log import DEFAULT_LOGGINGimport logging# This filter will be applied to handlersdef sensitive_data_filter(record): # Example: Censor 'password' field in log messages if isinstance(record.args, dict) and 'password' in record.args: record.args['password'] = '****' # Add more complex regex filtering for log record.msg if needed return TrueLOGGING = DEFAULT_LOGGING # Start with defaultsLOGGING['filters'] = { 'sensitive_data_filter': { '()': 'django.utils.log.CallbackFilter', 'callback': sensitive_data_filter, }}LOGGING['handlers']['console']['filters'] = ['sensitive_data_filter'] # Apply to console# Apply filter to file handlers as well# views.py (Secure Decorator for Error Reports)from django.views.decorators.debug import sensitive_post_parameters@sensitive_post_parameters('password', 'password_confirm')def user_signup(request): # SECURE: If this view raises an exception, Django's error reporter # will censor 'password' and 'password_confirm' from the report. if request.method == 'POST': # ...
# myapp/middleware.py (Secure Middleware)def secure_log_request_middleware(get_response): def middleware(request): if request.method == 'POST': # SECURE: Explicitly copy and filter POST data safe_post_data = request.POST.copy() if 'password' in safe_post_data: safe_post_data['password'] = '****' if 'api_key' in safe_post_data: safe_post_data['api_key'] = '****' logger.info(f"Filtered POST data for {request.path}: {safe_post_data}") response = get_response(request) return response return middleware
Vulnerable Scenario 2: Logging Full Request in Filter
A debugging filter logs the entire request body.
// filter/LoggingFilter.java@Componentpublic class LoggingFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { // DANGEROUS: If request body is read and logged here, // it will include passwords, tokens, etc., from login or API requests. // String requestBody = ... read request.getInputStream() ... // log.debug("Request body: {}", requestBody); chain.doFilter(request, response); }}
toString(): Override toString() in DTOs/Entities to mask sensitive fields (e.g., return password='****').
Log Masking: Use logging framework features (like Logback’s MaskingJsonEncoder or Log4j2’s RewritePolicy) to automatically mask patterns (regex for credit cards) or specific JSON keys (password).
Selective Logging: Explicitly log only the non-sensitive fields you need (e.g., log.info("Registering user: {}", dto.getUsername())), not the whole object.
Filter Configuration: Configure filters (like CommonsRequestLoggingFilter) to exclude sensitive headers and payload.
Submit forms with sensitive data. Review all log outputs (console, files, log aggregator). Check for plaintext passwords, API keys, session tokens, credit card numbers, or PII. Review toString() methods of models and DTOs. Review logging configurations (Logback/Log4j2 XML files) for masking rules.
// Models/RegistrationViewModel.cspublic class RegistrationViewModel { public string Email { get; set; } [DataType(DataType.Password)] public string Password { get; set; } // Sensitive data // Default .ToString() might just show class name, but logging // via JSON serialization will expose it.}// Controllers/AccountController.cspublic async Task<IActionResult> Register(RegistrationViewModel model){ // DANGEROUS: Logging the entire model object. If using structured logging // (like Serilog), this serializes the object, including the Password. _logger.LogInformation("New user registration attempt: {@RegistrationModel}", model); // ... registration logic ...}
Selective Logging: Do not log entire models or DTOs containing sensitive data. Log only non-sensitive properties (e.g., _logger.LogInformation("New user registration attempt: {Email}", model.Email);).
Log Filtering/Masking: Use logging library features (e.g., Serilog’s Destructure.ByTransforming<T> or IDestructuringPolicy) to automatically mask sensitive properties (Password, ApiKey) before serialization.
Exception Logging: Catch exceptions, log the stack trace (ex), but explicitly log a sanitized version of the message if it might contain sensitive input.
Submit forms with sensitive data. Review log outputs (console, files, Application Insights, Seq, etc.). Check for JSON logs or log messages containing cleartext passwords, API keys, etc. Review exception logging to ensure sensitive inputs are not part of the logged exception message or properties.
// app/Http/Controllers/SomeController.phppublic function someAction(Request $request){ $user = auth()->user(); // DANGEROUS: If User object's 'toArray()' or serialization // includes sensitive fields (e.g., 'remember_token', 'api_key'), // logging the object will expose them. Log::debug('Processing action for user:', ['user' => $user]); // ...}
Filter Parameters: Use Laravel’s built-in config.filter_parameters (in config/logging.php or config/app.php’s logging section) to list keys that Monolog should automatically filter (e.g., password, password_confirmation).
Selective Logging: Explicitly log only the non-sensitive fields needed (e.g., Log::info('User ID ' . $user->id)), not the entire object.
Model toArray(): Adjust the $hidden property in Eloquent models to ensure sensitive fields are excluded when the model is cast to an array (which logging might do).
Submit forms with passwords, API keys, etc. Review storage/logs/laravel.log. Verify that sensitive fields are replaced with [FILTERED] or ****. Check config/logging.php for filter configuration.
Vulnerable Scenario 2: Logging Full User Object from DB
// services/userService.jsasync function findUser(username) { const user = await User.findOne({ username }); // Assume returns Mongoose model // DANGEROUS: Logging the full user object might include // passwordHash, resetToken, etc. logger.debug('Found user:', { user: user }); return user;}
Filtering/Masking: Configure your logging library (Winston/Pino) with custom formatters or serializers that redact sensitive keys (e.t., password, token, authorization).
Selective Logging: Explicitly log only the properties you need, not entire objects (logger.info('Login attempt for user:', { username: req.body.username, ip: req.ip });).
// logger.js (Winston Secure Configuration - Conceptual)const winston = require('winston');const { combine, timestamp, json, prettyPrint } = winston.format;// SECURE: Create a custom format to redact sensitive keysconst redactFormat = winston.format((info, opts) => { // Simple recursive redact function (needs to be robust) function redact(obj) { if (typeof obj !== 'object' || obj === null) return obj; if (Array.isArray(obj)) return obj.map(redact); const newObj = {}; for (const key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { if (['password', 'token', 'apiKey', 'authorization', 'creditCard'].includes(key.toLowerCase())) { newObj[key] = '****'; // Mask } else { newObj[key] = redact(obj[key]); // Recurse } } } return newObj; } // Apply redaction to the whole info object (or specific parts) // This simple example just checks top-level 'message' if it's an object, // or 'body', 'user' etc. if they exist. if (info.message && typeof info.message === 'object') info.message = redact(info.message); if (info.body) info.body = redact(info.body); if (info.user) info.user = redact(info.user); // Also need to handle info itself if it was `logger.info({ password: '...' })` // A better approach might be to redact info.meta or the result of json() return redact(info); // Apply to whole info object});const logger = winston.createLogger({ level: 'info', // SECURE: Combine timestamp, redaction, and final format format: combine( timestamp(), redactFormat(), // Apply custom redaction json() // Convert to JSON ), transports: [ new winston.transports.Console() ],});module.exports = logger;// app.js (Usage)app.post('/login', (req, res) => { // SECURE: Logger configuration will redact 'password' field logger.info('Login attempt:', { body: req.body, ip: req.ip }); // ... auth logic ...});
Submit forms with sensitive data (login, API keys). Review log output (console, files, log aggregator). Verify sensitive keys (password, token, etc.) are masked (****). Check logging configuration for redaction/filtering logic.
Vulnerable Scenario 1: Not Filtering Custom Sensitive Parameters
# app/controllers/payment_controller.rbclass PaymentController < ApplicationController def process_payment # DANGEROUS: params[:credit_card_number] and params[:cvv] # are not in the default filter list. Logging params will expose them. Rails.logger.info "Processing payment params: #{params.inspect}" # ... process ... endend
Vulnerable Scenario 2: Logging Full Object inspect
# app/controllers/users_controller.rbdef show @user = User.find(params[:id]) # DANGEROUS: If User model has custom sensitive attributes # (e.g., @api_key, @social_security_number) that are not # filtered, .inspect will log them. Rails.logger.debug "User object: #{@user.inspect}" # ...end
Filter Parameters: Add all sensitive parameter names (including nested ones) to config.filter_parameters in config/initializers/filter_parameter_logging.rb.
inspect: Be cautious logging .inspect on Active Record objects. Override the inspect method on sensitive models to mask fields, or log attributes selectively (Rails.logger.debug "User ID: #{@user.id}").
Submit requests with parameters listed in filter_parameters (e.g., password, credit_card_number, cvv). Check log files (log/development.log, log/production.log) and verify the values for these parameters appear as [FILTERED]. Review code for .inspect calls on sensitive objects.