Verwendung von Cache mit sensiblen Informationen
Beschreibung
Verwendung von Cache mit sensiblen Informationen ist eine Schwachstelle, bei der Code einen Cache verwendet, der sensible Informationen enthält, aber der Cache von Akteuren außerhalb der beabsichtigten Kontrollsphäre gelesen werden kann. Anwendungen nutzen Caches zur Effizienzsteigerung bei der Interaktion mit entfernten Entitäten oder bei ressourcenintensiven Operationen. Diese Caches speichern Objekte, Threads, Verbindungen, Seiten, Finanzdaten, Passwörter oder ähnliche Ressourcen, um die Initialisierungszeit zu minimieren. Wenn unbefugte Parteien durch unzureichende Zugriffskontrollen, unsichere Speicherung oder Cache-Offenlegungsmechanismen auf den Cache zugreifen können, können Angreifer sensible Daten abrufen.
Risiko
Das Cachen sensibler Daten ohne angemessenen Schutz erzeugt Vertraulichkeitsrisiken. Browser-Caches können Authentifizierungsanmeldedaten, Sitzungstokens oder persönliche Daten speichern, die nach dem Abmelden bestehen bleiben. Geteilte Server-Caches können die Daten eines Benutzers einem anderen offenlegen. CDN- oder Proxy-Caches könnten sensible API-Antworten speichern, die für andere Benutzer zugänglich sind. In-Memory-Caches können durch Speicheroffenlegungs-Schwachstellen ausgegeben werden. Datenbank-Abfrage-Caches können sensible Abfrageergebnisse über ihre beabsichtigte Lebensdauer hinaus beibehalten. Cache-Timing-Angriffe können das Vorhandensein oder Fehlen sensibler Daten offenlegen. Das Risiko steigt in Mehrmandanten-Umgebungen, wo Cache-Isolation kritisch ist.
Lösung
Implementieren Sie starke Zugriffskontrollen für alle Cache-Systeme. Vermeiden Sie das Cachen sensibler Daten wenn möglich - verwenden Sie "no-store" und "no-cache" Direktiven für sensible HTTP-Antworten. Verschlüsseln Sie gecachte Inhalte im Ruhezustand und während der Übertragung. Implementieren Sie ordnungsgemäße Cache-Isolation in Mehrmandanten-Systemen. Verwenden Sie kurze TTLs für gecachte sensible Daten mit ordnungsgemäßer Invalidierung. Löschen Sie Caches bei Abmeldung und Sitzungsbeendigung. Wenden Sie das Prinzip der geringsten Privilegien auf Cache-Zugriff an. Verwenden Sie separate Cache-Instanzen für sensible und nicht-sensible Daten. Implementieren Sie Cache-Busting für sensible Ressourcen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Vertraulichkeit | Bereich: Vertraulichkeit Anwendungsdaten lesen - Unbefugter Zugriff auf gecachte sensible Informationen kann Anmeldedaten, persönliche Daten, Finanzinformationen oder andere vertrauliche Daten an Angreifer offenlegen. |
Beispielcode
Verwundbarer Code
// Verwundbar: Cachen sensibler Daten ohne Schutz
public class VulnerableCacheService {
// Verwundbar: Gemeinsamer Cache für alle Benutzer
private static final Map<String, Object> globalCache = new HashMap<>();
public void cacheUserData(String userId, UserProfile profile) {
// Verwundbar: Sensible Daten ohne Verschlüsselung gecacht
globalCache.put("profile:" + userId, profile);
// Verwundbar: Passwort im Klartext gecacht
globalCache.put("credentials:" + userId, profile.getPassword());
// Verwundbar: Finanzdaten gecacht
globalCache.put("balance:" + userId, profile.getAccountBalance());
}
public UserProfile getCachedProfile(String userId) {
// Verwundbar: Keine Zugriffskontrollprüfung
return (UserProfile) globalCache.get("profile:" + userId);
}
// Verwundbar: Jeder kann gecachte Benutzer aufzählen
public Set<String> getCachedUserIds() {
return globalCache.keySet();
}
}
// Verwundbar: Datenbank-Abfrage-Cache mit sensiblen Daten
public class VulnerableQueryCache {
private final Cache<String, ResultSet> queryCache =
CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS) // Länge TTL für sensible Daten
.build();
public ResultSet executeQuery(String query) {
// Verwundbar: Cached Abfragen mit sensiblen Daten
return queryCache.get(query, () -> database.execute(query));
}
// "SELECT * FROM users WHERE ssn = '123-45-6789'" wird gecacht!
}
# Verwundbar: Flask-Anwendung mit unsicherem Caching
from flask import Flask, request, session
from functools import lru_cache
app = Flask(__name__)
# Verwundbar: Globaler Cache für alle Anfragen zugänglich
user_data_cache = {}
@lru_cache(maxsize=1000)
def get_user_profile(user_id):
# Verwundbar: Sensible Profildaten global gecacht
# Jeder Benutzer kann potenziell über Cache-Timing darauf zugreifen
return database.get_user(user_id)
@app.route('/profile/<user_id>')
def vulnerable_profile(user_id):
# Verwundbar: Keine Autorisierungsprüfung vor Cache-Abfrage
profile = get_user_profile(user_id)
return jsonify(profile)
# Verwundbar: Sitzungsdaten unsicher cachen
@app.route('/login', methods=['POST'])
def vulnerable_login():
user = authenticate(request.form['username'], request.form['password'])
if user:
# Verwundbar: Sitzungstoken in geteiltem Dict gecacht
user_data_cache[user.id] = {
'session_token': generate_token(),
'password_hash': user.password_hash, # Sensibel!
'credit_card': user.credit_card # Sehr sensibel!
}
return redirect('/dashboard')
// Verwundbar: Express.js mit unsicherem Antwort-Caching
const express = require('express');
const app = express();
// Verwundbar: Keine Cache-Control-Header für sensible Antworten
app.get('/api/user/profile', (req, res) => {
const userId = req.session.userId;
const profile = getUserProfile(userId);
// Verwundbar: Standard-Caching erlaubt Browser/Proxy-Caching
// Keine Cache-Control-Header gesetzt
res.json({
username: profile.username,
email: profile.email,
ssn: profile.ssn, // Sensibel!
creditScore: profile.creditScore // Sensibel!
});
});
// Verwundbar: API-Schlüssel im Browser gecacht
app.get('/api/config', (req, res) => {
res.json({
apiKey: process.env.API_KEY, // Geheimnis offengelegt und gecacht!
secretToken: process.env.SECRET
});
});
// Verwundbar: Geteilter Redis-Cache ohne Benutzerisolation
const redis = require('redis').createClient();
app.get('/api/transactions', async (req, res) => {
const userId = req.session.userId;
const cacheKey = `transactions`; // Verwundbar: Nicht benutzerspezifisch!
let data = await redis.get(cacheKey);
if (!data) {
data = await getTransactions(userId);
await redis.set(cacheKey, JSON.stringify(data), 'EX', 3600);
}
res.json(JSON.parse(data));
});
Lösungscode
// Behoben: Sicheres Caching mit Zugriffskontrollen und Verschlüsselung
public class SecureCacheService {
private final EncryptionService encryption;
private final Cache<String, byte[]> secureCache;
public SecureCacheService(EncryptionService encryption) {
this.encryption = encryption;
// Behoben: Kurze TTL und Größenbeschränkungen
this.secureCache = CacheBuilder.newBuilder()
.maximumSize(100)
.expireAfterWrite(5, TimeUnit.MINUTES) // Kurze TTL
.removalListener(this::secureClear)
.build();
}
public void cacheUserData(String userId, UserProfile profile,
SecurityContext ctx) {
// Behoben: Autorisierung überprüfen
if (!ctx.canAccessUser(userId)) {
throw new UnauthorizedException("Kann keine Daten für diesen Benutzer cachen");
}
// Behoben: Passwörter niemals cachen
UserProfileCached cached = new UserProfileCached(profile);
// Sensible Felder entfernen
cached.setPassword(null);
cached.setSsn(null);
// Behoben: Vor dem Cachen verschlüsseln
byte[] encrypted = encryption.encrypt(
serialize(cached),
ctx.getUserKey() // Benutzerspezifischer Verschlüsselungsschlüssel
);
// Behoben: Benutzerspezifischer Cache-Schlüssel
String cacheKey = generateSecureKey(ctx.getUserId(), "profile");
secureCache.put(cacheKey, encrypted);
}
public UserProfile getCachedProfile(String userId, SecurityContext ctx) {
// Behoben: Autorisierungsprüfung
if (!ctx.canAccessUser(userId)) {
throw new UnauthorizedException();
}
String cacheKey = generateSecureKey(ctx.getUserId(), "profile");
byte[] encrypted = secureCache.getIfPresent(cacheKey);
if (encrypted == null) {
return null;
}
// Behoben: Mit Benutzerschlüssel entschlüsseln
byte[] decrypted = encryption.decrypt(encrypted, ctx.getUserKey());
return deserialize(decrypted);
}
// Behoben: Sichere Cache-Schlüssel-Generierung
private String generateSecureKey(String userId, String type) {
return HashUtil.sha256(userId + ":" + type + ":" + secretSalt);
}
// Behoben: Sichere Bereinigung bei Entfernung
private void secureClear(RemovalNotification<String, byte[]> notification) {
byte[] value = notification.getValue();
if (value != null) {
Arrays.fill(value, (byte) 0); // Speicher nullen
}
}
// Behoben: Alle Benutzerdaten bei Abmeldung löschen
public void clearUserCache(SecurityContext ctx) {
String prefix = HashUtil.sha256(ctx.getUserId() + ":");
secureCache.asMap().keySet().removeIf(k -> k.startsWith(prefix));
}
}
# Behoben: Sicheres Caching in Flask
from flask import Flask, request, session, make_response
from functools import wraps
import redis
from cryptography.fernet import Fernet
app = Flask(__name__)
redis_client = redis.Redis()
class SecureCache:
def __init__(self, encryption_key):
self.fernet = Fernet(encryption_key)
def get(self, user_id, key):
# Behoben: Benutzerspezifischer Cache-Schlüssel
cache_key = f"user:{user_id}:{key}"
encrypted = redis_client.get(cache_key)
if encrypted:
return self.fernet.decrypt(encrypted)
return None
def set(self, user_id, key, value, ttl=300): # Behoben: Kurze TTL
cache_key = f"user:{user_id}:{key}"
encrypted = self.fernet.encrypt(value.encode())
redis_client.setex(cache_key, ttl, encrypted)
def clear_user(self, user_id):
# Behoben: Alle gecachten Daten des Benutzers löschen
pattern = f"user:{user_id}:*"
for key in redis_client.scan_iter(pattern):
redis_client.delete(key)
secure_cache = SecureCache(app.config['CACHE_KEY'])
def no_cache(f):
"""Behoben: Decorator um Caching sensibler Antworten zu verhindern"""
@wraps(f)
def decorated_function(*args, **kwargs):
response = make_response(f(*args, **kwargs))
response.headers['Cache-Control'] = 'no-store, no-cache, must-revalidate, max-age=0'
response.headers['Pragma'] = 'no-cache'
response.headers['Expires'] = '0'
return response
return decorated_function
@app.route('/profile')
@no_cache # Behoben: Caching verhindern
def secure_profile():
user_id = session.get('user_id')
if not user_id:
return "Nicht autorisiert", 401
profile = get_user_profile(user_id)
# Behoben: Sensible Daten aus Antwort entfernen
safe_profile = {
'username': profile['username'],
'email': profile['email'],
# SSN und Kredit-Score nicht enthalten
}
return jsonify(safe_profile)
@app.route('/logout', methods=['POST'])
def logout():
user_id = session.get('user_id')
if user_id:
# Behoben: Benutzer-Cache bei Abmeldung löschen
secure_cache.clear_user(user_id)
session.clear()
return redirect('/')
// Behoben: Express.js mit ordnungsgemäßen Cache-Kontrollen
const express = require('express');
const helmet = require('helmet');
const app = express();
// Behoben: Helmet für Sicherheits-Header verwenden
app.use(helmet());
// Behoben: Middleware um Caching sensibler Routen zu verhindern
function noCacheSensitive(req, res, next) {
res.set({
'Cache-Control': 'no-store, no-cache, must-revalidate, proxy-revalidate',
'Pragma': 'no-cache',
'Expires': '0',
'Surrogate-Control': 'no-store'
});
next();
}
// Behoben: Auf sensible Endpunkte anwenden
app.get('/api/user/profile', noCacheSensitive, (req, res) => {
const userId = req.session.userId;
if (!userId) {
return res.status(401).json({ error: 'Nicht autorisiert' });
}
const profile = getUserProfile(userId);
// Behoben: Nur nicht-sensible Daten zurückgeben
res.json({
username: profile.username,
displayName: profile.displayName
// Keine SSN, Kredit-Score oder Finanzdaten
});
});
// Behoben: Separater Cache pro Benutzer mit Verschlüsselung
const crypto = require('crypto');
class SecureUserCache {
constructor(redisClient, encryptionKey) {
this.redis = redisClient;
this.key = encryptionKey;
}
async get(userId, dataKey) {
const cacheKey = this.generateKey(userId, dataKey);
const encrypted = await this.redis.get(cacheKey);
if (encrypted) {
return this.decrypt(encrypted);
}
return null;
}
async set(userId, dataKey, data, ttlSeconds = 300) {
const cacheKey = this.generateKey(userId, dataKey);
const encrypted = this.encrypt(JSON.stringify(data));
await this.redis.setex(cacheKey, ttlSeconds, encrypted);
}
async clearUser(userId) {
const pattern = `user:${userId}:*`;
const keys = await this.redis.keys(pattern);
if (keys.length > 0) {
await this.redis.del(...keys);
}
}
generateKey(userId, dataKey) {
const hash = crypto.createHmac('sha256', this.key)
.update(`${userId}:${dataKey}`)
.digest('hex');
return `user:${userId}:${hash}`;
}
encrypt(data) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-gcm', this.key, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return iv.toString('hex') + ':' + authTag.toString('hex') + ':' + encrypted;
}
decrypt(data) {
const [ivHex, authTagHex, encrypted] = data.split(':');
const iv = Buffer.from(ivHex, 'hex');
const authTag = Buffer.from(authTagHex, 'hex');
const decipher = crypto.createDecipheriv('aes-256-gcm', this.key, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return JSON.parse(decrypted);
}
}
CVE-Beispiele
Keine spezifischen CVEs sind in der MITRE-Datenbank für dieses CWE gelistet. Das Schwachstellenmuster ist jedoch dokumentiert in:
- CAPEC-204: Abheben sensibler Daten eingebettet in Cache
- Verschiedene Web-Anwendungs-Sicherheitshinweise
Referenzen
-
MITRE. "CWE-524: Use of Cache Containing Sensitive Information." https://cwe.mitre.org/data/definitions/524.html
-
OWASP. "Session Management Cheat Sheet - Cache Control."
-
MDN Web Docs. "HTTP Caching."