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
| Impact | Details |
|---|---|
| Access Control | Scope: Access Control Attackers who can narrow the prediction range can more efficiently brute force authentication tokens, session IDs, and credentials. |
| Confidentiality | Scope: Confidentiality Cryptographic keys or nonces with predictable ranges may be more susceptible to cryptanalytic attacks. |
| Integrity | Scope: 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
-
ENT — Statistical analysis of randomness.
-
NIST Statistical Test Suite — Tests for random number quality.
-
Custom analysis scripts — Detect patterns and range constraints.
CVE Examples
No specific CVEs directly reference this CWE, but the underlying patterns appear in many vulnerabilities involving weak random number generators.
References
-
MITRE Corporation. "CWE-343: Predictable Value Range from Previous Values." Common Weakness Enumeration. https://cwe.mitre.org/data/definitions/343.html
-
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
-
Goldberg, I., Wagner, D. "Randomness and the Netscape Browser." Dr. Dobb's Journal, 1996.