Gleicher Seed in Pseudo-Zufallszahlengenerator (PRNG)

Beschreibung

Gleicher Seed in Pseudo-Zufallszahlengenerator (PRNG) ist eine Schwachstelle, die auftritt, wenn ein PRNG mit demselben Seed-Wert über mehrere Instanzen, Ausführungen oder Systeme hinweg initialisiert wird. Da PRNGs deterministische Algorithmen sind, erzeugen identische Seeds identische Sequenzen von Ausgabewerten. Wenn derselbe Seed wiederverwendet wird, können Angreifer, die den Seed-Wert bestimmen können, alle nachfolgenden generierten Zufallszahlen vorhersagen. Diese Schwachstelle manifestiert sich häufig als hartcodierte Seeds im Quellcode, Seeds, die bei Anwendungsneustart zurückgesetzt werden, gemeinsame Seeds über verteilte Systeme oder Seeds, die aus konstanten Werten während der Initialisierung abgeleitet werden.

Risiko

Die Wiederverwendung von PRNG-Seeds erzeugt schwere und oft leicht ausnutzbare Schwachstellen. Wenn alle Instanzen einer Anwendung denselben Seed verwenden, erzeugt jede Instanz dieselbe "zufällige" Sequenz. Ein Angreifer, der den Seed von einer Instanz erhält oder errät, kann Werte über alle Instanzen vorhersagen. Dies ermöglicht Massenausnutzung, bei der die Kompromittierung der Zufälligkeit eines Systems alle Systeme kompromittiert. In Szenarien wie Kryptowährungs-Wallet-Generierung haben gleiche Seeds zum Diebstahl von Geldern geführt, wenn mehrere Wallets identische Schlüssel generierten. Session-Tokens werden über alle Benutzer hinweg vorhersagbar, Authentifizierungs-Challenges können vorberechnet werden, und Verschlüsselung bietet keinen Schutz, wenn Schlüssel identisch sind. Die Schwachstelle ist besonders gefährlich, weil sie durch normale Tests möglicherweise nicht erkannt wird - Werte erscheinen isoliert betrachtet zufällig.

Lösung

Verwenden Sie niemals PRNG-Seeds über Instanzen oder Ausführungen hinweg wieder. Generieren Sie eindeutige Seeds aus kryptografisch sicheren Entropiequellen für jede PRNG-Instanz. Verwenden Sie vom Betriebssystem bereitgestellte CSPRNGs, die das Seeding automatisch und korrekt behandeln. Implementieren Sie Seed-Management, das Frische gewährleistet: generieren Sie neue Seeds beim Start aus /dev/urandom oder Äquivalent, speichern Sie Seeds niemals persistent und implementieren Sie periodisches Reseeding. Vermeiden Sie hartcodierte Seeds im Quellcode, selbst für Testzwecke - verwenden Sie Konfiguration oder Umgebungsvariablen für Test-Seeds und stellen Sie sicher, dass sie nicht in Produktion gelangen können. Für verteilte Systeme stellen Sie sicher, dass jeder Knoten seinen eigenen unabhängigen Seed generiert, anstatt einen gemeinsamen Seed zu teilen.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleUmfang: Zugriffskontrolle

Wenn mehrere Systeme denselben PRNG-Seed verwenden, kann ein Angreifer, der die Sequenz von einem System vorhersagt, die Authentifizierung auf allen Systemen umgehen, die diesen Seed verwenden.
VertraulichkeitUmfang: Vertraulichkeit

Identische Seeds erzeugen identische Verschlüsselungsschlüssel, Nonces und IVs, was Angreifern ermöglicht, Daten über alle betroffenen Systeme zu entschlüsseln, sobald sie den Seed bestimmen.
IntegritätUmfang: Integrität

Sicherheits-Tokens, CSRF-Schutz und Integritätsprüfungen werden vorhersagbar und fälschbar, wenn sie auf PRNGs mit gleichem Seed basieren.

Beispielcode

Anfälliger Code (Python/Java)

Die folgenden Beispiele demonstrieren Schwachstellen durch gleiche Seeds:

# Anfällig: Gleicher Seed wiederverwendet
import random

