VNX-GO-008: Go weak PRNG for security
Reports a math/rand or math/rand/v2 call (Read, Intn, Int63, Uint64, IntN...) whose line, the line its value flows into, or the enclosing function name says the value is a token, password, secret, nonce, salt, OTP, PIN, CSRF value, session id, key or reset code. crypto/rand, jitter, backoff, sampling and shuffling outside secret-named functions, and test code are not reported.
| Field | Value |
|---|---|
| Rule ID | VNX-GO-008 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | Go |
| CWE | CWE-338 |
| CAPEC | CAPEC-112 |
| ATT&CK | T1110 |
| CWSS vector | CWSS:1.0/TI:M/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | prng randomness crypto |
| Source | internal/sast/rules/vnx-go-008.rego |
Overview
This rule flags math/rand values used as secrets. While math/rand is appropriate for non-security uses such as shuffling a playlist or randomizing test data, it is a pseudo-random number generator seeded deterministically and its output sequence can be predicted or reconstructed by an attacker. Any security-sensitive value, session tokens, password reset links, CSRF nonces, API keys, or one-time codes, that is generated with math/rand is potentially guessable. This maps to CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG).
Why This Matters
The output of math/rand is fully determined by its seed. In Go 1.20 and earlier, math/rand defaulted to a constant seed of 1 unless explicitly seeded, meaning the same sequence of numbers appeared in every process that forgot to call rand.Seed. Even with a time-based seed, an attacker who can observe a few generated values (or who knows approximately when the process started) can reconstruct the internal state and predict all future outputs. A session token with only 32 bits of effective entropy drawn from math/rand can be brute-forced in seconds. Token guessing attacks (MITRE ATT&CK T1110 – Brute Force) targeting predictable session identifiers or password reset tokens are well-documented and actively exploited.
What Gets Flagged
The rule fires on a call to math/rand or math/rand/v2 (Read, Int, Intn/IntN, Int31/Int63 and their n forms, Uint32/Uint64, N, Float64, Perm, Shuffle), under the package’s import alias or on a generator made with rand.New in the same file, when the line, a line within the next four that the assigned value flows into (n := rand.Int63() then token := strconv.FormatInt(n, 36)), or the enclosing function’s name says the value is a token, password, secret, nonce, salt, OTP, PIN, CSRF value, session id, API or signing key, password-reset or verification code, or credential. OTP and PIN must sit at a word or camelCase boundary, so rootPath and hotPartition are not secrets, and a bare Reset() is state, not a reset code. The import alone is not reported.
// FLAGGED: a one-time code from math/rand/v2
otp := fmt.Sprintf("%06d", mrand.IntN(1000000))
// FLAGGED: inside a function named for a password
func GenerateTemporaryPassword(n int) string {
out := make([]byte, n)
for i := range out {
out[i] = charset[rand.Intn(len(charset))]
}
return string(out)
}
Not reported: crypto/rand calls, math/rand used for timing (jitter, backoff, time.Duration), sampling, load spreading or shuffling in functions not named for a secret, rand.Seed, test code, comments and strings.
Remediation
- Replace
math/randwithcrypto/randfor all security-sensitive random values. Thecrypto/randpackage reads from the operating system’s cryptographically secure entropy source (/dev/urandomon Linux,BCryptGenRandomon Windows).
import (
"crypto/rand"
"encoding/base64"
"fmt"
)
// SAFE: cryptographically secure random token
func generateToken() (string, error) {
b := make([]byte, 32) // 256 bits of entropy
if _, err := rand.Read(b); err != nil {
return "", fmt.Errorf("failed to generate token: %w", err)
}
return base64.URLEncoding.EncodeToString(b), nil
}
- Generate secure random integers using
crypto/randwithbig.Int. For cases where you need a random integer in a range:
import (
"crypto/rand"
"math/big"
)
// SAFE: cryptographically secure random integer in [0, max)
func secureRandInt(max int64) (int64, error) {
n, err := rand.Int(rand.Reader, big.NewInt(max))
if err != nil {
return 0, err
}
return n.Int64(), nil
}
-
Keep
math/randonly for non-security use cases. If your codebase legitimately usesmath/randfor things like test data generation, simulations, or non-security shuffles, ensure those uses are clearly separated from security functions. Add a comment to make the intent explicit and avoid accidental reuse for security purposes. -
Audit all token, key, and nonce generation in your codebase. Search for all call sites that produce values used in authentication, authorization, or cryptographic operations, and confirm each uses
crypto/rand.
References
- CWE-338: Use of Cryptographically Weak Pseudo-Random Number Generator (PRNG)
- OWASP Cryptographic Storage Cheat Sheet
- Go crypto/rand package documentation
- Go math/rand package documentation
- CAPEC-112: Brute Force
- MITRE ATT&CK T1110 – Brute Force
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-go-008
// nosec vnx-go-008
Or record an ignore rule: vulnetix ignore add --rule vnx-go-008 --reason "...". See Suppressing findings.