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

Predictable Token

security CWE-338 OWASP A2:2021 CVSS 7.5 PHP 7.0+ Intermediate

Also Known As

weak token guessable token insecure token generation

TL;DR

Tokens generated with md5(time()) or rand() are trivially guessable because their entropy source is predictable.

Explanation

Security tokens (password reset links, session IDs, API keys) must be unpredictable. Using md5(time()) produces only 86,400 distinct values per day — an attacker can brute-force all of them in seconds. rand() and mt_rand() are pseudorandom and their output can be predicted from a small number of observed values. The only correct source for security-sensitive random data is a CSPRNG: bin2hex(random_bytes(32)) in PHP.

Common Misconception

Using a hash like md5(time()) produces an unpredictable token. Timestamps have limited entropy — an attacker who knows roughly when a token was generated can brute-force the millisecond-range space in seconds. Use random_bytes(32) instead.

Why It Matters

Guessable tokens — for sessions, CSRF, password resets, or API keys — allow an attacker to forge or brute-force a valid token without ever authenticating.

Common Mistakes

  • Using rand() or mt_rand() which are seeded by time and guessable.
  • Using MD5 or SHA1 of a timestamp — the timestamp is known or estimable.
  • Sequential integer tokens for anything security-sensitive.
  • Short tokens (less than 128 bits) that make brute force feasible even with secure randomness.

Code Examples

✗ Vulnerable
// Predictable — attacker can guess or brute-force
\$token = md5(\$userId . time());
\$token = uniqid();   // only 7 bytes of entropy
\$token = rand();     // seeded from time — predictable
✓ Fixed
// 32 bytes = 256 bits of entropy — computationally unguessable
\$token = bin2hex(random_bytes(32)); // 64 hex chars

// Store hash in DB, send raw token to user
\$stored = hash('sha256', \$token);
\$db->insert('password_resets', [
    'user_id'    => \$userId,
    'token_hash' => \$stored,
    'expires_at' => date('Y-m-d H:i:s', time() + 3600),
]);

// Verify with constant-time comparison:
if (!hash_equals(\$stored, hash('sha256', \$inputToken))) abort(400);
if (\$record->expires_at < now()) abort(400);

Added 15 Mar 2026
Edited 22 Mar 2026
Views 38
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 1 ping T 0 pings W 1 ping T 0 pings F 1 ping S 0 pings S 0 pings M 0 pings T 0 pings W 1 ping T 0 pings F 4 pings S 1 ping S 1 ping M 0 pings T 0 pings W 1 ping T 0 pings F 0 pings S
No pings yet today
No pings yesterday
Perplexity 8 Ahrefs 6 Amazonbot 5 Google 5 Unknown AI 2 ChatGPT 2 SEMrush 1
crawler 29
DEV INTEL Tools & Severity
🔴 Critical ⚙ Fix effort: Low
⚡ Quick Fix
Use bin2hex(random_bytes(32)) for all tokens — password reset, email verification, API keys, CSRF; the 256-bit entropy makes brute force infeasible
📦 Applies To
PHP 7.0+ web
🔗 Prerequisites
🔍 Detection Hints
md5(uniqid()) md5(time()) rand() for tokens; sequential numeric token IDs; token derived from user ID + timestamp
Auto-detectable: ✓ Yes semgrep psalm phpstan
⚠ Related Problems
🤖 AI Agent
Confidence: High False Positives: Low ✓ Auto-fixable Fix: Low Context: Line Tests: Update
CWE-330 CWE-340 CWE-338

✓ schema.org compliant