← CodeClarityLab Home
Browse by Category
+ added · updated 7d
← Back to glossary

Insecure Randomness

security PHP 7.0+ Intermediate
debt(d5/e3/b3/t7)
d5 Detectability Operational debt — how invisible misuse is to your safety net

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.

e3 Effort Remediation debt — work required to fix once spotted

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.

b3 Burden Structural debt — long-term weight of choosing wrong

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.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

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.

About DEBT scoring →

Also Known As

mt_rand security CSPRNG random_bytes predictable token

TL;DR

Using non-cryptographic random functions (rand(), mt_rand(), array_rand()) for security tokens — these are predictable and enable token forgery, session prediction, and CSRF bypass.

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

mt_rand() is secure because the output looks random — Mersenne Twister passes statistical randomness tests but is not cryptographically secure; observing 624 consecutive outputs allows reconstructing the full state.

Why It Matters

A password reset token generated with mt_rand() can be predicted by an attacker who observes other tokens from the same process — enabling account takeover for any user who requests a reset.

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

✗ Vulnerable
// 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
✓ Fixed
// 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();
}

Added 16 Mar 2026
Edited 22 Mar 2026
Views 37
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 1 ping S 0 pings S 2 pings M 0 pings T 0 pings W 0 pings T 3 pings F 0 pings S 1 ping S 0 pings M 1 ping T 0 pings W 0 pings T 2 pings F 0 pings S 0 pings S 0 pings M 1 ping T 0 pings W 2 pings T 0 pings F
No pings yet today
Amazonbot 1
Amazonbot 10 Perplexity 6 Google 4 ChatGPT 4 SEMrush 3 Unknown AI 2 Ahrefs 2 Majestic 1 Bing 1
crawler 28 crawler_json 5
DEV INTEL Tools & Severity
🟠 High ⚙ Fix effort: Low
⚡ Quick Fix
Replace rand()/mt_rand()/uniqid() with random_bytes(32) for tokens; use bin2hex() or base64_encode() for string output
📦 Applies To
PHP 7.0+ web cli
🔗 Prerequisites
🔍 Detection Hints
rand( or mt_rand( or uniqid( used in token/session/password-reset context
Auto-detectable: ✓ Yes semgrep psalm
⚠ Related Problems
🤖 AI Agent
Confidence: High False Positives: Low ✓ Auto-fixable Fix: Low Context: Line
CWE-330 CWE-338

✓ schema.org compliant