Cleartext Storage of Sensitive Information in Memory

Description

Cleartext Storage of Sensitive Information in Memory is a vulnerability that occurs when a product stores sensitive information in cleartext in memory. While memory is generally considered more secure than persistent storage, sensitive data in memory can be exposed through several vectors: memory dumps during crashes (core dumps), memory swapping to disk, hibernation files, debugging operations, memory scanning by malware, cold boot attacks, and exploitation of other memory-related vulnerabilities. Sensitive data that remains in memory longer than necessary increases the window of exposure to these attack vectors.

Risk

Cleartext memory storage exposes sensitive data through multiple attack scenarios. Core dumps generated during application crashes may contain passwords, encryption keys, and other sensitive data stored in memory. When systems swap memory pages to disk due to memory pressure, sensitive data may be written to unprotected swap partitions. Hibernation and sleep modes write memory contents to disk, potentially exposing all in-memory secrets. Debugging scenarios (both legitimate and through debug interface exploitation) allow memory inspection. Malware specifically designed to scan process memory can extract credentials and keys. Physical attacks like cold boot attacks can recover memory contents from recently powered-down systems. Memory forensics on compromised systems can reveal sensitive data. The risk is amplified when applications fail to clear sensitive data after use, leaving it accessible for extended periods.

Solution

Minimize the duration sensitive data exists in cleartext in memory. Use secure memory allocation that prevents swapping to disk where available. Explicitly clear sensitive data from memory immediately after use by overwriting with zeros or random data before freeing. Use language or platform features designed for sensitive data handling (SecureString in .NET, explicit_bzero in C). Implement application-level encryption for sensitive data when it must persist in memory. Configure systems to disable or encrypt core dumps in production. Use encrypted swap partitions. Disable hibernation on systems processing sensitive data. Implement memory protection mechanisms where available. Be aware that standard memory clearing may be optimized away by compilers - use designated secure zeroing functions. Consider using hardware security modules (HSMs) for the most sensitive cryptographic operations.

Common Consequences

ImpactDetails
ConfidentialityScope: Confidentiality

Attackers who can access memory through dumps, swapping, debugging, or memory scanning can read sensitive information including credentials, encryption keys, personal data, and session tokens.

Example Code

Vulnerable Code (C/C++)

The following examples demonstrate cleartext memory storage vulnerabilities:

// Vulnerable: Password left in memory after use
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int vulnerable_authenticate(const char *username) {
    char password[256];

    printf("Enter password: ");
    fgets(password, sizeof(password), stdin);
    password[strcspn(password, "\n")] = 0;

    int result = check_credentials(username, password);

    // Vulnerable: Password remains in memory!
    // free() or returning doesn't clear the data

    return result;
}

// Vulnerable: Encryption key left in memory
typedef struct {
    unsigned char key[32];
    unsigned char iv[16];
} EncryptionContext;

void vulnerable_encrypt(const char *data, size_t len) {
    EncryptionContext ctx;

    // Load key into memory
    load_encryption_key(ctx.key, sizeof(ctx.key));
    generate_iv(ctx.iv, sizeof(ctx.iv));

    // Perform encryption
    encrypt_data(&ctx, data, len);

    // Vulnerable: Key and IV remain in ctx on stack
    // Could be exposed in core dump or stack inspection
}

// Vulnerable: Dynamically allocated sensitive data
char* vulnerable_get_api_key() {
    char *api_key = malloc(64);
    strcpy(api_key, "sk_live_secret_key_12345");

    // Use the key...
    make_api_call(api_key);

    // Vulnerable: Just freeing doesn't clear memory
    free(api_key);
    // Memory still contains key until overwritten!

    return NULL;
}
# Vulnerable: Python password handling
def vulnerable_login(username):
    password = input("Enter password: ")

    # Password is a string object in memory
    result = authenticate(username, password)

    # Vulnerable: Can't easily clear password from memory
    # Python strings are immutable, del only removes reference
    del password
    # String may still exist in memory until garbage collected

    return result

