Introduction
The backend/controllers/authController.js file handles all user authentication logic including registration and login, but a subtle configuration flaw in the password hashing setup created a significant security risk. On line 10, the saltRounds constant was set to 10—a value that was once considered adequate but now falls short against modern GPU-accelerated password cracking techniques.
This matters because if an attacker ever gains access to your MongoDB database through a separate vulnerability (SQL injection, misconfigured access controls, or a data breach), the strength of your password hashes becomes your last line of defense. With salt rounds of 10, that defense was weaker than it should be.
The Vulnerability Explained
bcrypt is a password hashing function designed to be computationally expensive, making brute-force attacks impractical. The "salt rounds" parameter (also called the work factor or cost factor) determines how many iterations the algorithm performs. Each increment doubles the computational work required.
Here's the vulnerable configuration that was in production:
const saltRounds = 10;
At first glance, this seems reasonable—10 salt rounds was the standard recommendation for years. However, the security landscape has changed dramatically:
Why 10 Salt Rounds Is No Longer Sufficient
The computational cost of bcrypt with 10 rounds can be expressed as 2^10 = 1,024 iterations. Modern GPUs can test millions of password candidates per second at this work factor. According to security research, a single high-end GPU can crack bcrypt hashes at approximately:
- 10 rounds: ~5,000 hashes/second
- 12 rounds: ~1,250 hashes/second
This means an attacker with access to your password hashes could crack weak passwords (common words, short passwords, predictable patterns) in hours rather than days.
Attack Scenario Specific to This Application
Consider this exploitation path for the affected application:
- Database Access: An attacker exploits a MongoDB injection vulnerability or gains access through a misconfigured cloud database instance
- Hash Extraction: They dump the users collection containing bcrypt password hashes
- Offline Cracking: Using a GPU cluster, they run dictionary attacks against the hashes
- Account Takeover: With salt rounds of 10, common passwords like "Summer2024!" or "Company123" crack within hours
- Persistent Access: Given the
TOKEN_EXPIRY = "7d"setting visible in the code, compromised accounts provide week-long access windows
The combination of the 7-day JWT expiration and weak password hashing created a compounding risk—attackers had both easier password cracking AND longer exploitation windows.
The Fix
The fix was elegantly simple—a single character change with significant security implications:
Before
const saltRounds = 10;
After
const saltRounds = 12;
This change in backend/controllers/authController.js at line 10 increases the computational cost by a factor of 4 (2^12 / 2^10 = 4). Here's what this means in practice:
| Metric | Salt Rounds 10 | Salt Rounds 12 | Improvement |
|---|---|---|---|
| Iterations | 1,024 | 4,096 | 4x more work |
| GPU crack rate | ~5,000/sec | ~1,250/sec | 4x slower |
| Time to crack 1M hashes | ~3.3 minutes | ~13.3 minutes | 4x longer |
Why This Specific Change Works
The fix preserves complete backward compatibility—existing password hashes remain valid and users don't need to reset their passwords. When users next log in and their credentials are verified, new password changes will use the stronger work factor. Over time, as users update their passwords, the entire database migrates to the stronger configuration.
The change is also forward-looking. OWASP currently recommends a minimum of 10 rounds but suggests 12+ for sensitive applications. By choosing 12, this fix aligns with current best practices while maintaining acceptable login performance (bcrypt with 12 rounds typically completes in 200-400ms on modern server hardware).
Prevention & Best Practices
1. Establish Minimum Work Factor Standards
Create a security policy that mandates minimum bcrypt salt rounds:
// security-config.js
const SECURITY_CONSTANTS = {
BCRYPT_MIN_ROUNDS: 12,
BCRYPT_RECOMMENDED_ROUNDS: 13, // For high-security applications
};
// In your auth controller
const saltRounds = Math.max(
SECURITY_CONSTANTS.BCRYPT_MIN_ROUNDS,
parseInt(process.env.BCRYPT_ROUNDS) || 12
);
2. Implement Gradual Hash Upgrades
Add logic to upgrade password hashes on successful login:
async function loginUser(email, password) {
const user = await User.findOne({ email });
const isValid = await bcrypt.compare(password, user.passwordHash);
if (isValid) {
// Check if hash needs upgrading
const hashRounds = bcrypt.getRounds(user.passwordHash);
if (hashRounds < MINIMUM_ROUNDS) {
const newHash = await bcrypt.hash(password, CURRENT_ROUNDS);
await User.updateOne({ _id: user._id }, { passwordHash: newHash });
}
}
return isValid;
}
3. Annual Security Reviews
Password hashing requirements evolve with hardware capabilities. Schedule annual reviews of your authentication configuration. What's secure today may be insufficient in 2-3 years.
4. Defense in Depth
Don't rely solely on password hashing strength:
- Implement rate limiting on login endpoints
- Use account lockout after failed attempts
- Enable multi-factor authentication
- Monitor for credential stuffing attacks
- Consider pepper values for additional hash security
Key Takeaways
- The
saltRounds = 10configuration inauthController.jswas a silent vulnerability—the code worked correctly but provided insufficient protection against modern attacks - Each bcrypt salt round increment doubles computational cost—going from 10 to 12 rounds makes password cracking 4x harder
- The 7-day JWT expiration (
TOKEN_EXPIRY = "7d") amplified the risk—compromised passwords provided extended access windows - Existing password hashes remain valid after the fix—no user disruption, gradual security improvement as passwords are updated
- Static analysis can catch this pattern—tools can flag bcrypt configurations below threshold values
How Orbis AppSec Detected This
- Source: User password input during registration/login flows in
authController.js - Sink:
bcrypt.hash()call usingsaltRoundsconstant set to 10 - Missing control: Insufficient work factor configuration—salt rounds below the recommended minimum of 12
- CWE: CWE-916 (Use of Password Hash With Insufficient Computational Effort)
- Fix: Increased
saltRoundsfrom 10 to 12 inbackend/controllers/authController.js:10
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
This vulnerability demonstrates how security requirements evolve over time. Code that was secure when written can become vulnerable as attack capabilities improve. The bcrypt salt rounds configuration of 10 was once the standard recommendation, but GPU advances have made it insufficient for protecting against determined attackers.
The fix—changing a single number from 10 to 12—quadruples the difficulty of password cracking while maintaining full backward compatibility. It's a reminder that security isn't just about avoiding obvious mistakes; it's about staying current with evolving best practices.
Review your own authentication code today. If you're using bcrypt with fewer than 12 salt rounds, you have a similar vulnerability waiting to be exploited.