Kleiner Raum von Zufallswerten

Beschreibung

Kleiner Raum von Zufallswerten ist eine Schwachstelle, die auftritt, wenn die Anzahl möglicher Zufallswerte, die ein Produkt generieren kann, kleiner ist als für die Sicherheit erforderlich, wodurch das System anfällig für Brute-Force-Angriffe wird. Selbst bei Verwendung eines kryptografisch sicheren Zufallszahlengenerators können Angreifer alle Möglichkeiten in einem praktikablen Zeitrahmen aufzählen, wenn die Ausgabe auf einen kleinen Raum beschränkt ist (wie kurze Strings, begrenzte Zeichensätze oder abgeschnittene Werte). Dies reduziert effektiv die Sicherheit des Systems auf die Anzahl möglicher Werte anstelle der theoretischen Stärke der Zufallsquelle. Beispiele umfassen kurze Session-IDs, Token mit begrenzter Länge, kleine numerische Bereiche für Verifizierungscodes und Dateinamen mit unzureichender Eindeutigkeit.

Risiko

Ein kleiner Zufallswerteraum ermöglicht Angreifern, alle möglichen Werte in praktischer Zeit per Brute-Force zu durchsuchen. Wenn Session-IDs nur 32 Bits haben (etwa 4 Milliarden Werte), kann ein Angreifer, der 10.000 Anfragen pro Sekunde macht, alle Möglichkeiten in etwa 5 Tagen aufzählen. 16-Bit-Werte (65.536 Möglichkeiten) können in Sekunden per Brute-Force geknackt werden. Das Risiko ist besonders schwerwiegend für Session-Management, Passwort-Reset-Tokens und Authentifizierungscodes, bei denen erfolgreiches Erraten Zugang gewährt. Angreifer können Brute-Force-Versuche über mehrere Maschinen parallelisieren und so die Angriffszeit weiter reduzieren. Die Schwachstelle wird oft eingeführt, wenn Entwickler Benutzerfreundlichkeit (kürzere Codes, merkbare Werte) über Sicherheit priorisieren oder wenn Legacy-Systembeschränkungen die Wertlänge begrenzen.

Lösung

Stellen Sie sicher, dass Zufallswerte ausreichende Größe für ihren Sicherheitskontext haben. Session-Identifikatoren sollten mindestens 128 Bits (16 Bytes) haben, wie von OWASP empfohlen. Kryptografische Schlüssel sollten mindestens 128 Bits für symmetrische Verschlüsselung und 2048 Bits für RSA haben. Verwenden Sie FIPS 140-2 oder 140-3 konforme Zufallszahlengeneratoren mit angemessenen Ausgabegrößen. Berechnen Sie den erforderlichen Werteraum basierend auf der erwarteten Angriffsrate und der gewünschten Sicherheitslebensdauer: Für einen Wert, der 10 Millionen Rateversuche pro Tag für ein Jahr widerstehen soll, benötigen Sie mindestens 52 Bits (10M × 365 = ~3,6 Milliarden Versuche). Vermeiden Sie das Abschneiden von Zufallswerten nach der Generierung. Verwenden Sie URL-sichere Base64-Kodierung, um die Entropie pro Zeichen zu maximieren, wenn die Länge begrenzt ist.

Häufige Auswirkungen

AuswirkungDetails
ZugriffskontrolleUmfang: Zugriffskontrolle

Angreifer können den kleinen Werteraum per Brute-Force durchsuchen, um gültige Session-IDs, Tokens oder Identifikatoren zu erraten und so Authentifizierungs- und Autorisierungskontrollen zu umgehen.
VertraulichkeitUmfang: Vertraulichkeit

Kleine kryptografische Schlüsselräume oder Nonces können per Brute-Force geknackt werden, was die Vertraulichkeit verschlüsselter Daten kompromittiert.
IntegritätUmfang: Integrität

Vorhersagbare Dateinamen oder Ressourcen-Identifikatoren mit kleinen Werteräumen ermöglichen Angreifern den Zugriff auf oder das Überschreiben von Ressourcen anderer Benutzer.

Beispielcode

Anfälliger Code (Java/Python)

Die folgenden Beispiele demonstrieren Schwachstellen durch kleine Zufallswerteräume:

// Anfällig: Kleiner Raum von Zufallswerten in Java
import java.util.Random;

public class AnfälligerKleinerRaum {