# Vulnerable: Storing sensitive data in variables
class VulnerableConfig:
    def __init__(self):
        self.db_password = None
        self.api_secret = None

    def load_config(self):
        # Vulnerable: Sensitive data stays in object memory
        self.db_password = os.environ.get('DB_PASSWORD')
        self.api_secret = os.environ.get('API_SECRET')
        # These remain in memory for object lifetime

    def connect(self):
        # Uses cleartext credentials from memory
        return db_connect(self.db_user, self.db_password)
// Vulnerable: C# password handling
using System;

public class VulnerableAuth
{
    public bool Authenticate(string username)
    {
        // Vulnerable: String is immutable, can't be cleared
        string password = Console.ReadLine();

        bool result = CheckCredentials(username, password);

        // This doesn't clear the string from memory
        password = null;
        GC.Collect();  // Still doesn't guarantee clearing

        return result;
    }

    // Vulnerable: Storing credentials in regular strings
    private string _apiKey;

    public void LoadApiKey()
    {
        _apiKey = File.ReadAllText("api_key.txt");
        // Key remains in memory as immutable string
    }
}

Fixed Code (C/C++)

// Fixed: Secure memory handling for sensitive data
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#ifdef _WIN32
#include <windows.h>
#define secure_zero(ptr, size) SecureZeroMemory(ptr, size)
#else
// Use explicit_bzero or volatile write to prevent optimization
void secure_zero(void *ptr, size_t size) {
    volatile unsigned char *p = ptr;
    while (size--) {
        *p++ = 0;
    }
}
#endif

int secure_authenticate(const char *username) {
    char password[256];

    printf("Enter password: ");
    fgets(password, sizeof(password), stdin);
    password[strcspn(password, "\n")] = 0;

    int result = check_credentials(username, password);

    // Fixed: Securely clear password from memory
    secure_zero(password, sizeof(password));

    return result;
}

// Fixed: Secure encryption key handling
typedef struct {
    unsigned char key[32];
    unsigned char iv[16];
} EncryptionContext;

void secure_encrypt(const char *data, size_t len) {
    EncryptionContext ctx;

    // Lock memory to prevent swapping (requires privileges)
    #ifndef _WIN32
    mlock(&ctx, sizeof(ctx));
    #else
    VirtualLock(&ctx, sizeof(ctx));
    #endif

    // Load key into memory
    load_encryption_key(ctx.key, sizeof(ctx.key));
    generate_iv(ctx.iv, sizeof(ctx.iv));

    // Perform encryption
    encrypt_data(&ctx, data, len);

    // Fixed: Securely clear sensitive data
    secure_zero(ctx.key, sizeof(ctx.key));
    secure_zero(ctx.iv, sizeof(ctx.iv));

    // Unlock memory
    #ifndef _WIN32
    munlock(&ctx, sizeof(ctx));
    #else
    VirtualUnlock(&ctx, sizeof(ctx));
    #endif
}

// Fixed: Secure dynamic memory handling
typedef struct {
    char *data;
    size_t len;
} SecureBuffer;

SecureBuffer* secure_alloc(size_t size) {
    SecureBuffer *buf = malloc(sizeof(SecureBuffer));
    if (!buf) return NULL;

    buf->data = malloc(size);
    if (!buf->data) {
        free(buf);
        return NULL;
    }

    buf->len = size;

    // Lock to prevent swapping
    #ifndef _WIN32
    mlock(buf->data, size);
    #endif

    return buf;
}

void secure_free(SecureBuffer *buf) {
    if (buf) {
        if (buf->data) {
            // Fixed: Clear before freeing
            secure_zero(buf->data, buf->len);
            #ifndef _WIN32
            munlock(buf->data, buf->len);
            #endif
            free(buf->data);
        }
        free(buf);
    }
}

char* secure_get_api_key() {
    SecureBuffer *key_buf = secure_alloc(64);
    if (!key_buf) return NULL;

    strcpy(key_buf->data, "sk_live_secret_key_12345");

    // Use the key...
    make_api_call(key_buf->data);

    // Fixed: Securely clear and free
    secure_free(key_buf);

    return NULL;
}
// Fixed: C# secure memory handling
using System;
using System.Security;
using System.Runtime.InteropServices;

