Verwendung eines kryptografisch schwachen Pseudo-Zufallszahlengenerators (PRNG)

Beschreibung

Verwendung eines kryptografisch schwachen PRNG tritt auf, wenn Software einen Pseudo-Zufallszahlengenerator (PRNG) verwendet, der nicht für sicherheitsrelevante Kontexte geeignet ist. Standard-PRNGs wie rand(), Math.random() oder java.util.Random sind für Geschwindigkeit und statistische Verteilung konzipiert, nicht für Unvorhersagbarkeit. Ihre Ausgabe kann vorhergesagt werden, wenn der Seed bekannt ist oder wenn genügend Ausgabe beobachtet wird. Für Sicherheitszwecke wie Session-Tokens, Verschlüsselungsschlüssel oder CSRF-Tokens müssen kryptografisch sichere PRNGs (CSPRNGs) verwendet werden.

Risiko

Schwache PRNGs haben zahlreiche Angriffe ermöglicht. Vorhersagbare Session-IDs ermöglichen Session-Hijacking. Vorhersagbare CSRF-Tokens ermöglichen Cross-Site-Request-Forgery. Schwache Zufallszahlen in kryptografischen Protokollen ermöglichen Schlüsselwiederherstellung. Berühmte Beispiele umfassen den Debian-OpenSSL-Bug (CVE-2008-0166), bei dem schwache Zufälligkeit den Schlüsselraum auf nur 32.767 mögliche Schlüssel reduzierte und alle auf betroffenen Systemen generierten Schlüssel für 2 Jahre betraf.

Lösung

Verwenden Sie kryptografisch sichere Zufallszahlengeneratoren: os.urandom() oder secrets-Modul in Python, SecureRandom in Java, crypto.randomBytes() in Node.js, random_bytes() in PHP. Seeden Sie CSPRNGs niemals mit vorhersagbaren Werten. Verwenden Sie die Entropiequellen des Betriebssystems (/dev/urandom unter Unix, CryptGenRandom unter Windows). Für Webanwendungen verwenden Sie vom Framework bereitgestellte sichere Token-Generatoren. Implementieren Sie niemals benutzerdefinierte Zufallszahlengenerierung für Sicherheitszwecke.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleUmfang: Session-Hijacking

Vorhersagbare Session-Tokens ermöglichen Angreifern, Benutzer zu imitieren.
VertraulichkeitUmfang: Schlüsselwiederherstellung

Schwache Zufälligkeit bei Schlüsselgenerierung ermöglicht kryptografische Angriffe.
IntegritätUmfang: Token-Fälschung

Vorhersagbare CSRF-Tokens, Nonces oder eindeutige IDs können gefälscht werden.

Beispielcode + Korrigierter Code

Anfälliger Code

# ANFÄLLIG: random-Modul für Sicherheit verwenden
import random
import string

def generate_session_id():
    # random.choice ist nicht kryptografisch sicher!
    return ''.join(random.choice(string.ascii_letters + string.digits)
                   for _ in range(32))

def generate_password_reset_token():
    # Vorhersagbar!
    return str(random.randint(100000, 999999))

def generate_csrf_token():
    # Kann vorhergesagt werden, wenn Seed bekannt ist
    random.seed()  # Oft mit Zeit geseeded
    return hex(random.getrandbits(128))

def generate_api_key():
    # NICHT sicher!
    return ''.join(random.choices(string.hexdigits, k=32))
// ANFÄLLIG: java.util.Random verwenden
import java.util.Random;

public class SchwacherRandomGenerator {

    private Random random = new Random();  // Nicht kryptografisch sicher!

