Small Space of Random Values

Description

Small Space of Random Values is a vulnerability that occurs when the number of possible random values that a product can generate is smaller than needed for security, making the system susceptible to brute force attacks. Even when using a cryptographically secure random number generator, if the output is constrained to a small space (such as short strings, limited character sets, or truncated values), attackers can enumerate all possibilities in a feasible time frame. This effectively reduces the security of the system to the number of possible values rather than the theoretical strength of the random source. Examples include short session IDs, limited-length tokens, small numeric ranges for verification codes, and file names with insufficient uniqueness.

Risk

A small random value space allows attackers to brute force through all possible values in practical time. If session IDs are only 32 bits (about 4 billion values), an attacker making 10,000 requests per second can enumerate all possibilities in roughly 5 days. 16-bit values (65,536 possibilities) can be brute-forced in seconds. The risk is particularly severe for session management, password reset tokens, and authentication codes where successful guessing grants access. Attackers can parallelize brute force attempts across multiple machines, further reducing attack time. The vulnerability is often introduced when developers prioritize usability (shorter codes, memorable values) over security, or when legacy system constraints limit value length.

Solution

Ensure random values have sufficient size for their security context. Session identifiers should be at least 128 bits (16 bytes) as recommended by OWASP. Cryptographic keys should be at least 128 bits for symmetric encryption and 2048 bits for RSA. Use FIPS 140-2 or 140-3 compliant random number generators with adequate output sizes. Calculate the required value space based on the expected attack rate and desired security lifetime: for a value that should resist 10 million guesses per day for one year, you need at least 52 bits (10M × 365 = ~3.6 billion attempts). Avoid truncating random values after generation. Use URL-safe base64 encoding to maximize entropy per character when length is constrained.

Common Consequences

ImpactDetails
Access ControlScope: Access Control

Attackers can brute force through the small value space to guess valid session IDs, tokens, or identifiers, bypassing authentication and authorization controls.
ConfidentialityScope: Confidentiality

Small cryptographic key spaces or nonces can be brute-forced, compromising the confidentiality of encrypted data.
IntegrityScope: Integrity

Predictable file names or resource identifiers with small value spaces allow attackers to access or overwrite resources belonging to other users.

Example Code

Vulnerable Code (Java/Python)

The following examples demonstrate small random value space vulnerabilities:

// Vulnerable: Small space of random values in Java
import java.util.Random;

public class VulnerableSmallSpace {

    // Vulnerable: Only 8-byte session ID (too short)
    public String vulnerableSessionId() {
        Random rand = new Random();
        // Vulnerable: 8 hex chars = 32 bits = 4 billion possibilities
        return String.format("%08x", rand.nextInt());
    }

    // Vulnerable: 5-character alphanumeric code
    public String vulnerableVerificationCode() {
        // Vulnerable: Only 5 chars from 36-char alphabet
        // 36^5 = 60,466,176 possibilities - brute forced in minutes
        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();
    }

    // Vulnerable: 4-digit PIN
    public String vulnerablePin() {
        // Vulnerable: Only 10,000 possibilities
        return String.format("%04d", new Random().nextInt(10000));
    }

    // Vulnerable: Short file names
    public String vulnerableFileName() {
        // Vulnerable: 6-char hex = 16 million possibilities
        return String.format("%06x", new Random().nextInt(0xFFFFFF));
    }

    // Vulnerable: Using small subset of UUID
    public String vulnerableShortUuid() {
        // Vulnerable: Truncating UUID defeats its purpose
        String uuid = java.util.UUID.randomUUID().toString();
        return uuid.substring(0, 8);  // Only 32 bits!
    }
}
# Vulnerable: Small space of random values in Python
import random
import string

# Vulnerable: Short session ID
def vulnerable_session_id():
    # Vulnerable: Only 8 hex characters = 32 bits
    return format(random.randint(0, 0xFFFFFFFF), '08x')

# Vulnerable: Limited character verification code
def vulnerable_verification_code():
    # Vulnerable: 6-digit numeric code
    # Only 1 million possibilities
    return format(random.randint(0, 999999), '06d')

# Vulnerable: Short temporary password
def vulnerable_temp_password():
    # Vulnerable: 6 chars from limited set
    # 26^6 = 308 million - sounds big but attackable
    chars = string.ascii_lowercase
    return ''.join(random.choice(chars) for _ in range(6))

# Vulnerable: Small random file identifier
def vulnerable_file_id():
    # Vulnerable: Only 10000 possible values
    return f"file_{random.randint(0, 9999)}.tmp"

# Vulnerable: Truncated token
def vulnerable_api_token():
    import secrets
    # Vulnerable: Generating secure random but truncating!
    full_token = secrets.token_hex(32)
    return full_token[:8]  # Defeats the purpose

# Vulnerable: Small numeric range
def vulnerable_order_id():
    # Vulnerable: Sequential-ish with small random component
    base = 1000000
    random_part = random.randint(0, 999)  # Only 1000 values!
    return base + random_part
// Vulnerable: Small space of random values in C
#include <stdlib.h>
#include <stdio.h>
#include <time.h>

// Vulnerable: 16-bit session ID
unsigned short vulnerable_session_id() {
    // Vulnerable: Only 65536 possibilities
    return rand() % 65536;
}

// Vulnerable: 4-character code
void vulnerable_verification_code(char *code) {
    // Vulnerable: 4 alphanumeric = 36^4 = 1.6 million
    const char *chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
    for (int i = 0; i < 4; i++) {
        code[i] = chars[rand() % 36];
    }
    code[4] = '\0';
}

// Vulnerable: Small file ID range
int vulnerable_temp_file_id() {
    // Vulnerable: Only 1000 values
    return rand() % 1000;
}

