Debug Messages Revealing Unnecessary Information

Description

Debug Messages Revealing Unnecessary Information occurs when a product fails to adequately prevent the revealing of unnecessary and potentially sensitive system information within debugging messages. Debug messages help troubleshoot issues by revealing internal system state, such as through memory dumps or boot logs via interfaces like UART or scan chains. While detailed debug information aids troubleshooting, it risks exposing details that could help attackers understand vulnerabilities or system architecture. Though "security by obscurity" alone is insufficient, limiting debug information supports a "defense-in-depth" strategy.

Risk

Verbose debug messages have significant security implications. Memory contents exposed. System architecture revealed. Security bypass techniques disclosed. Pointer information leaked enabling ASLR bypass. Cryptographic keys exposed. Internal paths revealed. Authentication details disclosed. Privilege levels disclosed. Attack surface exposed through detailed technical information. Medium likelihood of exploitation.

Solution

Ensure that debug messages do not reveal unnecessary information during the debug process for the intended response. Implement different verbosity levels for debug and production. Strip sensitive information from error responses. Sanitize memory addresses before output. Never include cryptographic material in debug output. Review all debug outputs during security audits. Disable or restrict debug interfaces in production. Use secure debug authentication.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Sensitive memory contents and cryptographic keys may be exposed.
Access ControlScope: Access Control

Protection mechanisms may be bypassed using revealed information.
AuthenticationScope: Authentication, Authorization

Credential and session information may be exposed.

Example Code

Vulnerable Code

// Vulnerable: Debug messages revealing sensitive information

#include <stdio.h>
#include <stdint.h>
#include <string.h>

// VULNERABLE: Debug function exposes memory addresses
void vulnerable_debug_memory_error(void* address, size_t size, int error_code) {
    // VULNERABLE: Exposes actual memory addresses (defeats ASLR)
    printf("[DEBUG] Memory error at address %p, size %zu, code %d\n",
           address, size, error_code);

    // VULNERABLE: Memory dump includes sensitive data
    printf("[DEBUG] Memory contents:\n");
    hexdump(address, size);  // Could contain keys, passwords, etc.
}

// VULNERABLE: Boot log reveals system configuration
void vulnerable_boot_log(void) {
    // VULNERABLE: Reveals exact memory layout
    printf("[BOOT] Code region: 0x%08lx - 0x%08lx\n",
           (unsigned long)code_start, (unsigned long)code_end);
    printf("[BOOT] Data region: 0x%08lx - 0x%08lx\n",
           (unsigned long)data_start, (unsigned long)data_end);
    printf("[BOOT] Stack base: 0x%08lx\n", (unsigned long)stack_base);

    // VULNERABLE: Reveals security configuration
    printf("[BOOT] Secure boot: %s\n", secure_boot_enabled ? "enabled" : "disabled");
    printf("[BOOT] Debug fuse: %s\n", debug_fuse_blown ? "blown" : "intact");

    // VULNERABLE: Reveals cryptographic key storage location
    printf("[BOOT] Key storage at: 0x%08lx\n", (unsigned long)key_storage);
}

// VULNERABLE: Authentication debug reveals too much
int vulnerable_auth_debug(const char* username, const char* password) {
    // VULNERABLE: Reveals password storage location
    const char* stored_password = get_stored_password(username);

    if (stored_password == NULL) {
        printf("[AUTH DEBUG] User not found in database at %s\n",
               user_database_path);  // VULNERABLE: Reveals path
        return -1;
    }

    if (strcmp(password, stored_password) != 0) {
        // VULNERABLE: Reveals expected password length and location
        printf("[AUTH DEBUG] Password mismatch for user '%s'\n", username);
        printf("[AUTH DEBUG] Expected password stored at %p (len=%zu)\n",
               stored_password, strlen(stored_password));
        printf("[AUTH DEBUG] Expected first char: '%c'\n",
               stored_password[0]);  // VULNERABLE: Partial password!
        return -1;
    }

    printf("[AUTH DEBUG] Successful auth, session key: 0x%016llx\n",
           session_key);  // VULNERABLE: Exposes session key!
    return 0;
}

// VULNERABLE: TAP response reveals internal structure
void vulnerable_tap_response(int command, int status) {
    // VULNERABLE: Reveals TAP hierarchy when only success/failure needed
    printf("TAP Response:\n");
    printf("  Chain: CPU_TAP -> DEBUG_TAP -> TRACE_TAP\n");
    printf("  IR length: %d bits\n", ir_length);
    printf("  DR length: %d bits\n", dr_length);
    printf("  Command: 0x%04x\n", command);
    printf("  Internal state: 0x%08x\n", internal_state);
    printf("  Status: %s\n", status ? "SUCCESS" : "FAILURE");
}
# Vulnerable: Python debug logging with sensitive data