public class SecureAuth
{
    public bool Authenticate(string username)
    {
        // Fixed: Use SecureString for password
        SecureString password = new SecureString();

        ConsoleKeyInfo keyInfo;
        while ((keyInfo = Console.ReadKey(true)).Key != ConsoleKey.Enter)
        {
            password.AppendChar(keyInfo.KeyChar);
        }
        password.MakeReadOnly();

        bool result = CheckCredentialsSecure(username, password);

        // Fixed: Dispose clears the memory
        password.Dispose();

        return result;
    }

    private bool CheckCredentialsSecure(string username, SecureString password)
    {
        IntPtr passwordPtr = IntPtr.Zero;
        try
        {
            // Convert to unmanaged memory for API call
            passwordPtr = Marshal.SecureStringToGlobalAllocUnicode(password);
            string plainPassword = Marshal.PtrToStringUni(passwordPtr);

            // Check credentials
            bool result = VerifyPassword(username, plainPassword);

            return result;
        }
        finally
        {
            // Fixed: Clear unmanaged memory
            if (passwordPtr != IntPtr.Zero)
            {
                Marshal.ZeroFreeGlobalAllocUnicode(passwordPtr);
            }
        }
    }

    // Fixed: Secure byte array handling
    public byte[] ProcessSensitiveData(byte[] sensitiveInput)
    {
        byte[] workingCopy = new byte[sensitiveInput.Length];
        byte[] result;

        try
        {
            Array.Copy(sensitiveInput, workingCopy, sensitiveInput.Length);

            // Process data...
            result = Transform(workingCopy);
        }
        finally
        {
            // Fixed: Clear sensitive data
            Array.Clear(workingCopy, 0, workingCopy.Length);
            Array.Clear(sensitiveInput, 0, sensitiveInput.Length);
        }

        return result;
    }
}
# Fixed: Python secure memory handling (limited)
import ctypes
import gc

def secure_zero_string(s):
    """Attempt to clear string from memory (limited effectiveness)."""
    # Note: This is best-effort due to Python's string interning
    try:
        location = id(s) + 20  # Offset to string data
        size = len(s)
        ctypes.memset(location, 0, size)
    except:
        pass

def secure_zero_bytearray(ba):
    """Clear bytearray contents."""
    for i in range(len(ba)):
        ba[i] = 0

# Fixed: Use bytearray for mutable sensitive data
def secure_login(username):
    # Use bytearray instead of string (mutable)
    password = bytearray(input("Enter password: "), 'utf-8')

    try:
        result = authenticate(username, password.decode('utf-8'))
    finally:
        # Fixed: Clear bytearray
        secure_zero_bytearray(password)
        gc.collect()

    return result

# Fixed: Limited lifetime for sensitive config
class SecureConfig:
    def __init__(self):
        self._encrypted_password = None
        self._key = None

    def connect(self):
        # Decrypt only when needed, clear immediately after
        password = self._decrypt(self._encrypted_password, self._key)
        try:
            conn = db_connect(self.db_user, password)
        finally:
            if isinstance(password, bytearray):
                secure_zero_bytearray(password)
        return conn

The fix implements secure memory clearing, memory locking, and uses language-specific secure data types.


Exploited in the Wild

SSH Client Memory Exposure (Security Software, 2003)

CVE-2003-0291 documented an SSH client that did not clear credentials from memory after authentication, allowing memory inspection to reveal passwords.

Password Manager Memory Vulnerabilities (Security Software, 2001)

CVE-2001-0984 documented a password application that failed to clear memory when minimized, despite user settings to do so.


Tools to Test/Exploit

  • Volatility — Memory forensics framework for analyzing memory dumps.

  • Process Hacker — Tool for inspecting process memory.

  • WinDbg — Windows debugger for memory analysis.


CVE Examples

  • CVE-2001-1517 — Authentication information in cleartext memory.

  • CVE-2001-0984 — Password application fails to clear memory.

  • CVE-2003-0291 — SSH client doesn't clear credentials from memory.


References

  1. MITRE Corporation. "CWE-316: Cleartext Storage of Sensitive Information in Memory." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/316.html

  2. CERT/CC. "MEM03-C: Clear sensitive information stored in reusable resources." https://wiki.sei.cmu.edu/confluence/display/c/MEM03-C

  3. OWASP Foundation. "Memory Management Cheat Sheet." https://cheatsheetseries.owasp.org/