    // Anfällig: Nur 8-Byte Session-ID (zu kurz)
    public String anfälligeSessionId() {
        Random rand = new Random();
        // Anfällig: 8 Hex-Zeichen = 32 Bits = 4 Milliarden Möglichkeiten
        return String.format("%08x", rand.nextInt());
    }

    // Anfällig: 5-Zeichen alphanumerischer Code
    public String anfälligerVerifizierungscode() {
        // Anfällig: Nur 5 Zeichen aus 36-Zeichen-Alphabet
        // 36^5 = 60.466.176 Möglichkeiten - in Minuten per Brute-Force geknackt
        String chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
        StringBuilder sb = new StringBuilder();
        Random rand = new Random();
        for (int i = 0; i < 5; i++) {
            sb.append(chars.charAt(rand.nextInt(36)));
        }
        return sb.toString();
    }

    // Anfällig: 4-stellige PIN
    public String anfälligePIN() {
        // Anfällig: Nur 10.000 Möglichkeiten
        return String.format("%04d", new Random().nextInt(10000));
    }

    // Anfällig: Kurze Dateinamen
    public String anfälligerDateiname() {
        // Anfällig: 6-Zeichen Hex = 16 Millionen Möglichkeiten
        return String.format("%06x", new Random().nextInt(0xFFFFFF));
    }

    // Anfällig: Verwendung einer kleinen Teilmenge von UUID
    public String anfälligeKurzeUuid() {
        // Anfällig: Abschneiden der UUID verfehlt deren Zweck
        String uuid = java.util.UUID.randomUUID().toString();
        return uuid.substring(0, 8);  // Nur 32 Bits!
    }
}
# Anfällig: Kleiner Raum von Zufallswerten in Python
import random
import string

# Anfällig: Kurze Session-ID
def anfällige_session_id():
    # Anfällig: Nur 8 Hex-Zeichen = 32 Bits
    return format(random.randint(0, 0xFFFFFFFF), '08x')

# Anfällig: Verifizierungscode mit begrenzten Zeichen
def anfälliger_verifizierungscode():
    # Anfällig: 6-stelliger numerischer Code
    # Nur 1 Million Möglichkeiten
    return format(random.randint(0, 999999), '06d')

# Anfällig: Kurzes temporäres Passwort
def anfälliges_temp_passwort():
    # Anfällig: 6 Zeichen aus begrenztem Set
    # 26^6 = 308 Millionen - klingt viel, ist aber angreifbar
    chars = string.ascii_lowercase
    return ''.join(random.choice(chars) for _ in range(6))

# Anfällig: Kleiner zufälliger Datei-Identifikator
def anfällige_datei_id():
    # Anfällig: Nur 10000 mögliche Werte
    return f"file_{random.randint(0, 9999)}.tmp"

# Anfällig: Abgeschnittenes Token
def anfälliges_api_token():
    import secrets
    # Anfällig: Sicheren Zufall generieren, aber abschneiden!
    volles_token = secrets.token_hex(32)
    return volles_token[:8]  # Verfehlt den Zweck

# Anfällig: Kleiner numerischer Bereich
def anfällige_bestell_id():
    # Anfällig: Sequentiell-ish mit kleiner Zufallskomponente
    basis = 1000000
    zufalls_teil = random.randint(0, 999)  # Nur 1000 Werte!
    return basis + zufalls_teil
// Anfällig: Kleiner Raum von Zufallswerten in C
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

// Anfällig: 16-Bit Session-ID
unsigned short anfällige_session_id() {
    // Anfällig: Nur 65536 Möglichkeiten
    return rand() % 65536;
}

// Anfällig: 4-Zeichen-Code
void anfälliger_verifizierungscode(char *code) {
    // Anfällig: 4 alphanumerisch = 36^4 = 1,6 Millionen
    const char *chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    for (int i = 0; i < 4; i++) {
        code[i] = chars[rand() % 36];
    }
    code[4] = '\0';
}

// Anfällig: Kleiner Datei-ID-Bereich
int anfällige_temp_datei_id() {
    // Anfällig: Nur 1000 Werte
    return rand() % 1000;
}

// Anfällig: 3-Byte Zufallswert
void anfälliges_token(unsigned char *token) {
    // Anfällig: 24 Bits = 16 Millionen Möglichkeiten
    token[0] = rand() % 256;
    token[1] = rand() % 256;
    token[2] = rand() % 256;
}

