Unprotected Storage of Credentials
Description
Unprotected Storage of Credentials occurs when software stores authentication credentials (passwords, API keys, tokens, certificates) in a location or manner that is accessible to unauthorized actors. This includes storing credentials in plaintext files, unencrypted databases, application logs, world-readable configuration files, browser local storage, or source code repositories. Even temporary storage of credentials in insecure locations creates vulnerability windows.
Risk
Credentials stored without protection are easily compromised. Configuration files and environment variables are often exposed through path traversal, LFI, or misconfiguration. Database breaches expose millions of plaintext or weakly-encrypted credentials. Credentials in logs are often accessed by broader audiences than production data. Browser storage is accessible to XSS attacks. Source code repositories frequently contain accidentally committed credentials. Any credential exposure leads directly to account compromise.
Solution
Never store credentials in plaintext. Use proper credential management systems (secrets managers, vaults). Encrypt credentials at rest with keys managed separately. Use operating system credential stores where available. Implement proper access controls on credential storage. Avoid logging credentials. Use environment variables only when combined with proper system security. Implement credential rotation. Use hardware security modules (HSM) for high-value credentials.
Common Consequences
| Impact | Details |
|---|---|
| Confidentiality | Scope: Credential Theft Exposed credentials provide direct access to protected systems and data. |
| Authentication | Scope: Account Takeover Stolen credentials enable impersonation and unauthorized access. |
| Integrity | Scope: System Compromise Privileged credentials lead to full system compromise. |
Example Code + Solution Code
Vulnerable Code
# VULNERABLE: Credentials in plaintext config file
# config.py (committed to repo!)
DATABASE_USER = "admin"
DATABASE_PASSWORD = "secretpassword123"
API_KEY = "sk_live_1234567890abcdef"
# VULNERABLE: Credentials in .env file without protection
# .env (world-readable)
DB_PASSWORD=supersecret
AWS_SECRET_KEY=abcdefghijklmnop
# VULNERABLE: Credentials in application logs
import logging
def authenticate(username, password):
logging.info(f"Login attempt: user={username}, password={password}") # Logs password!
if check_credentials(username, password):
return create_session(username)
return None
# VULNERABLE: Storing credentials in database without encryption
def save_api_credentials(user_id, api_key, api_secret):
db.execute("""
INSERT INTO api_credentials (user_id, api_key, api_secret)
VALUES (?, ?, ?)
""", (user_id, api_key, api_secret)) # Plaintext storage!
# VULNERABLE: Browser local storage
# frontend.js
function saveCredentials(username, password) {
localStorage.setItem('username', username);
localStorage.setItem('password', password); // XSS can read this!
}
// VULNERABLE: Credentials in properties file
// application.properties (in jar)
database.username=admin
database.password=secretpassword123
api.key=sk_live_1234567890
// VULNERABLE: Credentials in Java class
public class DatabaseConfig {
private static final String DB_USER = "admin";
private static final String DB_PASS = "password123"; // Hard-coded!
}
// VULNERABLE: Storing credentials in shared preferences
public class VulnerablePreferences {
public void saveCredentials(Context context, String username, String password) {
SharedPreferences prefs = context.getSharedPreferences("auth", Context.MODE_PRIVATE);
prefs.edit()
.putString("username", username)
.putString("password", password) // Plaintext on device!
.apply();
}
}
// VULNERABLE: Credential logging
public void authenticate(String username, String password) {
logger.debug("Authenticating user {} with password {}", username, password); // Logs password!
// ...
}
// VULNERABLE: Credentials in JavaScript
const config = {
dbPassword: 'secretpassword123', // In client-side code!
apiKey: 'sk_live_1234567890'
};
// VULNERABLE: localStorage for sensitive data
function login(username, password) {
// Authenticate...
localStorage.setItem('authToken', token);
localStorage.setItem('password', password); // XSS accessible!
}
// VULNERABLE: Exposing credentials in error
app.use((err, req, res, next) => {
console.error('Error:', {
error: err,
config: {
dbPassword: process.env.DB_PASSWORD, // Logged!
apiKey: process.env.API_KEY
}
});
res.status(500).send('Error');
});
Fixed Code
# SAFE: Using secrets manager
import boto3
from functools import lru_cache
@lru_cache(maxsize=1)
def get_database_credentials():
"""Get credentials from AWS Secrets Manager."""
client = boto3.client('secretsmanager')
response = client.get_secret_value(SecretId='prod/database/credentials')
import json
return json.loads(response['SecretString'])
def get_db_connection():
creds = get_database_credentials()
return connect(
host=creds['host'],
user=creds['username'],
password=creds['password']
)
# SAFE: HashiCorp Vault
import hvac
def get_api_key():
"""Get API key from Vault."""
client = hvac.Client(url=os.environ['VAULT_ADDR'])
client.token = os.environ['VAULT_TOKEN']
secret = client.secrets.kv.v2.read_secret_version(path='api/production')
return secret['data']['data']['api_key']
# SAFE: Encrypted credential storage
from cryptography.fernet import Fernet
import keyring
def store_credentials_encrypted(service, username, password):
"""Store credentials using OS keyring."""
keyring.set_password(service, username, password)
def get_credentials_encrypted(service, username):
"""Retrieve credentials from OS keyring."""
return keyring.get_password(service, username)
# SAFE: Proper logging without credentials
import logging
def authenticate(username, password):
logging.info(f"Login attempt for user: {username}") # No password!
if check_credentials(username, password):
logging.info(f"Login successful for user: {username}")
return create_session(username)
else:
logging.warning(f"Login failed for user: {username}")
return None
# SAFE: Encrypted storage in database
from cryptography.fernet import Fernet
# Key should come from KMS or environment
ENCRYPTION_KEY = os.environ['CREDENTIAL_ENCRYPTION_KEY']
def save_api_credentials_safe(user_id, api_key, api_secret):
cipher = Fernet(ENCRYPTION_KEY)
encrypted_key = cipher.encrypt(api_key.encode())
encrypted_secret = cipher.encrypt(api_secret.encode())
db.execute("""
INSERT INTO api_credentials (user_id, api_key, api_secret)
VALUES (?, ?, ?)
""", (user_id, encrypted_key, encrypted_secret))
def get_api_credentials_safe(user_id):
cipher = Fernet(ENCRYPTION_KEY)
row = db.execute("""
SELECT api_key, api_secret FROM api_credentials WHERE user_id = ?
""", (user_id,)).fetchone()
return {
'api_key': cipher.decrypt(row[0]).decode(),
'api_secret': cipher.decrypt(row[1]).decode()
}
// SAFE: Using secrets manager in Java
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
import software.amazon.awssdk.services.secretsmanager.model.GetSecretValueRequest;
public class SecureCredentials {
private final SecretsManagerClient secretsClient;
public SecureCredentials() {
this.secretsClient = SecretsManagerClient.create();
}
public DatabaseCredentials getDatabaseCredentials() {
GetSecretValueRequest request = GetSecretValueRequest.builder()
.secretId("prod/database/credentials")
.build();
String secretString = secretsClient.getSecretValue(request).secretString();
return new ObjectMapper().readValue(secretString, DatabaseCredentials.class);
}
}
// SAFE: Android encrypted storage
import androidx.security.crypto.EncryptedSharedPreferences;
import androidx.security.crypto.MasterKey;
public class SecurePreferences {
private SharedPreferences encryptedPrefs;
public SecurePreferences(Context context) throws Exception {
MasterKey masterKey = new MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build();
encryptedPrefs = EncryptedSharedPreferences.create(
context,
"secure_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
}
public void saveToken(String token) {
encryptedPrefs.edit().putString("auth_token", token).apply();
}
public String getToken() {
return encryptedPrefs.getString("auth_token", null);
}
}
// SAFE: Credential logging sanitization
public void authenticate(String username, String password) {
// Never log passwords!
logger.info("Authenticating user: {}", username);
try {
User user = authService.authenticate(username, password);
logger.info("Authentication successful for user: {}", username);
} catch (AuthenticationException e) {
logger.warn("Authentication failed for user: {}", username);
throw e;
}
}
// SAFE: Spring Cloud Config encryption
// application.yml
/*
spring:
datasource:
password: '{cipher}AgBK1C2g...' # Encrypted by Spring Cloud Config Server
*/
// SAFE: Node.js secrets management
const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');
const client = new SecretsManagerClient({ region: 'us-east-1' });
async function getDbPassword() {
const command = new GetSecretValueCommand({
SecretId: 'prod/database/credentials'
});
const response = await client.send(command);
const secret = JSON.parse(response.SecretString);
return secret.password;
}
// SAFE: Environment variables with dotenv (not committed)
// .env.example (committed - template only)
/*
DB_PASSWORD=
API_KEY=
JWT_SECRET=
*/
// .env (NOT committed, in .gitignore)
// DB_PASSWORD=actual_password
require('dotenv').config();
const dbPassword = process.env.DB_PASSWORD;
if (!dbPassword) {
throw new Error('DB_PASSWORD environment variable required');
}
// SAFE: Browser storage with encryption
const CryptoJS = require('crypto-js');
class SecureStorage {
constructor(encryptionKey) {
this.key = encryptionKey; // Should come from secure source
}
setItem(name, value) {
const encrypted = CryptoJS.AES.encrypt(
JSON.stringify(value),
this.key
).toString();
sessionStorage.setItem(name, encrypted); // Use sessionStorage, not localStorage
}
getItem(name) {
const encrypted = sessionStorage.getItem(name);
if (!encrypted) return null;
const decrypted = CryptoJS.AES.decrypt(encrypted, this.key);
return JSON.parse(decrypted.toString(CryptoJS.enc.Utf8));
}
}
// SAFE: Token storage (avoid storing passwords)
// Only store tokens, never passwords in browser
function handleLogin(token) {
// Use httpOnly cookies set by server instead!
// If must use JS:
sessionStorage.setItem('token', token); // Session only, not persistent
// Better: Let server set httpOnly cookie
}
// SAFE: Sanitized error logging
app.use((err, req, res, next) => {
// Log error without sensitive data
const sanitizedError = {
message: err.message,
path: req.path,
method: req.method,
timestamp: new Date().toISOString()
// No credentials or config!
};
console.error('Error:', sanitizedError);
res.status(500).json({ error: 'Internal server error' });
});
Exploited in the Wild
GitHub Credential Exposure (Ongoing)
Millions of credentials have been found in public GitHub repositories, leading to AWS account compromises, database breaches, and system takeovers.
Uber 2016 Breach
Attackers found AWS credentials in a private GitHub repository, leading to access of 57 million user records.
Docker Hub Credential Exposure
API tokens stored in public Docker images have been used to compromise container registries.
Tools to test/exploit
-
TruffleHog — find credentials in Git repos.
-
GitLeaks — scan for secrets.
-
grep/ripgrep — search for credential patterns.
-
Snaffler — Windows credential hunting.
CVE Examples
-
CVE-2019-5736 — Container credential exposure.
-
CVE-2021-22893 — Pulse Secure credential storage.
-
CVE-2020-5741 — Plex plaintext credential storage.
References
-
MITRE. "CWE-256: Unprotected Storage of Credentials." https://cwe.mitre.org/data/definitions/256.html
-
OWASP. "Credential Management Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Credential_Management_Cheat_Sheet.html