Safeguard
Vulnerability Analysis

Insecure randomness vulnerabilities explained

CWE-338 explained through the Debian OpenSSL and Android Bitcoin SecureRandom breaches — how weak PRNGs get exploited, and how to detect and fix them.

Yukti Singhal
Security Analyst
7 min read

In August 2013, security researchers discovered that Android's implementation of java.security.SecureRandom was failing to properly seed itself on devices running Android 4.3 and earlier, due to an uninitialized OpenSSL PRNG. Wallet apps that relied on it to generate Bitcoin private keys produced predictable values, and researchers traced more than 55 BTC (roughly $5,700 at the time) stolen from affected wallets before Google patched the bug. Five years earlier, a Debian maintainer's 2006 patch to OpenSSL had stripped out a line of entropy-gathering code, cutting the possible SSH and SSL keys generated on Debian and Ubuntu systems down to about 32,768 per key type — a bug that went undetected for nearly two years. Both incidents trace back to the same root cause: insecure randomness. This post breaks down what an insecure randomness vulnerability actually is, why common PRNGs fail as security primitives, and how to find and fix the pattern before it ships.

What Is an Insecure Randomness Vulnerability?

An insecure randomness vulnerability is the use of a predictable or low-entropy random number generator to produce values — session tokens, password reset links, API keys, cryptographic nonces, or encryption keys — that an attacker can guess, reconstruct, or brute-force. It's tracked formally as CWE-338 ("Use of Cryptographically Weak Pseudo-Random Number Generator") and its sibling weaknesses CWE-330 (insufficiently random values), CWE-335 (incorrect PRNG seed usage), and CWE-337 (predictable seed). Under the OWASP Top 10 (2021 revision), it falls inside A02:2021 – Cryptographic Failures, the same category as broken TLS configs and weak hashing. The defect isn't that the code is "buggy" in a functional sense — a call to Math.random() or rand() works exactly as designed. The problem is that the developer used a general-purpose PRNG in a place that demanded a cryptographically secure one (a CSPRNG), and the two are not interchangeable.

Why Are Functions Like Math.random() and mt_rand() Considered Insecure?

They're insecure because their internal state can be reconstructed from observed output, which lets an attacker predict every past and future value. Most language-standard random functions — JavaScript's Math.random(), PHP's mt_rand(), Python's random module, and Java's java.util.Random — are built on the Mersenne Twister algorithm or comparable non-cryptographic generators. Mersenne Twister maintains 624 32-bit integers of internal state; once an attacker observes 624 consecutive outputs (easy to do if the values leak into URLs, cookies, or error messages), they can fully reconstruct the generator's state with public tools and predict all subsequent outputs with 100% accuracy. Python's own documentation for the random module states outright that it "should not be used for security purposes," and points developers to the secrets module, added specifically for this reason in Python 3.6 (released December 2016). These generators are fast and statistically well-distributed — good for simulations and games — but deterministic and reversible, which is disqualifying for anything security-sensitive.

What Real-World Breaches Were Caused by Insecure Randomness?

Two well-documented cases show the range of impact: mass key compromise and direct financial theft. The Debian OpenSSL bug (CVE-2008-0166) originated from a May 2006 code change that removed a call adding process-ID-based entropy to the key generation routine; the resulting keys had only about 15 bits of real entropy, meaning every RSA and DSA key of a given size generated on affected Debian- and Ubuntu-based systems came from a set of roughly 32,768 possible values. The flaw shipped in OpenSSL packages for nearly two years before disclosure on May 13, 2008, forcing a mass regeneration of SSH host keys, SSL certificates, and OpenVPN keys across the internet. The Android SecureRandom incident in August 2013 was more directly monetized: because the OS-level PRNG wasn't properly initialized with entropy on startup, Bitcoin wallet apps (including Bitcoin Wallet and early versions of blockchain.info's Android client) generated ECDSA signing values attackers could recover, letting them compute private keys and drain funds from exposed addresses. Both bugs lived at the platform/library layer rather than in application logic, which is part of why they went unnoticed for so long — the code calling the PRNG looked completely normal.