import logging
import traceback

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

class VulnerablePaymentProcessor:
    def __init__(self):
        self.api_key = "sk_live_abc123def456"  # Production API key

    def process_payment(self, card_number, cvv, amount):
        # VULNERABLE: Logs credit card details
        logger.debug(f"Processing payment: card={card_number}, cvv={cvv}, amount={amount}")

        try:
            result = self._call_payment_api(card_number, cvv, amount)
            # VULNERABLE: Logs API response with sensitive data
            logger.debug(f"API response: {result}")
            return result
        except Exception as e:
            # VULNERABLE: Full stack trace may reveal internal paths
            logger.debug(f"Payment failed: {traceback.format_exc()}")
            # VULNERABLE: Reveals API key in error
            logger.debug(f"Used API key: {self.api_key}")
            raise

class VulnerableCryptoHandler:
    def decrypt(self, ciphertext, key):
        # VULNERABLE: Logs cryptographic key
        logger.debug(f"Decrypting with key: {key.hex()}")

        try:
            plaintext = self._perform_decryption(ciphertext, key)
            # VULNERABLE: Logs decrypted sensitive data
            logger.debug(f"Decrypted plaintext: {plaintext}")
            return plaintext
        except Exception as e:
            # VULNERABLE: Reveals internal memory state
            logger.debug(f"Decryption failed, key buffer at: {id(key):#x}")
            logger.debug(f"Ciphertext length: {len(ciphertext)}")
            raise

Fixed Code

// Fixed: Debug messages without sensitive information

#include <stdio.h>
#include <stdint.h>
#include <string.h>

// Debug verbosity levels
typedef enum {
    DEBUG_LEVEL_NONE = 0,
    DEBUG_LEVEL_ERROR = 1,
    DEBUG_LEVEL_INFO = 2,
    DEBUG_LEVEL_VERBOSE = 3
} debug_level_t;

// Current debug level - set at compile time for production
#ifdef PRODUCTION_BUILD
static const debug_level_t current_level = DEBUG_LEVEL_ERROR;
#else
static debug_level_t current_level = DEBUG_LEVEL_VERBOSE;
#endif

// FIXED: Debug function without sensitive addresses
void secure_debug_memory_error(int region_id, int error_code) {
    if (current_level < DEBUG_LEVEL_ERROR) return;

    // FIXED: Use region ID instead of actual address
    printf("[ERROR] Memory error in region %d, code %d\n",
           region_id, error_code);

    // FIXED: No memory dump in production
#ifndef PRODUCTION_BUILD
    // Only in debug builds, with sanitization
    if (current_level >= DEBUG_LEVEL_VERBOSE && is_debug_authenticated()) {
        printf("[DEBUG] Additional info available via secure debug\n");
    }
#endif
}

// FIXED: Boot log without sensitive details
void secure_boot_log(void) {
    // FIXED: Only reveal necessary boot status
    printf("[BOOT] System initializing...\n");

    // FIXED: Generic status without addresses
    printf("[BOOT] Memory: OK\n");
    printf("[BOOT] Peripherals: OK\n");

    // FIXED: Don't reveal security configuration
    // Security status is need-to-know only
    if (is_debug_authenticated()) {
        // Even authenticated debug gets limited info
        printf("[BOOT] Security subsystem: initialized\n");
    }
}

// FIXED: Authentication debug without revealing secrets
int secure_auth_debug(const char* username, const char* password) {
    (void)password;  // Don't log password at all

    // FIXED: Generic error messages
    if (!user_exists(username)) {
        log_auth_event("AUTH_USER_NOT_FOUND", username);
        return -1;  // Don't reveal database location or structure
    }

    if (!verify_password(username, password)) {
        log_auth_event("AUTH_PASSWORD_MISMATCH", username);
        // FIXED: Don't reveal expected password or its properties
        return -1;
    }

    // FIXED: Don't log session keys
    log_auth_event("AUTH_SUCCESS", username);
    return 0;
}

// FIXED: TAP response with minimal information
void secure_tap_response(int status) {
    // FIXED: Only success/failure, no internal details
    if (status) {
        printf("TAP: OK\n");
    } else {
        printf("TAP: FAIL\n");
    }

    // FIXED: Detailed info only via authenticated secure debug
#ifndef PRODUCTION_BUILD
    if (is_tap_debug_authenticated()) {
        // Limited additional info for authenticated debug
        printf("TAP: Additional diagnostics available\n");
    }
#endif
}

