Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)

Description

Use of Cryptographically Weak PRNG occurs when software uses a pseudo-random number generator (PRNG) that is not suitable for security-sensitive contexts. Standard PRNGs like rand(), Math.random(), or java.util.Random are designed for speed and statistical distribution, not unpredictability. Their output can be predicted if the seed is known or if enough output is observed. For security purposes like session tokens, encryption keys, or CSRF tokens, cryptographically secure PRNGs (CSPRNGs) must be used.

Risk

Weak PRNGs have enabled numerous attacks. Predictable session IDs allow session hijacking. Predictable CSRF tokens enable cross-site request forgery. Weak random numbers in cryptographic protocols enable key recovery. Famous examples include the Debian OpenSSL bug (CVE-2008-0166) where weak randomness reduced the keyspace to only 32,767 possible keys, affecting all keys generated on affected systems for 2 years.

Solution

Use cryptographically secure random number generators: os.urandom() or secrets module in Python, SecureRandom in Java, crypto.randomBytes() in Node.js, random_bytes() in PHP. Never seed CSPRNGs with predictable values. Use the operating system's entropy sources (/dev/urandom on Unix, CryptGenRandom on Windows). For web applications, use framework-provided secure token generators. Never implement custom random number generation for security purposes.

Common Consequences

ImpactDetails
Access ControlScope: Session Hijacking

Predictable session tokens allow attackers to impersonate users.
ConfidentialityScope: Key Recovery

Weak randomness in key generation allows cryptographic attacks.
IntegrityScope: Token Forgery

Predictable CSRF tokens, nonces, or unique IDs can be forged.

Example Code + Solution Code

Vulnerable Code

# VULNERABLE: Using random module for security
import random
import string

def generate_session_id():
    # random.choice is not cryptographically secure!
    return ''.join(random.choice(string.ascii_letters + string.digits)
                   for _ in range(32))

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

def generate_csrf_token():
    # Can be predicted if seed is known
    random.seed()  # Often seeded with time
    return hex(random.getrandbits(128))

def generate_api_key():
    # NOT secure!
    return ''.join(random.choices(string.hexdigits, k=32))
// VULNERABLE: Using java.util.Random
import java.util.Random;

public class WeakRandomGenerator {

    private Random random = new Random();  // Not cryptographically secure!

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

    public String generateToken() {
        // Seeding with time makes it predictable
        Random r = new Random(System.currentTimeMillis());
        return Long.toHexString(r.nextLong());
    }

    // Even worse: constant seed
    public int generateOTP() {
        Random r = new Random(12345);  // Same sequence every time!
        return r.nextInt(1000000);
    }
}
// VULNERABLE: Using Math.random()
function generateSessionId() {
    // Math.random() is NOT cryptographically secure
    let id = '';
    for (let i = 0; i < 32; i++) {
        id += Math.floor(Math.random() * 16).toString(16);
    }
    return id;  // Predictable!
}

function generateCSRFToken() {
    // Can be predicted/brute-forced
    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;  // Weak!
}
// VULNERABLE: Using rand() or mt_rand()
function generateToken() {
    // rand() has very weak randomness
    return md5(rand());
}

function generateSessionId() {
    // mt_rand() can be predicted after observing outputs
    $id = '';
    for ($i = 0; $i < 32; $i++) {
        $id .= dechex(mt_rand(0, 15));
    }
    return $id;
}

function generateApiKey() {
    // Predictable time-based seed
    srand(time());
    return sha1(rand());
}

Fixed Code

# SAFE: Using secrets module (Python 3.6+)
import secrets
import string

def generate_session_id():
    # secrets.token_hex uses os.urandom internally
    return secrets.token_hex(32)  # 64 character hex string

def generate_password_reset_token():
    # URL-safe token
    return secrets.token_urlsafe(32)

def generate_csrf_token():
    # Cryptographically secure
    return secrets.token_hex(16)

def generate_api_key():
    # 256-bit key
    return secrets.token_hex(32)

def generate_otp():
    # Secure random digit string
    return ''.join(secrets.choice(string.digits) for _ in range(6))

def generate_secure_password(length=16):
    alphabet = string.ascii_letters + string.digits + string.punctuation
    # Ensure minimum complexity
    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)

# Or using os.urandom directly
import os
import base64

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

public class SecureRandomGenerator {

    // SecureRandom is cryptographically strong
    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();
    }

    // For UUID generation
    public String generateSecureUUID() {
        byte[] bytes = new byte[16];
        secureRandom.nextBytes(bytes);
        // Set version to 4 (random)
        bytes[6] = (byte) ((bytes[6] & 0x0f) | 0x40);
        // Set variant
        bytes[8] = (byte) ((bytes[8] & 0x3f) | 0x80);
        return formatUUID(bytes);
    }
}
// SAFE: Using crypto module in Node.js
const crypto = require('crypto');

function generateSessionId() {
    // crypto.randomBytes uses 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;
}

// For browser JavaScript, use Web Crypto API
function generateSecureTokenBrowser() {
    const array = new Uint8Array(32);
    crypto.getRandomValues(array);
    return Array.from(array, byte => byte.toString(16).padStart(2, '0')).join('');
}

// UUID v4 generation
function generateUUID() {
    return crypto.randomUUID();  // Node.js 14.17+
}
// SAFE: Using random_bytes() in PHP 7+
function generateToken(): string {
    // random_bytes uses 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);  // Cryptographically secure
    }
    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;
}

// For older PHP (< 7), use openssl_random_pseudo_bytes
function generateTokenLegacy(): string {
    $bytes = openssl_random_pseudo_bytes(32, $strong);
    if (!$strong) {
        throw new Exception('Weak random generation');
    }
    return bin2hex($bytes);
}

Exploited in the Wild

Debian OpenSSL Bug (2008)

CVE-2008-0166: A Debian-specific patch to OpenSSL accidentally removed most entropy sources, reducing the randomness to only the process ID. This meant only 32,767 possible keys could be generated. SSH and SSL keys generated on affected systems for 2 years were compromised.

PHP Session ID Prediction (Multiple)

Multiple vulnerabilities in PHP's session ID generation using weak random functions have allowed session prediction attacks.

Java Cryptographic Weakness (Android, 2013)

Android apps using java.util.Random for Bitcoin wallet key generation led to theft of bitcoins due to predictable keys.


Tools to test/exploit


CVE Examples


References

  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