How Do Attackers Actually Exploit Predictable Randomness?

Attackers exploit it by using the predictable output to skip the step they're supposed to be locked out of — guessing a valid session ID, forging a password reset token, or recovering a private key instead of brute-forcing it. If a password reset token is generated from a 32-bit timestamp-seeded PRNG rather than a CSPRNG, the search space collapses from "computationally infeasible" to "a few thousand guesses," since the attacker often knows the approximate request time within a narrow window. Session token prediction works the same way: if session IDs are derived from sequential or weakly randomized values, an attacker who obtains one valid session ID (their own, or one leaked in a log) can enumerate nearby IDs to hijack other users' sessions — this class of attack predates CWE numbering and was already cataloged in early 2000s web app pentesting methodology. For cryptographic keys specifically, the exploit is state recovery: capture enough output bytes, rebuild the generator's internal state, and every key or nonce it will ever produce becomes computable, which is exactly the mechanism behind both incidents in the previous section.

How Can You Detect Insecure Randomness in a Codebase?

You detect it by flagging every call to a non-cryptographic random function that flows into a security-sensitive value, which is a pattern static analysis tools can catch reliably. Grep-level triage looks for Math.random() in Node.js/JS, random.random() or random.randint() in Python, rand() or mt_rand() in PHP, and unseeded java.util.Random in Java, then traces whether the output reaches a token, key, filename, or identifier used for authentication or authorization. SAST tools mapped to CWE-338/CWE-330 (including Semgrep's insecure-randomness rulesets and CodeQL's py/insecure-randomness and js/insecure-randomness queries) catch the syntactic pattern quickly, but they generate false positives on legitimate non-security uses (jitter in retry logic, randomized UI animations, shuffling a game deck) unless the tool can trace data flow to confirm the value actually reaches a sensitive sink. That reachability step — confirming the insecure PRNG's output is actually used in an authentication, token-generation, or key-derivation path, not just called somewhere in the file — is what separates a noisy CWE-338 finding from one worth a developer's time.

How Do You Fix an Insecure Randomness Vulnerability?

You fix it by replacing the general-purpose PRNG with your language's cryptographically secure equivalent, and treating any keys or tokens already generated with the weak function as compromised. The concrete swaps: Node.js crypto.randomBytes() or the newer crypto.randomUUID() instead of Math.random(); Python's secrets.token_bytes() / secrets.token_urlsafe() instead of random; Java's java.security.SecureRandom (properly seeded by the OS) instead of java.util.Random; PHP's random_bytes() or random_int() (both available natively since PHP 7.0, released December 2015) instead of mt_rand(); and Go's crypto/rand instead of math/rand. Beyond the swap, never seed a CSPRNG with predictable inputs like the current timestamp or process ID — that was the exact category of mistake behind CVE-2008-0166 — and if a weak PRNG generated long-lived secrets (API keys, session-signing secrets, encryption keys), those values need to be rotated, not just the code path fixed, since historical output may already be predictable to anyone who captured it.

How Safeguard Helps

Safeguard's static analysis flags insecure-randomness patterns (CWE-330/CWE-338) across JavaScript, Python, Java, PHP, and Go, then runs reachability analysis to confirm the weak PRNG's output actually flows into a token, key, or session-identifier sink before surfacing it — cutting out the noise of flagging every Math.random() call regardless of context. Griffin, Safeguard's AI remediation engine, reviews the flagged data flow and opens an auto-fix pull request that swaps in the correct CSPRNG call for the language and runtime in use, rather than a generic suggestion. SBOM generation and ingest give teams visibility into which third-party libraries embed their own PRNG usage (a common blind spot, since the weak call often lives inside a dependency, not first-party code), and continuous monitoring re-flags any newly introduced insecure randomness the moment it lands in a pull request.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.