    public String generateSessionId() {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 32; i++) {
            sb.append(Integer.toHexString(random.nextInt(16)));
        }
        return sb.toString();  // Vorhersagbar!
    }

    public String generateToken() {
        // Seeding mit Zeit macht es vorhersagbar
        Random r = new Random(System.currentTimeMillis());
        return Long.toHexString(r.nextLong());
    }

    // Noch schlimmer: konstanter Seed
    public int generateOTP() {
        Random r = new Random(12345);  // Gleiche Sequenz jedes Mal!
        return r.nextInt(1000000);
    }
}
// ANFÄLLIG: Math.random() verwenden
function generateSessionId() {
    // Math.random() ist NICHT kryptografisch sicher
    let id = '';
    for (let i = 0; i < 32; i++) {
        id += Math.floor(Math.random() * 16).toString(16);
    }
    return id;  // Vorhersagbar!
}

function generateCSRFToken() {
    // Kann vorhergesagt/per Brute-Force geknackt werden
    return Math.random().toString(36).substring(2);
}

function generatePassword() {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    let password = '';
    for (let i = 0; i < 12; i++) {
        password += chars.charAt(Math.floor(Math.random() * chars.length));
    }
    return password;  // Schwach!
}
// ANFÄLLIG: rand() oder mt_rand() verwenden
function generateToken() {
    // rand() hat sehr schwache Zufälligkeit
    return md5(rand());
}

function generateSessionId() {
    // mt_rand() kann nach Beobachtung von Ausgaben vorhergesagt werden
    $id = '';
    for ($i = 0; $i < 32; $i++) {
        $id .= dechex(mt_rand(0, 15));
    }
    return $id;
}

function generateApiKey() {
    // Vorhersagbarer zeitbasierter Seed
    srand(time());
    return sha1(rand());
}

Korrigierter Code

# SICHER: secrets-Modul verwenden (Python 3.6+)
import secrets
import string

def generate_session_id():
    # secrets.token_hex verwendet intern os.urandom
    return secrets.token_hex(32)  # 64-Zeichen Hex-String

def generate_password_reset_token():
    # URL-sicheres Token
    return secrets.token_urlsafe(32)

def generate_csrf_token():
    # Kryptografisch sicher
    return secrets.token_hex(16)

def generate_api_key():
    # 256-Bit Schlüssel
    return secrets.token_hex(32)

def generate_otp():
    # Sichere zufällige Ziffernfolge
    return ''.join(secrets.choice(string.digits) for _ in range(6))

def generate_secure_password(length=16):
    alphabet = string.ascii_letters + string.digits + string.punctuation
    # Minimale Komplexität sicherstellen
    password = [
        secrets.choice(string.ascii_lowercase),
        secrets.choice(string.ascii_uppercase),
        secrets.choice(string.digits),
        secrets.choice(string.punctuation)
    ]
    password += [secrets.choice(alphabet) for _ in range(length - 4)]
    secrets.SystemRandom().shuffle(password)
    return ''.join(password)

# Oder direkt os.urandom verwenden
import os
import base64

def generate_token_urandom():
    return base64.urlsafe_b64encode(os.urandom(32)).decode('utf-8')
// SICHER: SecureRandom verwenden
import java.security.SecureRandom;
import java.util.Base64;

public class SichererRandomGenerator {

    // SecureRandom ist kryptografisch stark
    private final SecureRandom secureRandom = new SecureRandom();

    public String generateSessionId() {
        byte[] bytes = new byte[32];
        secureRandom.nextBytes(bytes);
        return bytesToHex(bytes);
    }

    public String generateToken() {
        byte[] bytes = new byte[32];
        secureRandom.nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }

    public String generateOTP(int length) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < length; i++) {
            sb.append(secureRandom.nextInt(10));
        }
        return sb.toString();
    }

    public String generateApiKey() {
        byte[] bytes = new byte[32];
        secureRandom.nextBytes(bytes);
        return bytesToHex(bytes);
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }

    // Für UUID-Generierung
    public String generateSecureUUID() {
        byte[] bytes = new byte[16];
        secureRandom.nextBytes(bytes);
        // Version auf 4 (zufällig) setzen
        bytes[6] = (byte) ((bytes[6] & 0x0f) | 0x40);
        // Variante setzen
        bytes[8] = (byte) ((bytes[8] & 0x3f) | 0x80);
        return formatUUID(bytes);
    }
}
// SICHER: crypto-Modul in Node.js verwenden
const crypto = require('crypto');

