Weak Password Hashing
Summary
BDServer stores passwords using unsalted SHA-512 hashes. Without a salt, identical passwords produce identical hashes, enabling rainbow table attacks. Without a work factor, SHA-512's speed makes brute-force attacks practical on modern hardware.
Vulnerability Details
| Property | Value |
|---|---|
| Type | Insecure Password Storage |
| CVSS | 7.5 (High) |
| CWE | CWE-916 (Use of Password Hash With Insufficient Computational Effort), CWE-759 (Use of a One-Way Hash without a Salt) |
Root Cause
Password hashing appears in three locations, all using the same pattern:
hashed = hashlib.sha512(password.encode("utf-8")).hexdigest()
hashed = hashlib.sha512(password.encode("utf-8")).hexdigest()
old_hashed = hashlib.sha512(old_password.encode("utf-8")).hexdigest()
new_hashed = hashlib.sha512(new_password.encode("utf-8")).hexdigest()
This implementation has two fundamental problems:
No Salt
A salt is random data added to each password before hashing. Without it:
-
Identical passwords produce identical hashes. If two users choose "password123", their stored hashes are identical. An attacker who cracks one automatically cracks the other.
-
Rainbow tables work. Precomputed tables mapping common passwords to SHA-512 hashes allow instant lookups. A salt forces attackers to compute hashes per-user, making precomputation infeasible.
-
Credential stuffing is easier. Attackers can compare leaked hashes against databases from other breaches to identify password reuse.
No Work Factor
SHA-512 is designed for speed, not password storage. Modern GPUs can compute billions of SHA-512 hashes per second, making brute-force attacks practical.
Password hashing algorithms like Argon2 intentionally slow down hashing through configurable iteration counts and memory requirements. A work factor that takes 100ms per hash has negligible impact on legitimate logins but drastically slows brute-force attacks.
Remediation
Replace SHA-512 with Argon2, the winner of the 2015 Password Hashing Competition:
from argon2 import PasswordHasher
ph = PasswordHasher()
def hash_password(password: str) -> str:
return ph.hash(password)
def verify_password(password: str, hashed: str) -> bool:
try:
ph.verify(hashed, password)
return True
except:
return False
Migration Strategy
Existing users have SHA-512 hashes that cannot be converted without knowing the plaintext password. To migrate incrementally:
- Update the code to hash new passwords with Argon2
- On login, check if the stored hash is SHA-512 (128 hex characters) or Argon2 (starts with
$argon2) - If SHA-512 and login succeeds, rehash the password with Argon2 and update storage
- Over time, active users migrate automatically
Disclaimer
This assessment was performed on a best-effort basis against BDServer commit dc86854 and reflects the state of the software at the time of testing. The findings and remediations are provided for informational purposes and should be independently validated before implementation. This report does not guarantee all vulnerabilities have been identified, nor does it guarantee the suggested fixes will be effective in all environments.