# Anfällig: Hartcodierter Seed im Code
def anfällig_hartcodierter_seed():
    random.seed(12345)  # Jedes Mal gleich!
    return random.randint(0, 2**64)

# Anfällig: Klasse mit konstantem Seed
class AnfälligerTokenGenerator:
    def __init__(self):
        # Anfällig: Alle Instanzen verwenden gleichen Seed
        random.seed(0xDEADBEEF)

    def generate_token(self):
        return format(random.getrandbits(64), '016x')

# Anfällig: Geteilter Seed über Modul
GLOBALER_SEED = 42
random.seed(GLOBALER_SEED)

def anfällige_session_id():
    # Anfällig: Verwendet global geseedeten random
    return random.getrandbits(128)

# Anfällig: Seed aus konstanter Konfiguration
CONFIG_SEED = "production_seed_value"

def anfällig_config_seed():
    random.seed(hash(CONFIG_SEED))
    return random.random()

# Anfällig: Gleicher Seed bei Neustart
class AnfälligerPersistenterRandom:
    SEED = 999

    def __init__(self):
        # Anfällig: Gleicher Seed bei jedem Neustart
        random.seed(self.SEED)

    def next(self):
        return random.random()

# Anfällig: SDK mit festem Seed (echtes CVE-Muster)
class AnfälligesSDK:
    _SEED = b"static_seed_value"

    def __init__(self):
        # Anfällig: Alle SDK-Benutzer bekommen gleichen Zufall!
        import hashlib
        seed = int.from_bytes(hashlib.sha256(self._SEED).digest()[:8], 'big')
        random.seed(seed)

    def generate_key(self):
        return bytes([random.randint(0, 255) for _ in range(32)])
// Anfällig: Gleicher Seed wiederverwendet in Java
import java.util.Random;

public class AnfälligerGleicherSeed {

    // Anfällig: Hartcodierte Seed-Konstante
    private static final long SEED = 12345L;

    // Anfällig: Alle Instanzen teilen gleichen Seed
    public String anfälligesToken() {
        Random rand = new Random(SEED);
        return Long.toHexString(rand.nextLong());
    }

    // Anfällig: Statisch geteilter Random mit festem Seed
    private static final Random geteilterRandom = new Random(0xCAFEBABE);

    public static long anfälligGeteilt() {
        return geteilterRandom.nextLong();
    }

    // Anfällig: Seed aus konstantem String
    public Random anfälligStringSeed() {
        String seedString = "fixed_seed_string";
        return new Random(seedString.hashCode());
    }

    // Anfällig: Gleicher Seed aus Klassennamen abgeleitet
    public Random anfälligKlassenSeed() {
        return new Random(this.getClass().getName().hashCode());
    }

    // Anfällig: Umgebungsvariable mit Standardwert
    public Random anfälligEnvSeed() {
        String seedStr = System.getenv("RANDOM_SEED");
        if (seedStr == null) {
            seedStr = "default_seed";  // Anfällig: Jeder verwendet Standard!
        }
        return new Random(seedStr.hashCode());
    }

    // Anfällig: Seed-Datei über Instanzen geteilt
    public Random anfälligDateiSeed() throws Exception {
        // Wenn alle Instanzen gleiche Datei lesen, bekommen alle gleichen Seed
        byte[] seedBytes = java.nio.file.Files.readAllBytes(
            java.nio.file.Paths.get("/etc/app/random.seed"));
        long seed = java.nio.ByteBuffer.wrap(seedBytes).getLong();
        return new Random(seed);
    }
}
// Anfällig: Gleicher Seed wiederverwendet in C
#include <stdlib.h>
#include <string.h>

// Anfällig: Hartcodierter Seed
#define RANDOM_SEED 12345

void anfällig_hartcodiert_init() {
    srand(RANDOM_SEED);  // Jede Ausführung gleich!
}

// Anfällig: Seed aus Kompilierzeit-Konstante
void anfällig_kompilierzeit_seed() {
    srand(__LINE__ * __COUNTER__);  // In jedem Build gleich!
}

// Anfällig: Gleicher Seed aus String-Konstante
void anfällig_string_seed() {
    const char *seed_str = "application_random_seed";
    unsigned int seed = 0;
    while (*seed_str) {
        seed = seed * 31 + *seed_str++;
    }
    srand(seed);  // Jedes Mal gleich!
}

