Predictable Value Range from Previous Values

Description

Predictable Value Range from Previous Values is a vulnerability that occurs when a random number generator produces sequences that allow attackers to infer a relatively small range of possibilities for the next value based on observing previous outputs. Unlike exact prediction where the next value can be calculated precisely, this weakness involves situations where the output space becomes constrained based on pattern analysis. For example, a generator might produce monotonically increasing values within a range, or exhibit clustering patterns, or have observable relationships between consecutive outputs that narrow the prediction space. This reduces the attacker's brute force effort from searching the full value space to searching a much smaller subset.

Risk

Range predictability significantly reduces the security of systems relying on random values. If an attacker can narrow session token possibilities from 2^128 to 2^16 by observing patterns, brute force becomes feasible. Even partial prediction dramatically improves attack efficiency - reducing the search space by a factor of 1000 makes attacks 1000 times faster. The vulnerability enables probabilistic attacks where attackers try the most likely values first. Patterns like monotonic sequences, value clustering, or bounded variations from previous outputs all provide exploitable information. The risk is amplified when attackers can make many guesses quickly, such as with online session hijacking attempts. Systems may appear to use sufficient entropy while actually constraining outputs to predictable ranges.

Solution

Use cryptographically secure random number generators that produce outputs with uniform distribution across the entire value space regardless of previous outputs. Each generated value should be statistically independent of all previous values. Avoid generators with patterns, biases, or output relationships even if exact prediction is not possible. Test random number generators with statistical analysis to detect non-uniform distributions or correlations. Implement proper seeding with adequate entropy. Use established cryptographic libraries rather than custom implementations. Conduct security analysis to ensure generated values don't exhibit exploitable patterns.

Common Consequences

ImpactDetails
Access ControlScope: Access Control

Attackers who can narrow the prediction range can more efficiently brute force authentication tokens, session IDs, and credentials.
ConfidentialityScope: Confidentiality

Cryptographic keys or nonces with predictable ranges may be more susceptible to cryptanalytic attacks.
IntegrityScope: Integrity

Transaction IDs or integrity tokens with predictable ranges enable more efficient forgery attacks.

Example Code

Vulnerable Code (Python/C)

The following examples demonstrate range-predictable value generation:

# Vulnerable: Predictable value ranges
import random
import time

# Vulnerable: Monotonically increasing with bounds
class VulnerableIncreasing:
    def __init__(self):
        self.current = random.randint(0, 1000)

    def next(self):
        # Vulnerable: Always increases by 1-10
        # Knowing current, next is in range [current+1, current+10]
        increment = random.randint(1, 10)
        self.current += increment
        return self.current

# Vulnerable: Bounded variation from previous
class VulnerableBoundedVariation:
    def __init__(self):
        self.previous = random.randint(0, 10000)

    def next(self):
        # Vulnerable: New value within ±100 of previous
        # Range is predictable: [previous-100, previous+100]
        variation = random.randint(-100, 100)
        self.previous = max(0, self.previous + variation)
        return self.previous

# Vulnerable: Cyclic with predictable phase
class VulnerableCyclic:
    def __init__(self):
        self.counter = 0

    def next(self):
        self.counter += 1
        # Vulnerable: Follows sine wave pattern
        # Range depends on phase
        import math
        base = int(5000 + 4000 * math.sin(self.counter * 0.1))
        noise = random.randint(-50, 50)
        return base + noise

# Vulnerable: Clustering around recent values
class VulnerableClustering:
    def __init__(self):
        self.history = [random.randint(0, 10000) for _ in range(5)]

    def next(self):
        # Vulnerable: New value clusters around average
        avg = sum(self.history) / len(self.history)
        spread = random.randint(-500, 500)
        new_value = int(avg + spread)
        self.history.pop(0)
        self.history.append(new_value)
        return new_value

# Vulnerable: Biased toward certain ranges
class VulnerableBiased:
    def next(self):
        # Vulnerable: 80% of values fall in narrow range
        if random.random() < 0.8:
            return random.randint(4000, 6000)  # 2000 range
        else:
            return random.randint(0, 10000)    # Full range

# Vulnerable: Time-bounded predictions
class VulnerableTimeBounded:
    def next(self):
        # Vulnerable: Value constrained by time of day
        hour = time.localtime().tm_hour
        # Morning: 0-3000, Afternoon: 3000-6000, Evening: 6000-10000
        if hour < 12:
            return random.randint(0, 3000)
        elif hour < 18:
            return random.randint(3000, 6000)
        else:
            return random.randint(6000, 10000)
// Vulnerable: Range-predictable generation in C
#include <stdlib.h>
#include <time.h>
#include <math.h>

// Vulnerable: Monotonic increase
static unsigned int mono_value = 0;

unsigned int vulnerable_monotonic() {
    // Vulnerable: Increment bounded [1, 100]
    mono_value += (rand() % 100) + 1;
    return mono_value;
}

// Vulnerable: Random walk with drift
static int walk_value = 5000;

int vulnerable_random_walk() {
    // Vulnerable: Bounded step size
    int step = (rand() % 201) - 100;  // -100 to +100
    walk_value += step;

    // Keep in bounds
    if (walk_value < 0) walk_value = 0;
    if (walk_value > 10000) walk_value = 10000;

    return walk_value;
}

// Vulnerable: Oscillating pattern
static int phase = 0;

unsigned int vulnerable_oscillating() {
    phase++;
    // Vulnerable: Follows predictable oscillation
    int base = 5000 + (int)(4000 * sin(phase * 0.05));
    int noise = rand() % 100 - 50;
    return base + noise;
}