// Anfällig: Kleines Salt für Hashing
void anfälliges_salt(unsigned char *salt) {
    // Anfällig: 2-Byte Salt = 65536 Werte
    // Rainbow-Tables werden praktikabel
    salt[0] = rand() % 256;
    salt[1] = rand() % 256;
}

Korrigierter Code (Java/Python)

// Korrigiert: Angemessener Zufallswerteraum in Java
import java.security.SecureRandom;
import java.util.Base64;

public class SichererWerteraum {

    private SecureRandom secureRandom = new SecureRandom();

    // Korrigiert: 128-Bit Session-ID (Minimum empfohlen)
    public String sichereSessionId() {
        // Korrigiert: 16 Bytes = 128 Bits = 3,4 × 10^38 Möglichkeiten
        byte[] bytes = new byte[16];
        secureRandom.nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }

    // Korrigiert: Sicherer Verifizierungscode (wenn kurzer Code erforderlich)
    public String sichererVerifizierungscode() {
        // Wenn kurzer Code für Benutzerfreundlichkeit benötigt, kombinieren mit:
        // - Rate-Limiting (max 5 Versuche)
        // - Kurze Gültigkeit (10 Minuten)
        // - Kontosperrung

        // Trotzdem angemessene Größe verwenden: 6-stellig mit Rate-Limiting
        // Oder 8-Zeichen alphanumerisch für bessere Sicherheit
        byte[] bytes = new byte[6];
        secureRandom.nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding()
                     .encodeToString(bytes).substring(0, 8);
    }

    // Korrigiert: Sicheres temporäres Passwort
    public String sicheresTempPasswort() {
        // Korrigiert: 32 Bytes = 256 Bits Entropie
        byte[] bytes = new byte[32];
        secureRandom.nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }

    // Korrigiert: Sicherer Datei-Identifikator
    public String sichereDateiId() {
        // Korrigiert: 128 Bits gewährleisten Eindeutigkeit
        byte[] bytes = new byte[16];
        secureRandom.nextBytes(bytes);
        return bytesToHex(bytes);
    }

    // Korrigiert: API-Token in voller Länge
    public String sicheresApiToken() {
        // Korrigiert: 256 Bits für API-Tokens
        byte[] bytes = new byte[32];
        secureRandom.nextBytes(bytes);
        return bytesToHex(bytes);
    }

    // Korrigiert: Sichere Bestellreferenz
    public String sichereBestellId() {
        // Korrigiert: UUID verwenden, die 122 zufällige Bits bietet
        return java.util.UUID.randomUUID().toString();
    }

    private String bytesToHex(byte[] bytes) {
        StringBuilder sb = new StringBuilder();
        for (byte b : bytes) {
            sb.append(String.format("%02x", b));
        }
        return sb.toString();
    }
}
# Korrigiert: Angemessener Zufallswerteraum in Python
import secrets
import os

# Korrigiert: 128-Bit Session-ID
def sichere_session_id():
    # Korrigiert: 32 Hex-Zeichen = 128 Bits
    return secrets.token_hex(16)

# Korrigiert: Sicherer Verifizierungscode
def sicherer_verifizierungscode():
    # Korrigiert: Für kurze Codes mit Sicherheitskontrollen kombinieren:
    # - Rate-Limiting
    # - Kurze TTL (5-10 Minuten)
    # - Kontosperrung nach N Versuchen

    # Wenn numerisch erforderlich, mindestens 8 Ziffern verwenden
    return format(secrets.randbelow(100000000), '08d')

# Korrigiert: Sicheres temporäres Passwort
def sicheres_temp_passwort():
    # Korrigiert: 256 Bits URL-sicher
    return secrets.token_urlsafe(32)

# Korrigiert: Sicherer Datei-Identifikator
def sichere_datei_id():
    # Korrigiert: 128 Bits gewährleisten Kollisionsresistenz
    return secrets.token_hex(16)

# Korrigiert: API-Token in voller Länge
def sicheres_api_token():
    # Korrigiert: 256 Bits für Hochsicherheits-Tokens
    return secrets.token_hex(32)

# Korrigiert: Sichere Bestellreferenz
import uuid

def sichere_bestell_id():
    # Korrigiert: UUID4 bietet 122 zufällige Bits
    return str(uuid.uuid4())

# Korrigiert: Kryptografisches Salt
def sicheres_salt():
    # Korrigiert: 16 Bytes (128 Bits) Minimum für Salts
    return os.urandom(16)