// Anfällig: Seed bei Funktionsaufruf zurückgesetzt
void anfällig_reset_seed() {
    // Anfällig: Seedet mit gleichem Wert bei jedem Aufruf
    static const unsigned int FESTER_SEED = 999;
    srand(FESTER_SEED);
}

// Anfällig: Geteilter Seed in Bibliothek
static int initialisiert = 0;
static const unsigned int LIB_SEED = 0xABCD1234;

void anfällig_bibliothek_random() {
    if (!initialisiert) {
        srand(LIB_SEED);  // Alle Bibliotheksbenutzer bekommen gleiche Sequenz
        initialisiert = 1;
    }
}

// Anfällig: Seed aus statischer Konfiguration
typedef struct {
    unsigned int random_seed;
} Config;

static const Config STANDARD_CONFIG = { .random_seed = 54321 };

void anfällig_config_seed(const Config *config) {
    if (config == NULL) {
        config = &STANDARD_CONFIG;
    }
    srand(config->random_seed);  // Verwendet oft Standard
}

Korrigierter Code (Python/Java)

# Korrigiert: Eindeutige Seeds für jede Instanz
import os
import secrets

# Korrigiert: secrets-Modul verwenden (kein manuelles Seeding)
def sicheres_token():
    return secrets.token_hex(32)

# Korrigiert: Eindeutiger Seed pro Instanz aus System-Entropie
class SichererTokenGenerator:
    def __init__(self):
        import random
        self._random = random.Random()
        # Korrigiert: Eindeutiger Seed aus OS-Entropie
        self._random.seed(os.urandom(32))

    def generate_token(self):
        return format(self._random.getrandbits(64), '016x')

# Korrigiert: Factory, die unabhängig geseedete Generatoren erstellt
def erstelle_random_generator():
    import random
    gen = random.Random()
    gen.seed(os.urandom(32))  # Korrigiert: Neue Entropie jedes Mal
    return gen

# Korrigiert: Kein globales Seeding - secrets verwenden
def sichere_session_id():
    return secrets.token_hex(16)

# Korrigiert: Seed aus Laufzeit-Entropie, nicht Konfiguration
def sicherer_config_random():
    # Korrigiert: Konfiguration kann Seed nicht beeinflussen
    return secrets.token_bytes(32)

# Korrigiert: Neuer Seed bei jedem Neustart
class SichererPersistenterRandom:
    def __init__(self):
        import random
        self._random = random.Random()
        # Korrigiert: Frischer Seed aus System-Entropie
        self._random.seed(os.urandom(32))

    def next(self):
        return self._random.random()

# Korrigiert: SDK mit ordnungsgemäßem Zufall
class SicheresSDK:
    def __init__(self):
        # Korrigiert: Kein fester Seed - System-Entropie verwenden
        pass

    def generate_key(self):
        # Korrigiert: System-CSPRNG verwenden
        return os.urandom(32)
// Korrigiert: Eindeutige Seeds in Java
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;

public class SicheresSeeding {

    // Korrigiert: SecureRandom behandelt Seeding ordnungsgemäß
    public String sicheresToken() throws NoSuchAlgorithmException {
        // Korrigiert: Jeder Aufruf verwendet ordnungsgemäß geseedeten SecureRandom
        SecureRandom sr = SecureRandom.getInstanceStrong();
        byte[] bytes = new byte[16];
        sr.nextBytes(bytes);
        return bytesToHex(bytes);
    }

    // Korrigiert: Instanz-spezifisches Seeding
    public class SichererGenerator {
        private final SecureRandom secureRandom;

        public SichererGenerator() throws NoSuchAlgorithmException {
            // Korrigiert: Eindeutiger Seed pro Instanz
            this.secureRandom = SecureRandom.getInstanceStrong();
        }

        public long nextLong() {
            return secureRandom.nextLong();
        }
    }

    // Korrigiert: Factory erstellt unabhängig geseedete Instanzen
    public SecureRandom erstelleSecureRandom() throws NoSuchAlgorithmException {
        // Korrigiert: Jede Instanz unabhängig geseeded
        return SecureRandom.getInstanceStrong();
    }

