Insecure Storage of Sensitive Information
Description
Insecure Storage of Sensitive Information occurs when software stores sensitive information without properly limiting read or write access by unauthorized actors. This vulnerability encompasses storing sensitive data in cleartext, using weak encryption, setting improper file permissions, or placing data in locations accessible to unintended parties. When read access isn't properly restricted, attackers can steal sensitive information such as credentials, personal data, or cryptographic keys. When write access isn't restricted, attackers can modify or delete data, potentially causing incorrect application behavior, data corruption, or denial of service.
Risk
Insecure storage can lead to severe security breaches. Exposed credentials enable account takeover and lateral movement within systems. Leaked personal data results in privacy violations and regulatory penalties. Compromised cryptographic keys undermine all security measures depending on those keys. Modified configuration data can alter application behavior maliciously. This vulnerability is particularly dangerous because it can persist undetected for extended periods, allowing attackers ongoing access to sensitive data. The risk extends beyond the immediate application to any systems that trust the compromised data.
Solution
Implement a defense-in-depth approach to data storage security. Classify data by sensitivity and apply appropriate protection levels. Use strong encryption for sensitive data at rest with properly managed keys. Store encryption keys separately from encrypted data, preferably in hardware security modules or secure key stores. Apply restrictive file permissions (owner-only where possible). Use platform-provided secure storage mechanisms (Keychain, KeyStore). Never store credentials in cleartext or reversibly encrypted form. Implement access logging for sensitive data. Regularly audit storage permissions and encryption strength. Consider data minimization—don't store sensitive data unless necessary.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Confidentiality Read Application Data - Attackers can read sensitive information by accessing the insecure storage mechanism. |
| Integrity | Scope: Integrity Modify Application Data - Attackers can overwrite or corrupt sensitive information in insecure storage. |
Example Code
Vulnerable Code
# Vulnerable: Storing credentials in cleartext config file
import json
class VulnerableConfig:
def save_credentials(self, username, password, api_key):
config = {
'username': username,
'password': password, # Cleartext password!
'api_key': api_key # Cleartext API key!
}
# Vulnerable: World-readable file
with open('/etc/myapp/config.json', 'w') as f:
json.dump(config, f)
# Default permissions may be readable by others
// Vulnerable: Storing sensitive data in SharedPreferences without encryption
public class VulnerablePreferences {
public void saveUserData(Context context, String password, String creditCard) {
SharedPreferences prefs = context.getSharedPreferences(
"user_data", Context.MODE_PRIVATE);
// Vulnerable: Stored in XML in cleartext
// /data/data/com.app/shared_prefs/user_data.xml
prefs.edit()
.putString("password", password) // Cleartext!
.putString("credit_card", creditCard) // Cleartext!
.apply();
// Root users and backup utilities can access this
}
}
// Vulnerable: Storing sensitive data in browser storage
class VulnerableStorage {
saveSession(sessionToken, userEmail, paymentInfo) {
// Vulnerable: localStorage is accessible to any script
localStorage.setItem('session_token', sessionToken);
localStorage.setItem('user_email', userEmail);
localStorage.setItem('payment_info', JSON.stringify(paymentInfo));
// XSS attacks can steal all of this
}
}
// Vulnerable: Storing password in configuration file
public class VulnerableDbConfig
{
public static void SaveDatabaseConfig(string connectionString)
{
// Vulnerable: Connection string with password in app.config
var config = ConfigurationManager.OpenExeConfiguration(
ConfigurationUserLevel.None);
config.ConnectionStrings.ConnectionStrings.Add(
new ConnectionStringSettings(
"MainDB",
// Cleartext: "Server=db;User=admin;Password=secret123;"
connectionString
)
);
config.Save();
}
}
// Vulnerable: Cookie storing sensitive data
<?php
function vulnerable_remember_me($userId, $password) {
// Vulnerable: Storing password in cookie
setcookie('user_id', $userId, time() + 86400);
setcookie('password', $password, time() + 86400); // Cleartext!
// Cookie transmitted with every request, visible in browser
}
// Vulnerable: Session data on shared hosting
session_save_path('/tmp'); // Shared temp directory
session_start();
$_SESSION['credit_card'] = $creditCard; // Other users might access
?>
// Vulnerable: Embedding secrets in binary
package main
const (
// Vulnerable: Hardcoded secrets extractable via strings command
APIKey = "sk_live_abc123xyz789"
DatabasePass = "super_secret_password"
)
func main() {
// These values are compiled into the binary
connectDatabase(DatabasePass)
}
Fixed Code
# Fixed: Secure credential storage with encryption
import json
import os
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
import base64
import keyring
class FixedConfig:
def __init__(self, app_name: str):
self.app_name = app_name
self.config_dir = os.path.join(os.path.expanduser('~'), '.config', app_name)
os.makedirs(self.config_dir, mode=0o700, exist_ok=True)
def save_credentials(self, username: str, password: str, api_key: str):
# Fixed: Store password in OS keyring (protected by OS)
keyring.set_password(self.app_name, 'password', password)
# Fixed: Store API key in keyring
keyring.set_password(self.app_name, 'api_key', api_key)
# Fixed: Only store non-sensitive data in file
config = {'username': username}
config_path = os.path.join(self.config_dir, 'config.json')
with open(config_path, 'w') as f:
json.dump(config, f)
# Fixed: Restrictive permissions
os.chmod(config_path, 0o600)
def get_password(self) -> str:
return keyring.get_password(self.app_name, 'password')
def get_api_key(self) -> str:
return keyring.get_password(self.app_name, 'api_key')
// Fixed: Using EncryptedSharedPreferences and Android Keystore
import androidx.security.crypto.EncryptedSharedPreferences;
import androidx.security.crypto.MasterKey;
public class FixedPreferences {
private SharedPreferences securePrefs;
public FixedPreferences(Context context) throws Exception {
// Fixed: Create master key in Android Keystore
MasterKey masterKey = new MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build();
// Fixed: Use encrypted SharedPreferences
securePrefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
}
public void saveUserData(String password, String creditCard) {
// Fixed: Data is encrypted automatically
securePrefs.edit()
.putString("password", password)
.putString("credit_card", creditCard)
.apply();
}
// For very sensitive data, use Keystore directly
public void saveTokenInKeystore(String token) throws Exception {
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
// Store using Keystore protection
// Token would need to be stored via EncryptedSharedPreferences
// or similar secure mechanism
}
}
// Fixed: Secure storage in browser with encryption
class FixedStorage {
constructor() {
this.cryptoKey = null;
}
async initialize() {
// Generate or retrieve encryption key
this.cryptoKey = await this.getOrCreateKey();
}
async saveSession(sessionToken, userEmail) {
// Fixed: Encrypt sensitive data before storage
const encryptedToken = await this.encrypt(sessionToken);
// Use sessionStorage for session-only data (cleared on close)
sessionStorage.setItem('session_token', JSON.stringify(encryptedToken));
// For less sensitive data, still avoid localStorage if possible
// Use HttpOnly cookies for session tokens when feasible
}
async encrypt(data) {
const iv = crypto.getRandomValues(new Uint8Array(12));
const encoded = new TextEncoder().encode(data);
const encrypted = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
this.cryptoKey,
encoded
);
return {
iv: Array.from(iv),
data: Array.from(new Uint8Array(encrypted))
};
}
async decrypt(encrypted) {
const decrypted = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv: new Uint8Array(encrypted.iv) },
this.cryptoKey,
new Uint8Array(encrypted.data)
);
return new TextDecoder().decode(decrypted);
}
async getOrCreateKey() {
// In production, key management needs careful consideration
// This is a simplified example
return await crypto.subtle.generateKey(
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt']
);
}
}
// Better: Use HttpOnly secure cookies for session tokens
// Set by server: Set-Cookie: session=xyz; HttpOnly; Secure; SameSite=Strict
// Fixed: Using DPAPI and secure configuration
using System.Security.Cryptography;
using Microsoft.Extensions.Configuration;
using Azure.Security.KeyVault.Secrets;
public class FixedDbConfig
{
// Fixed: Use environment variables or secret management
public static string GetConnectionString()
{
// Option 1: Environment variables
string password = Environment.GetEnvironmentVariable("DB_PASSWORD");
// Option 2: Azure Key Vault
var client = new SecretClient(
new Uri("https://mykeyvault.vault.azure.net/"),
new DefaultAzureCredential());
KeyVaultSecret secret = client.GetSecret("db-password");
password = secret.Value;
// Option 3: DPAPI for local storage
// password = DecryptWithDpapi(encryptedPassword);
return $"Server=db;User=admin;Password={password};";
}
public static void SaveEncryptedPassword(string password)
{
// Fixed: Encrypt with DPAPI before storage
byte[] passwordBytes = Encoding.UTF8.GetBytes(password);
byte[] encryptedBytes = ProtectedData.Protect(
passwordBytes,
null,
DataProtectionScope.CurrentUser
);
// Store encrypted bytes
File.WriteAllBytes(GetSecureConfigPath(), encryptedBytes);
}
private static string GetSecureConfigPath()
{
string appData = Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData);
return Path.Combine(appData, "MyApp", "db_creds.bin");
}
}
// Fixed: Secure session and cookie handling
<?php
function fixed_remember_me($userId) {
// Fixed: Generate secure random token
$token = bin2hex(random_bytes(32));
$tokenHash = password_hash($token, PASSWORD_DEFAULT);
// Store hash in database, not the token itself
store_remember_token($userId, $tokenHash, time() + 86400 * 30);
// Fixed: HttpOnly, Secure, SameSite cookies
setcookie('remember_token', $token, [
'expires' => time() + 86400 * 30,
'path' => '/',
'secure' => true, // HTTPS only
'httponly' => true, // Not accessible via JavaScript
'samesite' => 'Strict' // CSRF protection
]);
}
// Fixed: Secure session configuration
function configure_secure_session() {
// Use private session directory
$sessionDir = '/var/lib/myapp/sessions';
if (!is_dir($sessionDir)) {
mkdir($sessionDir, 0700, true);
}
session_save_path($sessionDir);
// Secure session settings
ini_set('session.cookie_httponly', 1);
ini_set('session.cookie_secure', 1);
ini_set('session.use_strict_mode', 1);
ini_set('session.cookie_samesite', 'Strict');
session_start();
}
?>
// Fixed: External secret management
package main
import (
"os"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/secretsmanager"
)
func getSecrets() (string, string, error) {
// Fixed: Use environment variables
apiKey := os.Getenv("API_KEY")
// Fixed: Or use secret manager
sess := session.Must(session.NewSession())
svc := secretsmanager.New(sess)
result, err := svc.GetSecretValue(&secretsmanager.GetSecretValueInput{
SecretId: aws.String("prod/myapp/database"),
})
if err != nil {
return "", "", err
}
databasePass := *result.SecretString
return apiKey, databasePass, nil
}
CVE Examples
- CVE-2009-2272: Password and username stored in cleartext in a cookie.
Related CWEs
- CWE-664: Improper Control of a Resource Through its Lifetime (parent)
- CWE-312: Cleartext Storage of Sensitive Information (child)
- CWE-921: Storage of Sensitive Data in a Mechanism without Access Control (child)
- CWE-311: Missing Encryption of Sensitive Data (related)
References
- MITRE Corporation. "CWE-922: Insecure Storage of Sensitive Information." https://cwe.mitre.org/data/definitions/922.html
- OWASP. "Cryptographic Storage Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html
- OWASP. "Password Storage Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html