VNX-303: Incorrect Implementation of Authentication Algorithm
Detects password checks that cannot give the right answer: bcrypt.compare()/argon2.verify() used without await (a Promise is always truthy, so every password passes), and a password re-hashed with a fresh salt and compared with the stored hash (never equal).
| Field | Value |
|---|---|
| Rule ID | VNX-303 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | Java, JavaScript / TypeScript, Python |
| CWE | CWE-303 |
| CAPEC | CAPEC-66 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | auth password-verification bcrypt cwe-303 |
| Source | internal/sast/rules/vnx-303.rego |
Overview
VNX-303 maps to CWE-303: Incorrect Implementation of Authentication Algorithm. The right primitive is in use, but it is called in a way that defeats it.
- An asynchronous verifier used as a boolean (JavaScript/TypeScript).
bcrypt.compare(),bcryptjs.compare()andargon2.verify()return a Promise. Withoutawait,.then()or a callback,if (bcrypt.compare(pw, hash))is always true and every password is accepted;if (!argon2.verify(...))never rejects. Reported inside anif,&&,||,!or ternary, or when assigned to a name that a condition within five lines tests. - Re-hashing with a fresh salt, then comparing (Python, Java, JavaScript/TypeScript).
bcrypt.hashpw(pw, bcrypt.gensalt()) == stored,generate_password_hash(pw) == stored, a passlib/argon2 hasher’s.hash(pw) ==,passwordEncoder.encode(pw).equals(stored),BCrypt.hashpw(pw, BCrypt.gensalt()).equals(...),bcrypt.hash(pw, 10) === stored, inline or through a name assigned from the hash. A salted hash never repeats, so the check always fails, and is typically “fixed” by weakening it.
What Gets Flagged
if (user && bcrypt.compare(req.body.password, user.passwordHash)) { // FLAGGED: always truthy
req.session.userId = user.id;
}
if bcrypt.hashpw(password.encode(), bcrypt.gensalt()) == user.password_hash: # FLAGGED
return user
if (passwordEncoder.encode(rawPassword).equals(user.getPassword())) { // FLAGGED
What Is Not Flagged
await bcrypt.compare(...), the callback and.then()forms, and a returned Promise.compareSync,bcrypt.checkpw,check_password_hash,passwordEncoder.matches.- Re-hashing with the stored hash as the salt (
bcrypt.hashpw(pw, stored) == stored) and hashing at registration. - Comments and string literals.
Remediation
Use the library’s verifier and await it:
if (user && await bcrypt.compare(req.body.password, user.passwordHash)) { ... }
if (passwordEncoder.matches(rawPassword, user.getPassword())) { ... }
if bcrypt.checkpw(password.encode(), user.password_hash): ...
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-303
// nosec vnx-303
Or record an ignore rule: vulnetix ignore add --rule vnx-303 --reason "...". See Suppressing findings.