    // Korrigiert: Kein Seed aus Strings
    public SecureRandom keinStringSeed() throws NoSuchAlgorithmException {
        // Korrigiert: System-Entropie, nicht aus String abgeleitet
        return new SecureRandom();
    }

    // Korrigiert: Umgebungsvariable beeinflusst nur nicht-krypto Zufall
    public SecureRandom sichererEnvRandom() throws NoSuchAlgorithmException {
        // Korrigiert: Umgebung für sicheren Zufall ignorieren
        // Umgebung könnte nicht-Sicherheits-Zufall für Tests steuern
        return SecureRandom.getInstanceStrong();
    }

    // Korrigiert: Keine geteilte Seed-Datei
    public SecureRandom keineDateiSeed() throws NoSuchAlgorithmException {
        // Korrigiert: Jede Instanz generiert eigene Entropie
        SecureRandom sr = new SecureRandom();
        sr.nextBytes(new byte[32]);  // Seeding erzwingen
        return sr;
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}
// Korrigiert: Eindeutige Seeds in C
#include <openssl/rand.h>
#include <fcntl.h>
#include <unistd.h>

// Korrigiert: Aus System-Entropie seeden
int sicher_init_random() {
    unsigned char seed[32];

    // Korrigiert: Von /dev/urandom lesen
    int fd = open("/dev/urandom", O_RDONLY);
    if (fd < 0) return -1;

    if (read(fd, seed, sizeof(seed)) != sizeof(seed)) {
        close(fd);
        return -1;
    }
    close(fd);

    // Korrigiert: OpenSSL seeden
    RAND_seed(seed, sizeof(seed));

    // Korrigiert: Seed aus Speicher löschen
    OPENSSL_cleanse(seed, sizeof(seed));

    return 0;
}

// Korrigiert: OpenSSL RAND_bytes verwenden
int sichere_zufalls_bytes(unsigned char *buffer, size_t lange) {
    // Korrigiert: RAND_bytes behandelt Seeding intern
    return RAND_bytes(buffer, lange) == 1 ? 0 : -1;
}

// Korrigiert: Pro-Instanz-Seeding
typedef struct {
    unsigned char state[32];
} SichererRandom;

int sicherer_random_init(SichererRandom *sr) {
    // Korrigiert: Jede Instanz bekommt eindeutigen Seed
    return sichere_zufalls_bytes(sr->state, sizeof(sr->state));
}

// Korrigiert: Bibliothek mit ordnungsgemäßer Initialisierung
static int lib_initialisiert = 0;

int sichere_bibliothek_init() {
    if (!lib_initialisiert) {
        // Korrigiert: Aus System-Entropie seeden, nicht Konstante
        if (sicher_init_random() != 0) {
            return -1;
        }
        lib_initialisiert = 1;
    }
    return 0;
}

// Korrigiert: Kein Standard-Config-Seed
typedef struct {
    int use_random;  // Konfiguration enthält keinen Seed
} SichereConfig;

int sichere_config_init(const SichereConfig *config) {
    (void)config;  // Konfiguration beeinflusst Zufalls-Seeding nicht
    return sicher_init_random();  // Immer System-Entropie verwenden
}

Die Korrektur stellt sicher, dass jede PRNG-Instanz einen eindeutigen Seed aus System-Entropiequellen erhält.


Ausgenutzt in der Praxis

JavaScript SDK fester Seed (CVE-2022-39218)

Ein JavaScript-SDK für Serverless-Anwendungen verwendete denselben festen Seed für seinen PRNG, was Angreifern ermöglichte, Kryptografie zu umgehen, indem sie generierte Werte vorhersagten.

Kryptowährungs-Wallet gleiche Schlüssel

Mehrere Kryptowährungs-Wallet-Implementierungen, die PRNGs mit gleichem Seed verwendeten, generierten identische private Schlüssel, was den Diebstahl von Geldern aus allen betroffenen Wallets ermöglichte.


Tools zum Testen/Ausnutzen


CVE-Beispiele

  • CVE-2022-39218 — JavaScript SDK fester Seed ermöglicht Krypto-Bypass.

Referenzen

  1. MITRE Corporation. "CWE-336: Same Seed in PRNG." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/336.html

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

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