Weak Password Storage and Validation
High Risk
Authentication & Authorization
authenticationpasswordscredential-managementpassword-policybcryptpassword-hashing
What it is
Applications that store passwords in plain text or use weak hashing algorithms, combined with insufficient password validation policies, allow attackers to compromise user accounts through password cracking, brute force attacks, or credential stuffing.
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
const users = [];
// VULNERABLE: Weak password validation and storage
app.post('/register', (req, res) => {
const { username, password } = req.body;
// VULNERABLE: Minimal password requirements
if (password.length < 6) {
return res.status(400).json({ error: 'Password too short' });
}
// Allows weak passwords: '123456', 'password', 'qwerty'
// VULNERABLE: Plain text password storage
const user = {
id: users.length + 1,
username,
password: password // Plain text!
};
users.push(user);
res.json({ message: 'User created', userId: user.id });
});
// VULNERABLE: Plain text password comparison
app.post('/login', (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username);
if (!user || user.password !== password) {
return res.status(401).json({ error: 'Invalid credentials' });
}
res.json({ message: 'Login successful', userId: user.id });
});
app.listen(3000);
const express = require('express');
const bcrypt = require('bcryptjs');
const app = express();
app.use(express.json());
const users = [];
const COMMON_PASSWORDS = new Set(['password', '123456', 'qwerty']);
function validatePassword(password, username) {
if (password.length < 12 || password.length > 128) {
return 'Password must be 12-128 characters';
}
const hasLower = /[a-z]/.test(password);
const hasUpper = /[A-Z]/.test(password);
const hasNumber = /\d/.test(password);
const hasSpecial = /[!@#$%^&*]/.test(password);
if ([hasLower, hasUpper, hasNumber, hasSpecial].filter(Boolean).length < 3) {
return 'Password needs 3+ character types';
}
if (COMMON_PASSWORDS.has(password.toLowerCase())) {
return 'Password is too common';
}
if (password.toLowerCase().includes(username.toLowerCase())) {
return 'Password must not contain username';
}
return null;
}
app.post('/register', async (req, res) => {
const { username, password } = req.body;
const error = validatePassword(password, username);
if (error) {
return res.status(400).json({ error });
}
// SECURE: Bcrypt with work factor 12 (auto-generates unique salt)
const passwordHash = await bcrypt.hash(password, 12);
users.push({ id: users.length + 1, username, passwordHash });
res.json({ message: 'User created' });
});
app.post('/login', async (req, res) => {
const { username, password } = req.body;
const user = users.find(u => u.username === username);
// SECURE: Timing-safe bcrypt comparison
if (!user || !await bcrypt.compare(password, user.passwordHash)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
res.json({ message: 'Login successful', userId: user.id });
});
app.listen(3000);
💡 Why This Fix Works
The secure version implements strong password validation requiring 12+ characters with complexity requirements, validates against common passwords, checks for personal information in passwords, and uses bcrypt with a work factor of 12 to securely hash passwords with automatic unique salts.
Why it happens
Typical mistakes in app/data layers enable this vulnerability.
Root causes
Fixes
Detect This Vulnerability in Your Code
Sourcery automatically identifies weak password storage and validation and many other security issues in your codebase.