The Login Key Made of Passwords — JavaScript Bug Hunt
Modelled on Ashley Madison (2015): after attackers published the site's user database, its passwords turned out to be hashed with bcrypt — slow to crack.
- Language: JavaScript
- Layer: Database
- Difficulty: Medium
- Concepts: Security, Auth
- Modelled on: Ashley Madison · 2015
- Visible tests: the password verifies; the login key is random, not derived from the password
- Reward: 50 XP for a complete fix
Briefing
Modelled on Ashley Madison (2015): after attackers published the site's user database, its passwords turned out to be hashed with bcrypt — slow to crack. But the password-cracking group CynoSure Prime found a second stored value, $loginkey, that for older accounts was an MD5 hash of the lowercased username and lowercased password. Cracking those fast hashes recovered about 11 million passwords, and the bcrypt protection counted for nothing.
register.js is a reconstruction. The password hash itself is fine (hashing.passwordHash is a salted, slow hash); the auto-login key stored beside it is derived from the password.
Fix register so nothing stored with the account is derived from the password except the slow hash.
Bug report
BUG-LOGINKEY · Priority: Critical · Reported by: security review
register(username, password, rng) returns { username, salt, passwordHash, loginKey } (in that key order) where rng(n) returns n random bytes (integers 0–255):
- salt = hashing.hex(rng(16)) (first call)
- passwordHash = hashing.passwordHash(password, salt)
- loginKey = hashing.hex(rng(16)) (second call) — random, and independent of the username and the password
verify(record, password) -> true exactly when the password matches.
Observed: loginKey is hashing.fastDigest(lower(username) + "::" + lower(password)) — an unsalted fast hash of the password stored next to the bcrypt one.
Logs
[audit] users.loginkey: 32-hex values, identical for accounts with the same username+password
[audit] loginkey recomputable from fastDigest(lower(user)::lower(pass))The code as shipped
src/accounts/register.js (editable)
var hashing = require("./hashing");
// rng(n) -> n random bytes (0-255) from the CSPRNG.
exports.register = function (username, password, rng) {
var salt = hashing.hex(rng(16));
return {
username: username,
salt: salt,
passwordHash: hashing.passwordHash(password, salt),
loginKey: hashing.fastDigest(username.toLowerCase() + "::" + password.toLowerCase())
};
};
exports.verify = function (record, password) {
return hashing.passwordHash(password, record.salt) === record.passwordHash;
};
Read-only context: src/accounts/hashing.js.
Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.