Overview
Insecure password storage occurs when applications store passwords in plaintext or use weak hashing algorithms without proper salting. In this demo, the vulnerable version stores passwords directly in the database without any hashing, making all user credentials immediately accessible if the database is compromised.Severity Rating
CRITICAL - CVSS Score: 9.8/10CWE Reference: CWE-259: Use of Hard-coded Password, CWE-916: Use of Password Hash With Insufficient Computational Effort
Vulnerable Code
The vulnerable implementation stores passwords in plaintext:Why This Is Dangerous
- No Hashing: Passwords stored as-is in the database
- Database Compromise = Complete Breach: Anyone with database access sees all passwords immediately
- SQL Injection Exposure: Password comparison in SQL query (see
vulnerable/app.py:26) - Password Reuse Risk: Users often reuse passwords across sites
- No Salt: Even if basic hashing was added, without salt it’s vulnerable to rainbow table attacks
- Compliance Violations: Violates GDPR, PCI-DSS, HIPAA, and other regulations
Attack Scenarios
Scenario 1: Database Backup Theft
Scenario 1: Database Backup Theft
Attack Vector: Attacker obtains database backup through:No cracking needed - passwords are plaintext!
- Misconfigured cloud storage
- Compromised backup server
- Insider threat
- SQL injection (data exfiltration)
Scenario 2: SQL Injection to Password Dump
Scenario 2: SQL Injection to Password Dump
Attack: Combine with SQL injection vulnerabilityResult: Complete user table with plaintext passwords dumped to screen
Scenario 3: Credential Stuffing on Other Sites
Scenario 3: Credential Stuffing on Other Sites
Attack Flow:
- Attacker obtains plaintext passwords from this site
- Tests same username/password combinations on:
- Gmail
- Banking sites
- Social media
- Corporate email
- Many users reuse passwords - accounts compromised
Scenario 4: Insider Threat
Scenario 4: Insider Threat
Risk: Any employee with database access can:
- View all user passwords
- Log into any account
- Steal credentials for external use
- No audit trail of password access
Database Evidence
Let’s see what the vulnerable database actually contains:| id | username | password | role | created_at | |
|---|---|---|---|---|---|
| 1 | admin | admin123 | [email protected] | admin | 2024-03-10 |
| 2 | usuario | password123 | [email protected] | user | 2024-03-10 |
Secure Implementation
The secure version uses bcrypt password hashing (secure/database.py and secure/app.py):
How Bcrypt Works
Salt Generation
Bcrypt generates a random salt (unique per password):The salt ensures identical passwords produce different hashes.
Hashing with Work Factor
Bcrypt applies the hashing algorithm multiple times (2^12 = 4096 rounds by default):This makes brute-force attacks computationally expensive.
Stored Hash
Final hash stored in database:Format:
$algorithm$cost$salt$hash$2b$: Bcrypt algorithm identifier$12$: Work factor (2^12 iterations)- Next 22 chars: Salt
- Remaining: Actual password hash
Secure Database Evidence
Here’s what the secure database contains:| id | username | password | role | |
|---|---|---|---|---|
| 1 | admin | 12$kR7L8… | [email protected] | admin |
| 2 | usuario | 12$9mF3X… | [email protected] | user |
Even if the database is compromised, passwords are protected by bcrypt hashing. Cracking would require billions of attempts per password.
Password Hashing Best Practices
1. Use Modern Hashing Algorithms
1. Use Modern Hashing Algorithms
Recommended:
- Bcrypt (used in this demo) - Work factor adjustable
- Argon2 - Winner of Password Hashing Competition 2015
- scrypt - Memory-hard function
- PBKDF2 - NIST approved, but slower per iteration
- MD5 - Broken, too fast
- SHA1 - Deprecated
- SHA256/512 without many iterations - Too fast for passwords
- Custom algorithms - Likely to be flawed
2. Use Sufficient Work Factor
2. Use Sufficient Work Factor
Configure algorithm difficulty to resist brute-force:Rule of Thumb: Hashing should take 250ms-500ms on your server
3. Always Use Random Salts
3. Always Use Random Salts
Salts prevent rainbow table attacks:Modern algorithms like bcrypt include random salt automatically.
4. Implement Password Policies
4. Implement Password Policies
Enforce strong passwords:
5. Never Log or Transmit Plaintext Passwords
5. Never Log or Transmit Plaintext Passwords
6. Implement Password Rotation
6. Implement Password Rotation
Force periodic password changes:
Cracking Comparison
Time to crack an 8-character password with different storage methods:| Storage Method | Time to Crack | Security Level |
|---|---|---|
| Plaintext | Instant | None |
| MD5 | 1 minute | Very Weak |
| SHA256 (no salt) | 5 minutes | Weak |
| SHA256 + salt | 30 minutes | Weak |
| PBKDF2 (10k iterations) | 3 days | Moderate |
| Bcrypt (work=12) | 2 years | Strong |
| Bcrypt (work=14) | 8 years | Very Strong |
| Argon2 | 10+ years | Excellent |
Times based on modern GPU (RTX 4090). Actual times vary by password complexity.
Migration Strategy
If you have existing plaintext passwords:Compliance Requirements
GDPR
Article 32: Appropriate technical measuresRequirement: Pseudonymization and encryption of personal dataPenalty: Up to €20 million or 4% of global revenue
PCI-DSS
Requirement 8.2.1: Render passwords unreadable using strong cryptographyStandard: Minimum one-way hash with saltPenalty: Fines up to $500,000 per incident
HIPAA
164.312(a)(2)(iv): Encryption and decryptionRequirement: Strong password protection for PHI accessPenalty: Up to $1.5 million per violation category per year
SOC 2
CC6.1: Logical access controlsRequirement: Strong password hashing required for complianceImpact: Failed audits, lost business
References
- OWASP Password Storage Cheat Sheet
- CWE-916: Use of Password Hash With Insufficient Computational Effort
- NIST SP 800-63B: Digital Identity Guidelines
- Argon2 Specification
Next Steps
Related security topics:
- SQL Injection - Another critical vulnerability in login
- Session Management - Protecting authenticated sessions
- Sensitive Data Exposure - Additional data protection