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
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Umfang: 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. |
| Vertraulichkeit | Umfang: 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ät | Umfang: 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
-
Statische Analyse-Tools — Können hartcodierte Seeds im Quellcode erkennen.
-
untwister — Stellt PRNG-Zustand aus Ausgaben wieder her.
-
randcrack — Nutzt vorhersagbare PRNG-Sequenzen aus.
CVE-Beispiele
- CVE-2022-39218 — JavaScript SDK fester Seed ermöglicht Krypto-Bypass.
Referenzen
-
MITRE Corporation. "CWE-336: Same Seed in PRNG." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/336.html
-
NIST. "Recommendation for Random Number Generation." SP 800-90A. https://csrc.nist.gov/publications/detail/sp/800-90a/rev-1/final
-
OWASP Foundation. "Insecure Randomness." https://owasp.org/www-community/vulnerabilities/Insecure_Randomness