// FIXED: Secure debug with authentication
typedef struct {
    uint8_t challenge[32];
    uint8_t response[32];
    bool authenticated;
} debug_session_t;

static debug_session_t debug_session = {0};

bool is_debug_authenticated(void) {
    return debug_session.authenticated;
}

bool authenticate_debug_session(const uint8_t* response) {
    // Verify challenge-response before enabling verbose debug
    if (verify_debug_challenge(debug_session.challenge, response)) {
        debug_session.authenticated = true;
        return true;
    }
    return false;
}
# Fixed: Python secure debug logging

import logging
import hashlib
import os

class SanitizedFormatter(logging.Formatter):
    """Custom formatter that sanitizes sensitive data."""

    SENSITIVE_PATTERNS = [
        'password', 'key', 'secret', 'token', 'card', 'cvv', 'ssn'
    ]

    def format(self, record):
        msg = super().format(record)

        # Sanitize potential sensitive data
        for pattern in self.SENSITIVE_PATTERNS:
            # Replace values that look like they contain sensitive data
            import re
            msg = re.sub(
                rf'{pattern}["\']?\s*[:=]\s*["\']?[^"\'\s,}}]+',
                f'{pattern}=[REDACTED]',
                msg,
                flags=re.IGNORECASE
            )

        return msg

# FIXED: Production logger configuration
def get_secure_logger(name):
    logger = logging.getLogger(name)

    # FIXED: INFO level in production, DEBUG only in dev
    if os.environ.get('PRODUCTION'):
        logger.setLevel(logging.INFO)
    else:
        logger.setLevel(logging.DEBUG)

    handler = logging.StreamHandler()
    handler.setFormatter(SanitizedFormatter(
        '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
    ))
    logger.addHandler(handler)

    return logger

logger = get_secure_logger(__name__)

class SecurePaymentProcessor:
    def __init__(self):
        self.api_key = os.environ.get('PAYMENT_API_KEY')

    def process_payment(self, card_number, cvv, amount):
        # FIXED: Log only non-sensitive identifiers
        masked_card = f"****{card_number[-4:]}" if len(card_number) >= 4 else "****"
        logger.info(f"Processing payment: card={masked_card}, amount={amount}")

        try:
            result = self._call_payment_api(card_number, cvv, amount)
            # FIXED: Log only transaction status
            logger.info(f"Payment completed: transaction_id={result.get('id')}")
            return result
        except Exception as e:
            # FIXED: Generic error without internal details
            logger.error(f"Payment failed: {type(e).__name__}")
            # FIXED: Log to separate secure audit log if needed
            self._audit_log_payment_failure(masked_card, amount)
            raise

class SecureCryptoHandler:
    def decrypt(self, ciphertext, key):
        # FIXED: Never log cryptographic keys
        logger.debug(f"Decrypting data, ciphertext_length={len(ciphertext)}")

        try:
            plaintext = self._perform_decryption(ciphertext, key)
            # FIXED: Never log decrypted sensitive data
            logger.debug("Decryption successful")
            return plaintext
        except Exception as e:
            # FIXED: Generic error without memory details
            logger.error(f"Decryption failed: {type(e).__name__}")
            raise

    def _audit_log(self, operation, success):
        """Secure audit log without sensitive details."""
        # Log only operation type and outcome
        audit_entry = {
            'operation': operation,
            'success': success,
            'timestamp': time.time()
        }
        secure_audit_write(audit_entry)

CVE Examples

  • CVE-2021-25476: DRM leaks pointer information, enabling ASLR bypass.
  • CVE-2020-24491: Processor debug message contains memory transaction addresses.
  • CVE-2017-18326: Modem debug messages include cryptographic keys.

  • CWE-200: Exposure of Sensitive Information to an Unauthorized Actor (parent)
  • CWE-209: Generation of Error Message Containing Sensitive Information (peer)
  • CWE-1207: Debug and Test Problems (category)
  • CWE-532: Insertion of Sensitive Information into Log File (related)

References

  1. MITRE Corporation. "CWE-1295: Debug Messages Revealing Unnecessary Information." https://cwe.mitre.org/data/definitions/1295.html
  2. OWASP. "Information Exposure Through Debug Information"
  3. NIST. "SP 800-123 Guide to General Server Security"