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

AuswirkungDetails
ZugriffskontrolleUmfang: 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.
VertraulichkeitUmfang: 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ätUmfang: 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


CVE-Beispiele


Referenzen

  1. MITRE Corporation. "CWE-341: Predictable from Observable State." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/341.html

  2. OWASP Foundation. "Insecure Randomness." https://owasp.org/www-community/vulnerabilities/Insecure_Randomness

  3. NIST. "Recommendation for Random Number Generation." SP 800-90A. https://csrc.nist.gov/publications/detail/sp/800-90a/rev-1/final