This vulnerability involves storing user passwords in a format that allows the original password to be retrieved by the application or an attacker who gains access to the storage. This includes storing passwords in plaintext (see CWE-312) or using reversible encryption (like AES, DES, two-way encryption) instead of a strong, salted, one-way hash function (like bcrypt, Argon2, PBKDF2, scrypt). 🔑🔄
Irreversible Reputational Damage: A breach involving plaintext or easily decrypted passwords causes a massive loss of user trust.
Passwords should never need to be decrypted by the application. Authentication works by hashing the user’s login attempt and comparing it to the stored hash.
Reference Details
CWE ID:CWE-257OWASP Top 10 (2021): A04:2021 - Insecure Design (Failure to use secure password storage) & A02:2021 - Cryptographic Failures
Severity: Critical
This is a critical design flaw. The only secure way to store passwords is using a strong, adaptive, salted, one-way hash function. Frameworks provide tools for this (often by default), but developers might mistakenly choose encryption due to misunderstanding or implementing features like “email my password” (which is inherently insecure).Remediation:
Use Hashing ONLY: Replace any encryption logic for passwords with a standard password hashing library (bcrypt, Argon2, PBKDF2, scrypt).
Migrate Existing Passwords: If passwords are currently stored recoverably, implement a migration strategy:
Add a new database column for the secure hash.
When a user next logs in successfully (using the old, recoverable password), hash the provided password using the new secure method and store it in the new column.
Clear the old recoverable password.
Eventually, remove the column containing the recoverable passwords.
Eliminate “Recover Password” Features: Replace “email my password” features with secure password reset links that use temporary, single-use, unpredictable tokens.
Use Django’s built-in User model or AbstractUser and rely on user.set_password() and user.check_password(), which use configured secure hashers (bcrypt, Argon2, PBKDF2).
Inspect the database schema and data. Look for columns explicitly storing passwords (password, pwd, secret, etc.). Determine if the stored data is a hash (long, random-looking string, often with prefixes like bcrypt$, argon2$) or encrypted/plaintext (might be Base64, hex, or directly readable). Review code responsible for setting and checking passwords; ensure it uses hashing functions (set_password, check_password, hashers.make_password, hashers.check_password), not encryption/decryption.
Use Spring Security’s PasswordEncoder interface with a strong implementation (BCryptPasswordEncoder, Argon2PasswordEncoder). Store the resulting hash. Never encrypt passwords.
// service/UserService.java (Secure Hashing)import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.stereotype.Service;// Assume UserRepository and User model (with passwordHash field) exist@Servicepublic class UserService { @Autowired private PasswordEncoder passwordEncoder; @Autowired private UserRepository userRepository; public User registerUserHash(String username, String rawPassword) { User user = new User(); user.setUsername(username); // SECURE: Hash password before saving using configured encoder. user.setPasswordHash(passwordEncoder.encode(rawPassword)); return userRepository.save(user); } public boolean checkPasswordHash(String username, String rawPasswordToCheck) { User user = userRepository.findByUsername(username); if (user == null) return false; // SECURE: Use encoder's matches() method for comparison. return passwordEncoder.matches(rawPasswordToCheck, user.getPasswordHash()); }}
Inspect the database column storing passwords. It should contain long, salted hash strings (e.g., starting $2a$, $argon2id$). Review the registration and login code. Ensure PasswordEncoder.encode() is used for saving and PasswordEncoder.matches() is used for verifying. Verify no encryption/decryption methods are called for password handling.
Use ASP.NET Core Identity’s UserManager<TUser> and SignInManager<TUser>. UserManager.CreateAsync(user, password) automatically hashes the password using the configured IPasswordHasher. SignInManager.CheckPasswordSignInAsync handles the hash comparison securely.
// Services/AccountService.cs (Secure - using Identity UserManager)using Microsoft.AspNetCore.Identity; // Added namespaceusing System.Threading.Tasks; // Added namespacepublic class AccountService{ private readonly UserManager<IdentityUser> _userManager; // Use your actual user type private readonly SignInManager<IdentityUser> _signInManager; public AccountService(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager) { _userManager = userManager; _signInManager = signInManager; } public async Task<IdentityResult> RegisterUserHash(string email, string password) { var user = new IdentityUser { UserName = email, Email = email }; // SECURE: CreateAsync hashes the password automatically. // Stores hash in the PasswordHash property of IdentityUser. var result = await _userManager.CreateAsync(user, password); return result; } public async Task<SignInResult> CheckPasswordHash(string email, string passwordToCheck) { // SECURE: CheckPasswordSignInAsync compares the provided password // against the stored hash securely. // The last arg `lockoutOnFailure` enables lockout policies. var result = await _signInManager.CheckPasswordSignInAsync( await _userManager.FindByEmailAsync(email), // Find user first passwordToCheck, lockoutOnFailure: true ); return result; }}
Inspect the AspNetUsers table (or your user table). Verify the PasswordHash column contains hashes, not plaintext or encrypted blobs. Ensure any Password or PasswordCleartext columns are null or non-existent. Review registration and login code to confirm usage of UserManager.CreateAsync and SignInManager.CheckPasswordSignInAsync (or UserManager.CheckPasswordAsync).
// database/migrations/..._create_users_table.php// DANGEROUS: Has a 'password_cleartext' column// ... Assume User::create saves directly to this column ...
Inspect the users table. The password column should contain bcrypt ($2y$) or Argon2 ($argon2id$) hashes. Review registration/login code for usage of Hash::make and Hash::check (or Auth::attempt). Ensure Crypt::encryptString or openssl_encrypt are not used for passwords.
Inspect user documents/rows in the database. Ensure password fields contain bcrypt hashes ($2b$..). Review registration and login code to verify bcrypt.hash and bcrypt.compare are used. Check that no encryption/decryption functions (crypto.createCipheriv, etc.) are used for password storage or comparison.
# app/models/user.rb (Secure Hashing)class User < ApplicationRecord # SECURE: Includes bcrypt hashing, salting, and comparison via authenticate method. # Requires a 'password_digest' column in the users table. # Requires `gem 'bcrypt'` in Gemfile. has_secure_password # Now you can use: # user = User.new(password: 'secret', password_confirmation: 'secret') # user.save # user.authenticate('secret') # => user or falseend
Inspect the users table. Ensure there is a password_digest column containing bcrypt hashes ($2a$..) and no columns storing plaintext or encrypted passwords. Review the User model for has_secure_password. Check authentication logic uses the user.authenticate(password) method.