# Korrigiert: Erforderliche Entropie für gegebenes Sicherheitsniveau berechnen
def berechne_erforderliche_bits(versuche_pro_sekunde, sicherheits_jahre,
                                erfolgswahrscheinlichkeit=1e-6):
    """
    Berechnet benötigte Bits für gegebene Sicherheitsparameter.

    Beispiel: 10000 Versuche/Sek, 1 Jahr Schutz, 1-zu-Million Erfolg
    erfordert etwa 52 Bits Minimum.
    """
    gesamt_versuche = versuche_pro_sekunde * 86400 * 365 * sicherheits_jahre
    erforderlicher_raum = gesamt_versuche / erfolgswahrscheinlichkeit
    import math
    return math.ceil(math.log2(erforderlicher_raum))
// Korrigiert: Angemessener Zufallswerteraum in C
#include <openssl/rand.h>
#include <stdio.h>
#include <string.h>

// Korrigiert: 128-Bit Session-ID
int sichere_session_id(char *session_id, size_t max_len) {
    unsigned char bytes[16];  // 128 Bits

    if (RAND_bytes(bytes, sizeof(bytes)) != 1) {
        return -1;
    }

    // In Hex konvertieren (32 Zeichen + Null)
    if (max_len < 33) return -1;

    for (int i = 0; i < 16; i++) {
        sprintf(session_id + (i * 2), "%02x", bytes[i]);
    }

    return 0;
}

// Korrigiert: Sicherer Verifizierungscode
int sicherer_verifizierungscode(char *code, size_t max_len) {
    // Korrigiert: 8-Byte Zufall für Code-Generierung
    unsigned char bytes[8];

    if (RAND_bytes(bytes, sizeof(bytes)) != 1) {
        return -1;
    }

    // In alphanumerisch konvertieren (angemessene Länge)
    const char *chars = "0123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnpqrstuvwxyz";
    int char_count = strlen(chars);

    for (int i = 0; i < 8 && i < (int)max_len - 1; i++) {
        code[i] = chars[bytes[i] % char_count];
    }
    code[8] = '\0';

    return 0;
}

// Korrigiert: Sicherer Datei-Identifikator
int sichere_datei_id(char *file_id, size_t max_len) {
    unsigned char bytes[16];  // 128 Bits

    if (RAND_bytes(bytes, sizeof(bytes)) != 1) {
        return -1;
    }

    if (max_len < 33) return -1;

    for (int i = 0; i < 16; i++) {
        sprintf(file_id + (i * 2), "%02x", bytes[i]);
    }

    return 0;
}

// Korrigiert: Sichere Salt-Generierung
int sicheres_salt(unsigned char *salt, size_t len) {
    // Korrigiert: Mindestens 16 Bytes für Salts
    if (len < 16) {
        return -1;  // Mindestgröße erzwingen
    }

    return RAND_bytes(salt, len) == 1 ? 0 : -1;
}

Die Korrektur verwendet ausreichende Werteräume: mindestens 128 Bits für Identifikatoren, 256 Bits für kryptografische Tokens.


Ausgenutzt in der Praxis

Kurze Session-ID Hijacking (Verschiedene)

Mehrere Webanwendungen mit 32-Bit Session-IDs wurden durch Brute-Force-Angriffe ausgenutzt, die es Angreifern ermöglichten, aktive Sessions zu kapern.

Eine SYN-Cookies-Implementierung, die auf 32-Bit-Schlüssel beschränkt war, ermöglichte Angreifern, alle möglichen Werte per Brute-Force zu durchsuchen.


Tools zum Testen/Ausnutzen

  • Burp Suite Intruder — Kann kleine Werteräume durch automatisierte Anfragen aufzählen.

  • Hydra — Netzwerk-Login-Cracker, der Authentifizierungscodes per Brute-Force knacken kann.

  • Benutzerdefinierte Skripte — Einfache Skripte können kleine Räume in Minuten aufzählen.


CVE-Beispiele

  • CVE-2002-0583 — Nur 5 alphanumerische Zeichen für Dateinamen.

  • CVE-2003-1230 — SYN-Cookies auf 32-Bit-Schlüssel beschränkt.


Referenzen

  1. MITRE Corporation. "CWE-334: Small Space of Random Values." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/334.html

  2. OWASP Foundation. "Session Management Cheat Sheet." https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html

  3. NIST. "FIPS 140-2: Security Requirements for Cryptographic Modules." https://csrc.nist.gov/publications/detail/fips/140/2/final