Insecure Randomness
debt(d5/e3/b3/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep and psalm, both specialist/SAST tools, and the code_pattern confirms they look for rand()/mt_rand()/uniqid() in security-sensitive contexts. This won't be caught by a default linter, but a configured semgrep rule will flag it — squarely at d5.
Closest to 'simple parameterised fix' (e3). The quick_fix is a straightforward replacement: swap rand()/mt_rand()/uniqid() with random_bytes(32) plus bin2hex() or base64_encode(). Each call site is a small, contained change. It's slightly more than a one-line patch (e1) because multiple call sites may exist and output encoding must be verified, but it doesn't require cross-file architectural refactoring — e3 is the right anchor.
Closest to 'localised tax' (b3). The insecure call is typically confined to token-generation or session-handling code — one or a few components. The rest of the codebase is unaffected. It applies to web and cli contexts but isn't a system-shaping architectural choice; it's a localized coding mistake with a focused remediation scope.
Closest to 'serious trap — contradicts how a similar concept works elsewhere' (t7). The misconception field is explicit: mt_rand() looks random (it passes statistical tests) so developers assume it is secure. The trap is that statistical randomness ≠ cryptographic unpredictability. Observing 624 outputs allows full state reconstruction — a severe consequence that directly contradicts the developer's intuition. This is worse than a documented gotcha (t5) but not quite 'the obvious way is always wrong' (t9) since many developers do know to reach for CSPRNG in security contexts, landing at t7.
Also Known As
TL;DR
Explanation
PHP's rand() and mt_rand() use Mersenne Twister — a PRNG designed for statistical randomness, not security. With enough outputs, its entire state can be reconstructed, making all past and future outputs predictable. Security-sensitive tokens (session IDs, CSRF tokens, password reset links, API keys, nonces) must use cryptographically secure randomness: random_bytes() (PHP 7+) or openssl_random_pseudo_bytes(). Always verify that the $strong parameter is true for openssl. Never use uniqid() (microsecond timestamp — predictable) or md5(time()) for security tokens.
Common Misconception
Why It Matters
Common Mistakes
- rand() or mt_rand() for password reset tokens — cryptographically predictable.
- uniqid() for session tokens — based on microsecond timestamp, guessable.
- md5(time()) or sha1(microtime()) — entirely predictable.
- Not using bin2hex() or base64_encode() on random_bytes() output — raw bytes may cause encoding issues.
Code Examples
// Predictable tokens — security disaster:
$resetToken = md5(time() . $userId); // Predictable timestamp
$apiKey = uniqid('key_', true); // Microsecond timestamp
$sessionId = substr(md5(mt_rand()), 0, 32); // mt_rand is predictable
$csrfToken = base64_encode(rand(0, 999999)); // Only 1M possible values
// Cryptographically secure tokens:
$resetToken = bin2hex(random_bytes(32)); // 256-bit secure token
$apiKey = 'sk_' . bin2hex(random_bytes(32)); // Prefixed for scanning
$sessionId = bin2hex(random_bytes(32)); // 64 hex chars
$csrfToken = base64_encode(random_bytes(32)); // 2^256 possible values
// Verify reset token in constant time:
if (!hash_equals($storedToken, $submittedToken)) {
throw new InvalidTokenException();
}