Falsche Verwendung von Seeds in Pseudo-Zufallszahlengeneratoren (PRNG)
Beschreibung
Falsche Verwendung von Seeds in Pseudo-Zufallszahlengeneratoren (PRNG) ist eine Schwachstelle, die auftritt, wenn ein Produkt einen PRNG verwendet, aber die Seed-Werte nicht ordnungsgemäß verwaltet. Da PRNGs deterministische Algorithmen sind, die eine Zahlenfolge basierend ausschließlich auf einem anfänglichen Seed-Wert erzeugen, hängt die Sicherheit der generierten Zahlen kritisch von der Unvorhersagbarkeit und Geheimhaltung des Seeds ab. Falsche Seed-Verwendung umfasst die Verwendung vorhersagbarer Werte (wie Zeitstempel oder Prozess-IDs), die Wiederverwendung von Seeds über Sessions oder Instanzen hinweg, die Offenlegung von Seeds durch Logs oder Fehler, oder die Verwendung unzureichender Entropie für den Seed. Wenn Seeds kompromittiert oder vorhersagbar sind, können Angreifer die gesamte Sequenz der generierten Werte bestimmen.
Risiko
Falsches PRNG-Seeding untergräbt alle Sicherheitsmechanismen, die auf Zufallswerten basieren. Wenn ein Angreifer den Seed bestimmen oder vorhersagen kann, kann er die exakte Sequenz der "zufälligen" Zahlen berechnen, die der PRNG erzeugen wird, was die Vorhersage von Session-Tokens, Verschlüsselungsschlüsseln, Authentifizierungs-Challenges und anderen sicherheitskritischen Werten ermöglicht. Die Verwendung der Systemzeit als Seed reduziert den Suchraum dramatisch - ein Angreifer, der ungefähr weiß, wann ein Seed erstellt wurde, muss möglicherweise nur tausende oder Millionen von Werten ausprobieren anstelle des theoretischen Raums von 2^128 oder mehr. Seeds, die aus Prozess-IDs (typischerweise 16-Bit-Werte) abgeleitet werden, sind ähnlich eingeschränkt. Reale Angriffe haben vorhersagbares Seeding ausgenutzt, um Kryptowährungs-Wallets zu kompromittieren, Sessions zu kapern und Verschlüsselung zu brechen.
Lösung
Speisen Sie PRNGs mit kryptografisch sicheren, unvorhersagbaren Werten aus ordnungsgemäßen Entropiequellen. Verwenden Sie vom Betriebssystem bereitgestellte CSPRNGs wie /dev/urandom, CryptGenRandom oder SecureRandom.getInstanceStrong(). Behandeln Sie Seeds als kryptografische Geheimnisse - loggen Sie sie niemals, legen Sie sie nicht in Fehlermeldungen offen und übertragen Sie sie nicht. Verwenden Sie mindestens 256 Bits Entropie für Seeds. Implementieren Sie automatisches Reseeding aus hochwertigen Entropiequellen in regelmäßigen Abständen. Leiten Sie Seeds niemals ausschließlich aus vorhersagbaren Werten wie Zeitstempeln, Prozess-IDs oder benutzergesteuerten Eingaben ab. Wenn mehrere PRNG-Instanzen benötigt werden, sollte jede unabhängig generierte Seeds haben. Erwägen Sie die Verwendung von FIPS 140-2-konformer Zufallszahlengenerierung für sicherheitskritische Anwendungen.
Häufige Auswirkungen
| Auswirkung | Details |
|---|---|
| Zugriffskontrolle | Umfang: Zugriffskontrolle Vorhersagbare oder offengelegte Seeds ermöglichen Angreifern, Authentifizierungs-Tokens, Session-IDs und Zugangsdaten zu berechnen und so Authentifizierungs- und Autorisierungskontrollen zu umgehen. |
| Vertraulichkeit | Umfang: Vertraulichkeit Verschlüsselungsschlüssel, die aus vorhersagbaren Seeds generiert wurden, können von Angreifern rekonstruiert werden, was die Vertraulichkeit aller mit diesen Schlüsseln verschlüsselten Daten kompromittiert. |
| Integrität | Umfang: Integrität Vorhersagbare Seeds in Integritätsmechanismen wie CSRF-Tokens oder Nonces ermöglichen Angreifern, gültige Tokens zu fälschen und Integritätsverifikation zu umgehen. |
Beispielcode
Anfälliger Code (Python/Java)
Die folgenden Beispiele demonstrieren falsche PRNG-Seed-Verwendung:
# Anfällig: Falsches PRNG-Seeding
import random
import time
import os
# Anfällig: Zeitbasierter Seed
def anfällig_zeit_seed():
# Anfällig: Aktuelle Zeit ist vorhersagbar
# Nur ~31 Bits Entropie, leicht erratbar
random.seed(time.time())
return random.randint(0, 2**64)
# Anfällig: Prozess-ID Seed
def anfällig_pid_seed():
# Anfällig: PID ist nur 15-16 Bits
random.seed(os.getpid())
return random.getrandbits(128)
# Anfällig: Benutzergesteuerter Seed
def anfällig_benutzer_seed(user_input):
# Anfällig: Angreifer kontrolliert den Seed
random.seed(hash(user_input))
return random.getrandbits(64)
# Anfällig: Seed in Logs offengelegt
def anfällig_geloggter_seed():
seed = int(time.time() * 1000)
print(f"Initialisiere mit Seed: {seed}") # Anfällig: Loggt Seed!
random.seed(seed)
return random.getrandbits(128)
# Anfällig: Konstanter Seed
def anfällig_konstanter_seed():
# Anfällig: Gleiche Ausgabe bei jedem Lauf
random.seed(12345)
return random.getrandbits(64)
# Anfällig: Unzureichende Seed-Entropie
def anfällig_kurzer_seed():
# Anfällig: Nur 32 Bits Entropie
seed = os.urandom(4) # Sollte mindestens 32 Bytes sein
random.seed(int.from_bytes(seed, 'big'))
return random.getrandbits(256)
# Anfällig: Seed aus Hostname/IP
def anfällig_netzwerk_seed():
import socket
# Anfällig: Hostname ist vorhersagbar
hostname = socket.gethostname()
random.seed(hash(hostname))
return random.getrandbits(64)
// Anfällig: Falsches PRNG-Seeding in Java
import java.util.Random;
public class AnfälligesSeeding {
// Anfällig: Zeitbasierter Seed
public long anfälligZeitSeed() {
// Anfällig: System.currentTimeMillis() ist vorhersagbar
Random rand = new Random(System.currentTimeMillis());
return rand.nextLong();
}
// Anfällig: Nano-Zeit Seed
public long anfälligNanoSeed() {
// Anfällig: Immer noch vorhersagbar, nur präziser
Random rand = new Random(System.nanoTime());
return rand.nextLong();
}
// Anfällig: Konstanter Seed für "Reproduzierbarkeit"
public long anfälligKonstanterSeed() {
// Anfällig: Gleiche Sequenz jedes Mal
Random rand = new Random(0xDEADBEEF);
return rand.nextLong();
}
// Anfällig: Hash eines vorhersagbaren Werts
public long anfälligHashSeed(String benutzername) {
// Anfällig: Angreifer kennt oder kann Benutzernamen erraten
Random rand = new Random(benutzername.hashCode());
return rand.nextLong();
}
// Anfällig: Kombinierte schwache Quellen
public long anfälligKombinierterSeed() {
// Anfällig: Kombination schwacher Quellen macht nicht stark
long seed = System.currentTimeMillis() ^
Runtime.getRuntime().freeMemory() ^
Thread.currentThread().getId();
Random rand = new Random(seed);
return rand.nextLong();
}
// Anfällig: Offengelegter Seed
public long anfälligOffengelegterSeed() {
long seed = System.currentTimeMillis();
System.out.println("Debug: seed=" + seed); // Anfällig!
return new Random(seed).nextLong();
}
// Anfällig: Wiederverwendeter Seed über Instanzen
private static final long GETEILTER_SEED = System.currentTimeMillis();
public Random anfälligGeteilterSeed() {
// Anfällig: Alle Instanzen verwenden gleichen Seed
return new Random(GETEILTER_SEED);
}
}
// Anfällig: Falsches PRNG-Seeding in C
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <stdio.h>
// Anfällig: Zeitbasierter Seed
void anfällig_zeit_seed() {
// Anfällig: time() gibt Sekunden zurück - sehr vorhersagbar
srand(time(NULL));
}
// Anfällig: PID-basierter Seed
void anfällig_pid_seed() {
// Anfällig: PID hat < 16 Bits Entropie
srand(getpid());
}
// Anfällig: Konstanter Seed
void anfällig_konstanter_seed() {
// Anfällig: Gleiche Sequenz bei jeder Ausführung
srand(42);
}
// Anfällig: Offengelegter Seed
void anfällig_geloggter_seed() {
unsigned int seed = time(NULL) ^ getpid();
printf("Verwende Seed: %u\n", seed); // Anfällig!
srand(seed);
}
// Anfällig: Benutzereingabe als Seed
void anfällig_benutzer_seed(const char *user_input) {
// Anfällig: Angreifer kontrolliert Seed
unsigned int seed = 0;
while (*user_input) {
seed = seed * 31 + *user_input++;
}
srand(seed);
}
// Anfällig: Stack-Adress-Seed
void anfällig_adress_seed() {
// Anfällig: ASLR bietet begrenzte Entropie
int stack_var;
srand((unsigned int)&stack_var);
}
// Anfällig: Kombinierte schwache Quellen
void anfällig_kombinierter_seed() {
// Anfällig: XOR schwacher Quellen ist immer noch schwach
srand(time(NULL) ^ getpid() ^ getppid());
}
Korrigierter Code (Python/Java)
# Korrigiert: Ordnungsgemäßes PRNG-Seeding
import secrets
import os
# Korrigiert: secrets-Modul verwenden (kein manuelles Seeding nötig)
def sicheres_token():
# Korrigiert: secrets verwendet intern System-CSPRNG
return secrets.token_hex(32)
# Korrigiert: Ordnungsgemäßes Seeding wenn benutzerdefinierter PRNG benötigt
def sicher_geseedeter_random():
import random
# Korrigiert: Mit ausreichender Entropie vom OS seeden
seed_bytes = os.urandom(32) # 256 Bits
random.seed(seed_bytes)
return random
# Korrigiert: Seed geheim halten
def sicherer_random_mit_audit():
# Korrigiert: Nicht den tatsächlichen Seed loggen
import hashlib
seed = os.urandom(32)
# Nur nicht-umkehrbaren Identifikator loggen
seed_id = hashlib.sha256(seed).hexdigest()[:16]
print(f"Zufallsgenerator initialisiert (id: {seed_id})")
import random
random.seed(seed)
return random
# Korrigiert: Unabhängige Seeds für mehrere Instanzen
class SichererRandomPool:
def __init__(self, anzahl):
self.generatoren = []
for _ in range(anzahl):
# Korrigiert: Jede Instanz bekommt unabhängigen Seed
import random
gen = random.Random()
gen.seed(os.urandom(32))
self.generatoren.append(gen)
# Korrigiert: Periodisches Reseeding
class ReseedingRandom:
def __init__(self, reseed_intervall=1000):
import random
self._random = random.Random()
self._reseed()
self._count = 0
self._reseed_intervall = reseed_intervall
def _reseed(self):
# Korrigiert: Aus System-Entropie reseeden
self._random.seed(os.urandom(32))
self._count = 0
def getrandbits(self, bits):
self._count += 1
if self._count >= self._reseed_intervall:
self._reseed()
return self._random.getrandbits(bits)
// Korrigiert: Ordnungsgemäßes PRNG-Seeding in Java
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;
public class SicheresSeeding {
// Korrigiert: SecureRandom verwenden, das sich selbst ordnungsgemäß seeded
public byte[] sichereZufallsBytes(int lange) throws NoSuchAlgorithmException {
// Korrigiert: SecureRandom behandelt Seeding intern
SecureRandom sr = SecureRandom.getInstanceStrong();
byte[] bytes = new byte[lange];
sr.nextBytes(bytes);
return bytes;
}
// Korrigiert: Ordnungsgemäßes Seeding wenn benötigt
public SecureRandom sicherGeseedeterRandom() throws NoSuchAlgorithmException {
SecureRandom sr = new SecureRandom();
// Korrigiert: generateSeed holt Entropie vom System
byte[] seed = sr.generateSeed(32); // 256 Bits
sr.setSeed(seed);
return sr;
}
// Korrigiert: Unabhängige Seeds für mehrere Instanzen
public SecureRandom[] erstelleRandomPool(int anzahl)
throws NoSuchAlgorithmException {
SecureRandom[] pool = new SecureRandom[anzahl];
for (int i = 0; i < anzahl; i++) {
// Korrigiert: Jede Instanz unabhängig geseeded
pool[i] = SecureRandom.getInstanceStrong();
}
return pool;
}
// Korrigiert: Sicheres Audit-Logging
public SecureRandom sicherMitAudit() throws NoSuchAlgorithmException {
SecureRandom sr = SecureRandom.getInstanceStrong();
// Korrigiert: Nur nicht-sensible Infos loggen
System.out.println("SecureRandom initialisiert: " +
sr.getAlgorithm() + " Provider: " +
sr.getProvider().getName());
return sr;
}
// Korrigiert: Periodisches Reseeding
public class ReseedingSecureRandom {
private SecureRandom secureRandom;
private int aufrufZähler = 0;
private final int reseedIntervall = 1000;
public ReseedingSecureRandom() throws NoSuchAlgorithmException {
secureRandom = SecureRandom.getInstanceStrong();
}
public synchronized byte[] nextBytes(int lange) {
byte[] bytes = new byte[lange];
secureRandom.nextBytes(bytes);
aufrufZähler++;
if (aufrufZähler >= reseedIntervall) {
// Korrigiert: Periodisches Reseed
secureRandom.setSeed(secureRandom.generateSeed(32));
aufrufZähler = 0;
}
return bytes;
}
}
}
// Korrigiert: Ordnungsgemäßes PRNG-Seeding in C
#include <openssl/rand.h>
#include <stdio.h>
// Korrigiert: OpenSSL RAND verwenden, das Seeding behandelt
int sicherer_zufall(unsigned char *buffer, size_t lange) {
// Korrigiert: RAND_bytes verwendet System-Entropie
return RAND_bytes(buffer, lange) == 1 ? 0 : -1;
}
// Korrigiert: Von /dev/urandom seeden wenn manuelles Seeding benötigt
int sicher_prng_seeden() {
unsigned char seed[32]; // 256 Bits
// Korrigiert: Von System-Entropiequelle lesen
FILE *f = fopen("/dev/urandom", "rb");
if (!f) return -1;
if (fread(seed, 1, sizeof(seed), f) != sizeof(seed)) {
fclose(f);
return -1;
}
fclose(f);
// Korrigiert: Entropie zu OpenSSL hinzufügen
RAND_seed(seed, sizeof(seed));
// Korrigiert: Seed aus Speicher löschen
OPENSSL_cleanse(seed, sizeof(seed));
return 0;
}
// Korrigiert: Audit ohne Seed offenzulegen
int sicher_init_mit_audit() {
if (sicher_prng_seeden() != 0) {
fprintf(stderr, "PRNG-Seeding fehlgeschlagen\n");
return -1;
}
// Korrigiert: Nur Status loggen, nicht den tatsächlichen Seed
if (RAND_status() == 1) {
printf("PRNG ordnungsgemäß geseeded\n");
}
return 0;
}
// Korrigiert: getrandom() unter Linux verwenden
#ifdef __linux__
#include <sys/random.h>
int sicher_zufall_linux(unsigned char *buffer, size_t lange) {
// Korrigiert: getrandom() blockiert bis Entropie verfügbar
ssize_t result = getrandom(buffer, lange, 0);
return result == (ssize_t)lange ? 0 : -1;
}
#endif
Die Korrektur verwendet ordnungsgemäße Entropiequellen für Seeds, hält Seeds geheim und implementiert periodisches Reseeding.
Ausgenutzt in der Praxis
Debian OpenSSL-Schwachstelle (CVE-2008-0166)
Debians OpenSSL-Paket seedete den PRNG fälschlicherweise nur mit der Prozess-ID und generierte so nur 65.536 eindeutige Schlüsselpaare auf allen betroffenen Systemen.
Cloud-Anwendung zeitbasiertes Seeding (CVE-2020-7010)
Eine Kubernetes-Cloud-Anwendung generierte Passwörter mit einem schwachen RNG, der mit der Bereitstellungszeit geseeded wurde, was Angreifern ermöglichte, generierte Anmeldedaten vorherzusagen.
Tools zum Testen/Ausnutzen
-
untwister — Stellt PRNG-Zustand und Seed aus beobachteten Ausgaben wieder her.
-
randcrack — Knackt Pythons random-Modul bei ausreichenden Ausgaben.
-
NIST Statistical Test Suite — Testet Zufälligkeitsqualität.
CVE-Beispiele
-
CVE-2008-0166 — Debian OpenSSL nur PID-Seeding.
-
CVE-2016-10180 — Router-PIN-Generierung mit rand(time(0)).
-
CVE-2018-12520 — Nicht geseedeter PRNG für Session-ID-Generierung.
Referenzen
-
MITRE Corporation. "CWE-335: Incorrect Usage of Seeds in PRNG." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/335.html
-
NIST. "Recommendation for Random Number Generation." SP 800-90A. https://csrc.nist.gov/publications/detail/sp/800-90a/rev-1/final
-
Goldberg, I., Wagner, D. "Randomness and the Netscape Browser." Dr. Dobb's Journal, 1996.