Skip to content

VNX-327: Broken hash algorithm (MD5, SHA-1) protecting a password, token or signature

MD5 or SHA-1 is computed through a hash API on a security value: the call line, the three code lines above it or the next one (the next three for a Java/Kotlin MessageDigest, which is fed afterwards), never looking past a line that closes a block name a password, secret, token, session id, cookie, signature, salt, nonce, CSRF value, API key, credential or reset value. Both algorithms are broken for collision resistance and are fast to brute-force, so they must not hash passwords or derive tokens and signatures; use SHA-256 or stronger, HMAC-SHA256 for tokens and signatures, and argon2id/bcrypt/scrypt for passwords. Calls recognised: Python hashlib.md5()/sha1()/new('md5'|'sha1') and bare md5()/sha1() after from hashlib import, Node.js createHash('md5'|'sha1') and CryptoJS.MD5/SHA1, Java/Kotlin MessageDigest.getInstance("MD5"|"SHA-1"), commons-codec/Spring DigestUtils.md5Hex/sha1Hex/md5DigestAsHex and Guava Hashing.md5()/sha1(), PHP md5()/sha1()/md5_file()/sha1_file(), Ruby Digest::MD5/SHA1, OpenSSL::Digest::MD5/SHA1 and OpenSSL::Digest.new('MD5'|'SHA1'). Not reported: digests with no security name nearby (cache keys, ETags, checksums, content addresses), digests of a constant (static::class, __CLASS__, a string literal), Python calls marked usedforsecurity=False, HMAC-SHA1, names, fields, enums and comments that only mention the algorithms, and test files. A password passed straight into the digest (md5($pass), hashlib.md5(password.encode()), createHash('md5').update(password), DigestUtils.md5Hex(password), Digest::MD5.hexdigest(password)) is reported by VNX-CRYPTO-007, a password hashed with a fast hash, and not here; this rule keeps tokens, session ids, cookies, signatures, salts, nonces and keys, and a password named only around the call. Other MD5 and SHA-1 calls are reported by VNX-CRYPTO-001 and VNX-CRYPTO-002; Go is covered there.

Updated 02 Nov 2026Checked against cli@de9b52a
Field Value
Rule ID VNX-327
Severity High
SARIF level error
Kind sast
Languages Java, Kotlin, JavaScript / TypeScript, PHP, Python, Ruby
CWE CWE-327
CAPEC CAPEC-66
ATT&CK T1190
CWSS vector CWSS:1.0/TI:H/AP:A/AL:A/IC:N/FC:LT/RP:N/RL:A/AV:I/AS:N/IN:A/SC:A/BI:H/DI:H/EX:H/EC:N/P:H
Tags crypto weak-algorithm cwe-327
Source internal/sast/rules/vnx-327.rego

Overview

VNX-327 maps to CWE-327: Use of a Broken or Risky Cryptographic Algorithm. It reports a call into MD5 or SHA-1 when the value being hashed or produced is a security value: the call line, the three code lines above it or the next line (the next three for a Java/Kotlin MessageDigest, which is fed afterwards) name a password, secret, token, session id, cookie, signature, salt, nonce, CSRF value, API key, credential or reset value.

A password passed straight into the digest (md5($pass), hashlib.md5(password.encode()), createHash('md5').update(password), DigestUtils.md5Hex(password), Digest::MD5.hexdigest(password)) is reported by VNX-CRYPTO-007, a password hashed with a fast hash, and not here, so the line is reported once with the password-hashing fix.

Why This Matters

Both algorithms are broken for collision resistance and are fast enough to brute-force billions of guesses a second, so a password hashed with them is recovered from a leaked database, and a token or signature derived from them can be forged or predicted.

What Gets Flagged

# FLAGGED
return hashlib.md5(f"{email}:{time.time()}".encode()).hexdigest()   # in make_reset_token
// FLAGGED
const signature = CryptoJS.SHA1(secret + body).toString(CryptoJS.enc.Hex);
// FLAGGED: the digest is fed a password below
MessageDigest md = MessageDigest.getInstance("MD5");
// FLAGGED
$_SESSION['session_token'] = md5(uniqid());
# FLAGGED
OpenSSL::Digest::SHA1.hexdigest("#{ACCESS_TOKEN_SALT}:#{id}")

What Is Not Flagged

  • Digests with no security name nearby: cache keys, ETags, checksums, content addresses.
  • Digests of a constant (sha1(static::class), __CLASS__, a string literal).
  • Python calls marked usedforsecurity=False, and HMAC-SHA1.
  • Fields, enums and comments that only mention the algorithms, and test files.
  • A password hashed directly ($pass = md5($pass), Digest::MD5.hexdigest(password)): reported by VNX-CRYPTO-007.

Other MD5 and SHA-1 calls, including Go, are reported by VNX-CRYPTO-001 and VNX-CRYPTO-002.

Remediation

  1. Hash passwords with argon2id, bcrypt or scrypt.
  2. Derive tokens from a CSPRNG (secrets.token_urlsafe(32)), and sign with HMAC-SHA256.
  3. Use SHA-256 or stronger wherever a digest protects integrity.

References

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-327
// nosec vnx-327

Or record an ignore rule: vulnetix ignore add --rule vnx-327 --reason "...". See Suppressing findings.

Edit this pageSources: cli/internal/sast/rules/vnx-327.rego, cli/internal/sast/docs/vnx-327.md
esc
↑ ↓ move↵ openSearches in your browser. A key like autonomy.max or a code like threshold_required jumps straight to it.