// Vulnerable: 3-byte random value
void vulnerable_token(unsigned char *token) {
    // Vulnerable: 24 bits = 16 million possibilities
    token[0] = rand() % 256;
    token[1] = rand() % 256;
    token[2] = rand() % 256;
}

// Vulnerable: Small salt for hashing
void vulnerable_salt(unsigned char *salt) {
    // Vulnerable: 2-byte salt = 65536 values
    // Rainbow tables become feasible
    salt[0] = rand() % 256;
    salt[1] = rand() % 256;
}

Fixed Code (Java/Python)

// Fixed: Adequate random value space in Java
import java.security.SecureRandom;
import java.util.Base64;

public class SecureValueSpace {

    private SecureRandom secureRandom = new SecureRandom();

    // Fixed: 128-bit session ID (minimum recommended)
    public String secureSessionId() {
        // Fixed: 16 bytes = 128 bits = 3.4 × 10^38 possibilities
        byte[] bytes = new byte[16];
        secureRandom.nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }

    // Fixed: Secure verification code (when short code required)
    public String secureVerificationCode() {
        // If short code is needed for usability, combine with:
        // - Rate limiting (max 5 attempts)
        // - Short expiration (10 minutes)
        // - Account lockout

        // Still use reasonable size: 6-digit with rate limiting
        // Or 8-character alphanumeric for better security
        byte[] bytes = new byte[6];
        secureRandom.nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding()
                     .encodeToString(bytes).substring(0, 8);
    }

    // Fixed: Secure temporary password
    public String secureTempPassword() {
        // Fixed: 32 bytes = 256 bits of entropy
        byte[] bytes = new byte[32];
        secureRandom.nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }

    // Fixed: Secure file identifier
    public String secureFileId() {
        // Fixed: 128 bits ensures uniqueness
        byte[] bytes = new byte[16];
        secureRandom.nextBytes(bytes);
        return bytesToHex(bytes);
    }

    // Fixed: Full-length API token
    public String secureApiToken() {
        // Fixed: 256 bits for API tokens
        byte[] bytes = new byte[32];
        secureRandom.nextBytes(bytes);
        return bytesToHex(bytes);
    }

    // Fixed: Secure order reference
    public String secureOrderId() {
        // Fixed: Use UUID which provides 122 random bits
        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();
    }
}
# Fixed: Adequate random value space in Python
import secrets
import os

# Fixed: 128-bit session ID
def secure_session_id():
    # Fixed: 32 hex chars = 128 bits
    return secrets.token_hex(16)

# Fixed: Secure verification code
def secure_verification_code():
    # Fixed: For short codes, combine with security controls:
    # - Rate limiting
    # - Short TTL (5-10 minutes)
    # - Account lockout after N attempts

    # If numeric required, use 8 digits minimum
    return format(secrets.randbelow(100000000), '08d')

# Fixed: Secure temporary password
def secure_temp_password():
    # Fixed: 256 bits URL-safe
    return secrets.token_urlsafe(32)

# Fixed: Secure file identifier
def secure_file_id():
    # Fixed: 128 bits ensures collision resistance
    return secrets.token_hex(16)

# Fixed: Full-length API token
def secure_api_token():
    # Fixed: 256 bits for high-security tokens
    return secrets.token_hex(32)

# Fixed: Secure order reference
import uuid

def secure_order_id():
    # Fixed: UUID4 provides 122 random bits
    return str(uuid.uuid4())

# Fixed: Cryptographic salt
def secure_salt():
    # Fixed: 16 bytes (128 bits) minimum for salts
    return os.urandom(16)

# Fixed: Calculate required entropy for given security level
def calculate_required_bits(attempts_per_second, security_years,
                            success_probability=1e-6):
    """
    Calculate bits needed for given security parameters.

    Example: 10000 attempts/sec, 1 year protection, 1-in-million success
    requires about 52 bits minimum.
    """
    total_attempts = attempts_per_second * 86400 * 365 * security_years
    required_space = total_attempts / success_probability
    import math
    return math.ceil(math.log2(required_space))
// Fixed: Adequate random value space in C
#include <openssl/rand.h>
#include <stdio.h>
#include <string.h>

// Fixed: 128-bit session ID
int secure_session_id(char *session_id, size_t max_len) {
    unsigned char bytes[16];  // 128 bits

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

    // Convert to hex (32 chars + null)
    if (max_len < 33) return -1;

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

    return 0;
}

// Fixed: Secure verification code
int secure_verification_code(char *code, size_t max_len) {
    // Fixed: 8-byte random for code generation
    unsigned char bytes[8];

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

    // Convert to alphanumeric (reasonable length)
    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;
}

// Fixed: Secure file identifier
int secure_file_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;
}

// Fixed: Secure salt generation
int secure_salt(unsigned char *salt, size_t len) {
    // Fixed: At least 16 bytes for salts
    if (len < 16) {
        return -1;  // Enforce minimum size
    }

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

The fix uses sufficient value spaces: 128 bits minimum for identifiers, 256 bits for cryptographic tokens.


Exploited in the Wild

Short Session ID Hijacking (Various)

Multiple web applications with 32-bit session IDs have been exploited through brute force attacks, allowing attackers to hijack active sessions.

A SYN cookies implementation limited to 32-bit keys allowed attackers to brute force through all possible values.


Tools to Test/Exploit

  • Burp Suite Intruder — Can enumerate small value spaces through automated requests.

  • Hydra — Network login cracker that can brute force authentication codes.

  • Custom scripts — Simple scripts can enumerate small spaces in minutes.


CVE Examples


References

  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