Root, No Password, Second Try — JavaScript Bug Hunt
Modelled on the macOS High Sierra root login bug (CVE-2017-13872, November 2017): typing root with an empty password into an authentication dialog failed…
- Language: JavaScript
- Layer: Backend
- Difficulty: Hard
- Concepts: Security, Auth
- Modelled on: macOS · CVE-2017-13872
- Visible tests: a normal account checks its password; root with an empty password never gets in
- Reward: 50 XP for a complete fix
Briefing
Modelled on the macOS High Sierra root login bug (CVE-2017-13872, November 2017): typing root with an empty password into an authentication dialog failed the first time and succeeded the second. The root account ships disabled, with no password at all; the credential check, finding no stored password hash, took the path meant for upgrading legacy passwords and saved the one just typed. Apple described it as a logic error in the validation of credentials and shipped a security update within a day.
auth.js reconstructs that check. hashing.js holds the (toy) current hash and legacy formats.
Fix authenticate so a disabled account never authenticates and a login attempt never sets a password.
Bug report
BUG-ROOTLOGIN · Priority: Critical (privilege escalation) · Reported by: a developer on Twitter
directory: { <username>: { passwordHash: string|null, legacyCrypt: string|null } }
authenticate(directory, username, password):
- unknown user -> false
- an empty password never authenticates, for any account
- passwordHash set -> hashing.verify(password, passwordHash)
- only legacyCrypt set (an account from before the hash upgrade): if hashing.verifyLegacy(password, legacyCrypt) the account is upgraded (passwordHash = hashing.hash(password), legacyCrypt = null) and the login succeeds; otherwise false and nothing changes
- neither set: the account is disabled -> false, and the record is never modified
Observed: root/"" fails once, then succeeds — and root now has a password.
Logs
[authd] authenticate root: no shadow hash, upgrading credential
[authd] authenticate root: FAILED
[authd] authenticate root: OKThe code as shipped
src/auth/auth.js (editable)
var hashing = require("./hashing");
exports.authenticate = function (directory, username, password) {
var account = directory[username];
if (!account) return false;
if (!account.passwordHash) {
var upgrading = !!account.legacyCrypt;
if (upgrading && !hashing.verifyLegacy(password, account.legacyCrypt)) return false;
account.passwordHash = hashing.hash(password);
account.legacyCrypt = null;
return upgrading;
}
return hashing.verify(password, account.passwordHash);
};
Read-only context: src/auth/ACCOUNTS.js, src/auth/hashing.js.
Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.