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
| Impact | Details |
|---|---|
| Confidentiality | Scope: 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
-
MITRE Corporation. "CWE-316: Cleartext Storage of Sensitive Information in Memory." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/316.html
-
CERT/CC. "MEM03-C: Clear sensitive information stored in reusable resources." https://wiki.sei.cmu.edu/confluence/display/c/MEM03-C
-
OWASP Foundation. "Memory Management Cheat Sheet." https://cheatsheetseries.owasp.org/