Vorhersagbar aus beobachtbarem Zustand
Beschreibung
Vorhersagbar aus beobachtbarem Zustand ist eine Schwachstelle, die auftritt, wenn Zahlen oder Identifikatoren von einem Angreifer vorhergesagt werden können, der System- oder Netzwerkbedingungen wie Zeit, Prozess-ID, Netzwerkstatistiken oder andere zugängliche Zustandsinformationen beobachtet. Im Gegensatz zu rein sequenziellen Identifikatoren oder solchen, die von festen Seeds abgeleitet werden, beinhalten diese Werte beobachtbaren Systemzustand, der scheinbar Zufälligkeit hinzufügt, aber für Angreifer vorhersagbar bleibt, die diesen Zustand beobachten oder schätzen können. Häufige Muster umfassen die Verwendung von Systemzeit (auch mit hoher Präzision), Prozess- oder Thread-IDs, Speicheradressen, Netzwerkpaketsequenzen oder Kombinationen dieser Werte. Die scheinbare Komplexität des Generierungsschemas erzeugt ein falsches Gefühl von Sicherheit.
Risiko
Identifikatoren basierend auf beobachtbarem Zustand erzeugen Schwachstellen, bei denen Angreifer den Vorhersageraum dramatisch einschränken können, indem sie Systeminformationen sammeln. Ein Angreifer im selben Netzwerk kann Timing-Muster beobachten. Prozess-IDs sind auf 16-Bit-Bereiche begrenzt und können über verschiedene Seitenkanale entdeckt werden. Speicheradressen bieten trotz ASLR nur begrenzte zusätzliche Entropie. Zeitstempel, selbst mit Mikrosekunden-Präzision, können von Angreifern geschätzt werden, die wissen, wann Operationen stattfanden. Dies ermöglicht Session-Hijacking, wenn Session-Tokens den Login-Zeitpunkt enthalten, Dateizugriff, wenn Dateinamen Erstellungszeitstempel enthalten, DNS-Spoofing, wenn Query-IDs vom Systemzustand abgeleitet werden, und Authentifizierungs-Bypass, wenn Tokens aus beobachtbaren Werten rekonstruiert werden können. Die Schwachstelle ist besonders gefährlich, da Entwickler glauben können, der Identifikator sei ausreichend zufällig, während Angreifer ihn rekonstruieren können.
Lösung
Leiten Sie niemals sicherheitsrelevante Identifikatoren von beobachtbarem Systemzustand ab. Verwenden Sie kryptografisch sichere Zufallszahlengeneratoren, die nicht von vorhersagbaren Eingaben abhängen. Für Session-Tokens generieren Sie 128+ Bits Entropie aus CSPRNGs. Für Dateinamen, die Eindeutigkeit erfordern, kombinieren Sie Zufallswerte mit Zeitstempeln, anstatt nur Zeitstempel zu verwenden. Implementieren Sie indirekte Referenzen, die externe Identifikatoren (den Benutzern angezeigt) auf interne Identifikatoren (vom System verwendet) abbilden. Wenn Systemzustand aus betrieblichen Gründen (wie Logging oder Debugging) einbezogen werden muss, stellen Sie sicher, dass die sicherheitskritische Komponente weiterhin von einem CSPRNG abgeleitet wird. Führen Sie Bedrohungsmodellierung durch, um zu identifizieren, welchen Systemzustand Angreifer beobachten können, und stellen Sie sicher, dass die Identifikatorgenerierung nicht auf diesen Informationen basiert.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Umfang: Zugriffskontrolle Angreifer, die Systemzustand beobachten oder schätzen können, können Authentifizierungs-Tokens, Session-IDs und Zugangsdaten vorhersagen und so Authentifizierung und Autorisierung umgehen. |
| Vertraulichkeit | Umfang: Vertraulichkeit Vorhersagbare Dateinamen oder Ressourcen-Identifikatoren basierend auf beobachtbarem Zustand ermöglichen unbefugten Zugriff auf Dateien und Daten, die von anderen Benutzern hochgeladen oder erstellt wurden. |
| Integrität | Umfang: Integrität Vorhersagbare Transaktions-IDs oder Sequenznummern ermöglichen Request-Forgery, Replay-Angriffe und unbefugte Änderungen. |
Beispielcode
Anfälliger Code (Python/Java)
Die folgenden Beispiele demonstrieren Identifikatoren, die aus beobachtbarem Zustand vorhersagbar sind:
# Anfällig: Identifikatoren basierend auf beobachtbarem Zustand
import time
import os
import socket
# Anfällig: Zeitstempel-basiertes Session-Token
def vulnerable_session_token():
# Anfällig: Zeit ist für Angreifer beobachtbar
timestamp = int(time.time() * 1000000) # Mikrosekunden
return format(timestamp, '016x')
# Anfällig: PID + Zeit Kombination
def vulnerable_combined_token():
# Anfällig: Beide Werte sind beobachtbar
pid = os.getpid()
timestamp = int(time.time())
return f"{pid:05d}-{timestamp}"
# Anfällig: Netzwerk-Interface-basierter Seed
def vulnerable_network_seed():
import random
# Anfällig: Hostname ist ermittelbar
hostname = socket.gethostname()
random.seed(hash(hostname) ^ int(time.time()))
return random.getrandbits(64)
# Anfällig: Speicheradresse als Identifikator
def vulnerable_address_id():
obj = object()
# Anfällig: Adressraum ist begrenzt, ASLR partiell
return format(id(obj), '016x')
# Anfällig: Thread-ID-Einbeziehung
import threading
def vulnerable_thread_token():
# Anfällig: Thread-IDs sind aufzählbar
thread_id = threading.current_thread().ident
timestamp = int(time.time() * 1000)
return f"T{thread_id:08x}-{timestamp}"
# Anfällig: System-Uptime-basiert
def vulnerable_uptime_token():
# Anfällig: Uptime kann geschätzt werden
with open('/proc/uptime', 'r') as f:
uptime = float(f.read().split()[0])
return format(int(uptime * 1000000), '016x')
# Anfällig: Login-Zeit im Reset-Token
def vulnerable_reset_token(user_id, login_time):
# Anfällig: Beide Werte sind bekannt oder beobachtbar
import hashlib
data = f"{user_id}:{login_time}"
return hashlib.sha256(data.encode()).hexdigest()[:32]
// Anfällig: Identifikatoren basierend auf beobachtbarem Zustand in Java
import java.util.Random;
import java.net.InetAddress;
public class VulnerableObservableState {
// Anfällig: System.currentTimeMillis() basiert
public String vulnerableTimeToken() {
// Anfällig: Zeit ist beobachtbar
return Long.toHexString(System.currentTimeMillis());
}
// Anfällig: nanoTime-basiert (erscheint zufälliger)
public String vulnerableNanoToken() {
// Anfällig: Immer noch auf beobachtbarer Zeit basierend
return Long.toHexString(System.nanoTime());
}
// Anfällig: Thread-ID-Einbeziehung
public String vulnerableThreadToken() {
// Anfällig: Thread-IDs sind begrenzt und beobachtbar
long threadId = Thread.currentThread().getId();
long time = System.currentTimeMillis();
return String.format("%08x%016x", threadId, time);
}
// Anfällig: Object-Hashcode
public String vulnerableHashToken() {
Object obj = new Object();
// Anfällig: Hashcode ist aus Speicherlayout deterministisch
return Integer.toHexString(obj.hashCode());
}
// Anfällig: Laufzeit-Speicherzustand
public String vulnerableMemoryToken() {
Runtime runtime = Runtime.getRuntime();
// Anfällig: Speicherstatistiken sind abfragbar
long free = runtime.freeMemory();
long total = runtime.totalMemory();
return Long.toHexString(free ^ total ^ System.currentTimeMillis());
}
// Anfällig: Host-basierter Seed
public String vulnerableHostToken() throws Exception {
// Anfällig: Hostname und IP sind ermittelbar
InetAddress addr = InetAddress.getLocalHost();
String host = addr.getHostAddress();
return Integer.toHexString(host.hashCode());
}
// Anfällig: Kombinieren schwacher beobachtbarer Quellen
public String vulnerableCombinedToken() {
long seed = System.currentTimeMillis() ^
Thread.currentThread().getId() ^
Runtime.getRuntime().freeMemory();
Random rand = new Random(seed);
return Long.toHexString(rand.nextLong());
}
}
// Anfällig: Identifikatoren basierend auf beobachtbarem Zustand in C
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <sys/types.h>
#include <pthread.h>
// Anfällig: Zeit-basiertes Token
void vulnerable_time_token(char *buffer, size_t len) {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
// Anfällig: Zeit ist beobachtbar
snprintf(buffer, len, "%lx%09lx",
(long)ts.tv_sec, ts.tv_nsec);
}
// Anfällig: PID-basierter Identifikator
void vulnerable_pid_token(char *buffer, size_t len) {
// Anfällig: PID ist aufzählbar (15-16 Bits)
snprintf(buffer, len, "P%05d-%ld", getpid(), time(NULL));
}
// Anfällig: Thread-ID-Einbeziehung
void vulnerable_thread_token(char *buffer, size_t len) {
pthread_t tid = pthread_self();
// Anfällig: Thread-IDs sind beobachtbar
snprintf(buffer, len, "T%lx-%ld", (unsigned long)tid, time(NULL));
}
// Anfällig: Stack-Adress-Entropie
void vulnerable_stack_token(char *buffer, size_t len) {
int stack_var;
// Anfällig: ASLR bietet begrenzte Entropie
snprintf(buffer, len, "%p-%ld", (void*)&stack_var, time(NULL));
}
// Anfällig: Boot-Zeit basiert
void vulnerable_boot_token(char *buffer, size_t len) {
FILE *f = fopen("/proc/uptime", "r");
double uptime;
fscanf(f, "%lf", &uptime);
fclose(f);
// Anfällig: Uptime kann geschätzt werden
snprintf(buffer, len, "%016lx", (unsigned long)(uptime * 1000000));
}
// Anfällig: Netzwerkstatistiken basiert
void vulnerable_network_token(char *buffer, size_t len) {
FILE *f = fopen("/proc/net/dev", "r");
long bytes_rx = 0;
// Netzwerkstatistiken parsen (vereinfacht)
// Anfällig: Netzwerkzähler sind im selben Netzwerk beobachtbar
snprintf(buffer, len, "%016lx", bytes_rx ^ time(NULL));
fclose(f);
}
Korrigierter Code (Python/Java)
# Korrigiert: Identifikatoren nicht basierend auf beobachtbarem Zustand
import secrets
import os
import uuid
# Korrigiert: Kryptografisch zufälliges Session-Token
def secure_session_token():
# Korrigiert: Reiner Zufall aus CSPRNG
return secrets.token_hex(16)
# Korrigiert: Keine PID- oder Zeit-Abhängigkeit
def secure_token():
# Korrigiert: Systemzustand wird nicht verwendet
return secrets.token_urlsafe(32)
# Korrigiert: Zufälliger Dateiname
def secure_filename(extension):
# Korrigiert: Zufällig, nicht zeit-basiert
random_part = secrets.token_hex(16)
return f"file_{random_part}{extension}"
# Korrigiert: UUID für eindeutige Identifikatoren
def secure_uuid_token():
# Korrigiert: UUID4 verwendet sicheren Zufall
return str(uuid.uuid4())
# Korrigiert: Wenn Betriebsinfo benötigt wird, getrennt von Sicherheit halten
def secure_with_context(operation_name):
import time
# Betriebspräfix (nicht sicherheitsrelevant)
prefix = f"{operation_name}_{int(time.time())}"
# Sicherheitstoken ist rein zufällig
token = secrets.token_hex(16)
return f"{prefix}_{token}"
# Korrigiert: Sicheres Reset-Token (keine Benutzerinfo)
def secure_reset_token():
# Korrigiert: Reiner Zufall, nicht aus beobachtbarem Zustand abgeleitet
return secrets.token_urlsafe(32)
# Korrigiert: Thread-sicheres sicheres Token
import threading
_lock = threading.Lock()
def secure_thread_safe_token():
# Korrigiert: Thread-Sicherheit ohne Thread-ID zu verwenden
with _lock:
return secrets.token_hex(16)
// Korrigiert: Identifikatoren nicht basierend auf beobachtbarem Zustand in Java
import java.security.SecureRandom;
import java.util.UUID;
import java.util.Base64;
public class SecureNonObservable {
private static final SecureRandom secureRandom = new SecureRandom();
// Korrigiert: Reines zufälliges Token
public String secureToken() {
// Korrigiert: Nicht auf Zeit oder Systemzustand basierend
byte[] bytes = new byte[16];
secureRandom.nextBytes(bytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
}
// Korrigiert: Keine nanoTime-Abhängigkeit
public String secureTimelessToken() {
// Korrigiert: Nur Zufall
byte[] bytes = new byte[32];
secureRandom.nextBytes(bytes);
return bytesToHex(bytes);
}
// Korrigiert: Keine Thread-ID im Token
public String secureThreadSafeToken() {
// Korrigiert: Thread-ID wird nicht verwendet
byte[] bytes = new byte[16];
secureRandom.nextBytes(bytes);
return bytesToHex(bytes);
}
// Korrigiert: UUID anstelle von Hashcode
public String secureUniqueId() {
// Korrigiert: UUID4 verwendet SecureRandom
return UUID.randomUUID().toString();
}
// Korrigiert: Keine Speicherzustand-Abhängigkeit
public String secureMemoryIndependentToken() {
// Korrigiert: Nicht auf Laufzeitzustand basierend
byte[] bytes = new byte[32];
secureRandom.nextBytes(bytes);
return bytesToHex(bytes);
}
// Korrigiert: Keine Host-Abhängigkeit
public String secureHostIndependentToken() {
// Korrigiert: Nicht auf Hostname oder IP basierend
byte[] bytes = new byte[16];
secureRandom.nextBytes(bytes);
return bytesToHex(bytes);
}
// Korrigiert: Kombinieren von Betriebskontext mit sicherem Zufall
public String secureWithContext(String context) {
// Kontext ist nur für Logging/Debugging
String secureToken = UUID.randomUUID().toString();
// Kontext nicht in sicherheitskritischen Verwendungen offenlegen
return secureToken;
}
private String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
}
// Korrigiert: Identifikatoren nicht basierend auf beobachtbarem Zustand in C
#include <openssl/rand.h>
#include <stdio.h>
#include <string.h>
// Korrigiert: Reines zufälliges Token
int secure_token(char *buffer, size_t len) {
unsigned char random_bytes[16];
// Korrigiert: Nur kryptografischer Zufall
if (RAND_bytes(random_bytes, sizeof(random_bytes)) != 1) {
return -1;
}
for (size_t i = 0; i < sizeof(random_bytes) && i * 2 < len - 1; i++) {
sprintf(buffer + (i * 2), "%02x", random_bytes[i]);
}
return 0;
}
// Korrigiert: Keine PID-Abhängigkeit
int secure_session_id(char *buffer, size_t len) {
// Korrigiert: Nicht auf PID oder Zeit basierend
unsigned char random_bytes[32];
if (RAND_bytes(random_bytes, sizeof(random_bytes)) != 1) {
return -1;
}
for (size_t i = 0; i < sizeof(random_bytes) && i * 2 < len - 1; i++) {
sprintf(buffer + (i * 2), "%02x", random_bytes[i]);
}
return 0;
}
// Korrigiert: Sicherer Temp-Dateiname
int secure_temp_filename(char *buffer, size_t len) {
unsigned char random_bytes[8];
// Korrigiert: Zufällig, nicht zeit-basiert
if (RAND_bytes(random_bytes, sizeof(random_bytes)) != 1) {
return -1;
}
char hex[17];
for (int i = 0; i < 8; i++) {
sprintf(hex + (i * 2), "%02x", random_bytes[i]);
}
snprintf(buffer, len, "/tmp/secure_%s.tmp", hex);
return 0;
}
// Korrigiert: UUID-ähnlicher Identifikator
int secure_uuid(char *buffer, size_t len) {
unsigned char random_bytes[16];
if (RAND_bytes(random_bytes, sizeof(random_bytes)) != 1) {
return -1;
}
// Als UUID formatieren
snprintf(buffer, len,
"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
random_bytes[0], random_bytes[1], random_bytes[2], random_bytes[3],
random_bytes[4], random_bytes[5], random_bytes[6], random_bytes[7],
random_bytes[8], random_bytes[9], random_bytes[10], random_bytes[11],
random_bytes[12], random_bytes[13], random_bytes[14], random_bytes[15]);
return 0;
}
Die Korrektur verwendet kryptografisch sichere Zufallsgenerierung unabhängig von beobachtbarem Systemzustand.
Ausgenutzt in der Praxis
E-Commerce Zeitstempel-Authentifizierung (CVE-2024-48445)
Eine E-Commerce-Anwendung verwendete erratbare Zeitstempel in schwachen Authentifizierungsmechanismen, was unbefugten Zugriff ermöglichte.
Mail-Server vorhersagbare Dateinamen (CVE-2002-0389)
Ein Mail-Server speicherte Nachrichten mit vorhersagbaren Dateinamen basierend auf Zeit und Sequenz, was unbefugten Zugriff ermöglichte.
Tools zum Testen/Ausnutzen
-
Burp Suite — Analysiert Muster in Identifikatoren über Zeit.
-
Timing-Analyse-Tools — Korreliert beobachtete Tokens mit Systemzeit.
-
Prozess-Enumeration — Entdeckt PID-Muster in Multi-Prozess-Systemen.
CVE-Beispiele
-
CVE-2024-48445 — Zeitstempel-basierte schwache Authentifizierung.
-
CVE-2002-0389 — Vorhersagbare Mail-Dateinamen.
-
CVE-2000-0335 — DNS-Resolver vorhersagbare IDs.
Referenzen
-
MITRE Corporation. "CWE-341: Predictable from Observable State." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/341.html
-
OWASP Foundation. "Insecure Randomness." https://owasp.org/www-community/vulnerabilities/Insecure_Randomness
-
NIST. "Recommendation for Random Number Generation." SP 800-90A. https://csrc.nist.gov/publications/detail/sp/800-90a/rev-1/final