function generateSessionId() {
    // crypto.randomBytes verwendet OS CSPRNG
    return crypto.randomBytes(32).toString('hex');
}

function generateCSRFToken() {
    return crypto.randomBytes(32).toString('base64url');
}

function generatePassword(length = 16) {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*';
    const randomBytes = crypto.randomBytes(length);
    let password = '';
    for (let i = 0; i < length; i++) {
        password += chars[randomBytes[i] % chars.length];
    }
    return password;
}

function generateOTP(length = 6) {
    const digits = '0123456789';
    const randomBytes = crypto.randomBytes(length);
    let otp = '';
    for (let i = 0; i < length; i++) {
        otp += digits[randomBytes[i] % 10];
    }
    return otp;
}

// Für Browser-JavaScript Web Crypto API verwenden
function generateSecureTokenBrowser() {
    const array = new Uint8Array(32);
    crypto.getRandomValues(array);
    return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}

// UUID v4 Generierung
function generateUUID() {
    return crypto.randomUUID();  // Node.js 14.17+
}
// SICHER: random_bytes() in PHP 7+ verwenden
function generateToken(): string {
    // random_bytes verwendet OS CSPRNG
    return bin2hex(random_bytes(32));
}

function generateSessionId(): string {
    return bin2hex(random_bytes(32));
}

function generateApiKey(): string {
    return base64_encode(random_bytes(32));
}

function generateOTP(int $length = 6): string {
    $otp = '';
    for ($i = 0; $i < $length; $i++) {
        $otp .= random_int(0, 9);  // Kryptografisch sicher
    }
    return $otp;
}

function generateSecurePassword(int $length = 16): string {
    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*';
    $password = '';
    $max = strlen($chars) - 1;
    for ($i = 0; $i < $length; $i++) {
        $password .= $chars[random_int(0, $max)];
    }
    return $password;
}

// Für älteres PHP (< 7), openssl_random_pseudo_bytes verwenden
function generateTokenLegacy(): string {
    $bytes = openssl_random_pseudo_bytes(32, $strong);
    if (!$strong) {
        throw new Exception('Schwache Zufallsgenerierung');
    }
    return bin2hex($bytes);
}

Ausgenutzt in der Praxis

Debian OpenSSL Bug (2008)

CVE-2008-0166: Ein Debian-spezifischer Patch für OpenSSL entfernte versehentlich die meisten Entropiequellen, wodurch die Zufälligkeit auf nur die Prozess-ID reduziert wurde. Dies bedeutete, dass nur 32.767 mögliche Schlüssel generiert werden könnten. SSH- und SSL-Schlüssel, die auf betroffenen Systemen für 2 Jahre generiert wurden, waren kompromittiert.

PHP Session-ID-Vorhersage (Mehrere)

Mehrere Schwachstellen in PHPs Session-ID-Generierung mit schwachen Zufallsfunktionen ermöglichten Session-Vorhersageangriffe.

Java Kryptografische Schwäche (Android, 2013)

Android-Apps, die java.util.Random für Bitcoin-Wallet-Schlüsselgenerierung verwendeten, führten zum Diebstahl von Bitcoins aufgrund vorhersagbarer Schlüssel.


Tools zum Testen/Ausnutzen

  • Burp Sequencer — Analysiert Zufälligkeit von Tokens.

  • ent — Entropie-Testwerkzeug.

  • Untwister — Mersenne-Twister-Seed-Wiederherstellung.

  • RandCrack — Python-random-Modul-Cracker.


CVE-Beispiele


Referenzen

  1. MITRE. "CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator." https://cwe.mitre.org/data/definitions/338.html

  2. OWASP. "Insufficient Entropy." https://owasp.org/www-community/vulnerabilities/Insufficient_Entropy