// Vulnerable: Quantized output
unsigned int vulnerable_quantized() {
    unsigned int raw = rand();
    // Vulnerable: Output always multiple of 1000
    // Only 10 possible values: 0, 1000, 2000, ..., 9000
    return (raw % 10) * 1000;
}

// Vulnerable: Biased distribution
unsigned int vulnerable_biased() {
    int r = rand() % 100;
    // Vulnerable: Most values in narrow band
    if (r < 70) {
        return 4500 + (rand() % 1000);  // 4500-5500 (70%)
    } else if (r < 90) {
        return 3000 + (rand() % 1500);  // 3000-4500 (20%)
    } else {
        return rand() % 10000;           // Full range (10%)
    }
}

// Vulnerable: Previous-dependent bounds
static unsigned int prev_value = 5000;

unsigned int vulnerable_dependent_bounds() {
    // Vulnerable: Range depends on previous value
    unsigned int low = prev_value > 1000 ? prev_value - 1000 : 0;
    unsigned int high = prev_value + 1000;
    if (high > 10000) high = 10000;

    prev_value = low + (rand() % (high - low + 1));
    return prev_value;
}

Fixed Code (Python/C)

# Fixed: Unpredictable full-range generation
import secrets
import os

# Fixed: Full-range uniform distribution
class SecureUniform:
    @staticmethod
    def next(max_value=10000):
        # Fixed: Each value independent, full range possible
        return secrets.randbelow(max_value)

# Fixed: No pattern between consecutive values
class SecureIndependent:
    @staticmethod
    def next():
        # Fixed: 64-bit random, no relationship to previous
        return secrets.randbits(64)

# Fixed: Secure if monotonic needed for ordering
class SecureMonotonic:
    def __init__(self):
        self.counter = 0
        self.random_base = secrets.randbits(64)

    def next(self):
        self.counter += 1
        # Fixed: Random component prevents range prediction
        random_part = secrets.randbits(32)
        return (self.random_base + self.counter) ^ random_part

# Fixed: No clustering
class SecureNoClustering:
    @staticmethod
    def next():
        # Fixed: Each value independent of history
        return secrets.token_hex(16)

# Fixed: Uniform distribution
class SecureUnbiased:
    @staticmethod
    def next(max_value=10000):
        # Fixed: secrets.randbelow is uniform
        return secrets.randbelow(max_value)

# Fixed: Time-independent
class SecureTimeIndependent:
    @staticmethod
    def next():
        # Fixed: No time dependency in output
        return secrets.randbits(64)

# Fixed: Full entropy byte array
class SecureBytes:
    @staticmethod
    def next(length=16):
        # Fixed: Each byte independent, full range 0-255
        return os.urandom(length)
// Fixed: Unpredictable full-range generation in C
#include <openssl/rand.h>
#include <stdio.h>

// Fixed: Full-range random
int secure_random(unsigned int *value, unsigned int max) {
    unsigned int random_value;

    if (RAND_bytes((unsigned char*)&random_value, sizeof(random_value)) != 1) {
        return -1;
    }

    // Fixed: Uniform distribution in [0, max)
    *value = random_value % max;
    return 0;
}

// Fixed: Independent of previous values
int secure_independent(unsigned char *buffer, size_t len) {
    // Fixed: Each byte independent, full 0-255 range
    return RAND_bytes(buffer, len) == 1 ? 0 : -1;
}

// Fixed: Secure monotonic if ordering required
typedef struct {
    unsigned long long counter;
    unsigned char key[32];
} SecureMonotonic;

int secure_monotonic_init(SecureMonotonic *sm) {
    if (RAND_bytes(sm->key, sizeof(sm->key)) != 1) {
        return -1;
    }
    sm->counter = 0;
    return 0;
}

int secure_monotonic_next(SecureMonotonic *sm, unsigned char *output) {
    sm->counter++;

    // Fixed: HMAC ensures unpredictability of output
    unsigned char input[8];
    for (int i = 0; i < 8; i++) {
        input[i] = (sm->counter >> (56 - i*8)) & 0xFF;
    }

    // Use HMAC-SHA256 to generate output
    // (simplified - actual implementation would use OpenSSL HMAC)
    return RAND_bytes(output, 32);
}

// Fixed: No bias
int secure_unbiased(unsigned int *value, unsigned int max) {
    unsigned int random_value;
    unsigned int threshold = (UINT_MAX - (UINT_MAX % max)) % max;

    // Fixed: Rejection sampling for unbiased result
    do {
        if (RAND_bytes((unsigned char*)&random_value, sizeof(random_value)) != 1) {
            return -1;
        }
    } while (random_value < threshold);

    *value = random_value % max;
    return 0;
}

// Fixed: Full entropy output
int secure_full_entropy(unsigned char *buffer, size_t len) {
    // Fixed: Each byte has full 8 bits of entropy
    return RAND_bytes(buffer, len) == 1 ? 0 : -1;
}

The fix ensures each value is statistically independent with uniform distribution across the full value space.


Exploited in the Wild

Statistical Analysis Attacks

Various systems using biased or range-limited random number generators have been exploited through statistical analysis revealing predictable patterns.

Session Token Range Analysis

Web applications with clustered session token distributions have been attacked by focusing guesses on likely value ranges.


Tools to Test/Exploit


CVE Examples

No specific CVEs directly reference this CWE, but the underlying patterns appear in many vulnerabilities involving weak random number generators.


References

  1. MITRE Corporation. "CWE-343: Predictable Value Range from Previous Values." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/343.html

  2. NIST. "A Statistical Test Suite for Random and Pseudorandom Number Generators." SP 800-22. https://csrc.nist.gov/publications/detail/sp/800-22/rev-1a/final

  3. Goldberg, I., Wagner, D. "Randomness and the Netscape Browser." Dr. Dobb's Journal, 1996.