From 26662db315dd5775f7beaad6ab04e9ca4c97f372 Mon Sep 17 00:00:00 2001 From: Bryce Kwon Date: Fri, 16 Oct 2026 22:11:55 -1000 Subject: Spend a missing account's login on a real hash The fixed SHA-512 stand-in matched only the documented setup, so a site hashing with yescrypt, other rounds or bcrypt answered for missing and real names in different times, and on OpenBSD it failed at once. --- custom/extensions/auth-inline.lua | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) (limited to 'custom/extensions/auth-inline.lua') diff --git a/custom/extensions/auth-inline.lua b/custom/extensions/auth-inline.lua index 53b1efc..3355b5c 100644 --- a/custom/extensions/auth-inline.lua +++ b/custom/extensions/auth-inline.lua @@ -90,11 +90,6 @@ local cookie_insecure = false -- the password's length, and one longer than this is turned down unchecked. local max_password_bytes = 256 --- A throwaway hash of the documented shape, used only to spend the same work --- on a missing account as on a present one, so a failed login does not reveal --- by timing whether the user name exists. -local dummy_hash = "$6$rounds=300000$0000000000000000$" - -- One request reaches this script as an open, a write and a close, so what the -- open decodes is kept here for the calls that follow. local action, http, cgit, post @@ -120,11 +115,19 @@ do end end +-- A name with no account comes back as nil and another account's hash, so +-- a failed login can spend the same work on a hash of the same kind and +-- cost, and its timing does not tell whether the name exists. function account_hash(user) if user == nil then return nil end - return users[user:lower()] + local hash = users[user:lower()] + if hash ~= nil then + return hash + end + local _, stand_in = next(users) + return nil, stand_in end -- The users allowed into a repository come back keyed by lowercased name, and @@ -416,10 +419,12 @@ function authenticate_post() local password = post["password"] local ok = false if username ~= nil and password ~= nil and #password <= max_password_bytes then - local hash = account_hash(username) + local hash, stand_in = account_hash(username) if hash == nil then - -- Spend the work anyway, see dummy_hash. - unistd.crypt(password, dummy_hash) + -- Spend the work anyway, see account_hash. + if stand_in ~= nil then + unistd.crypt(password, stand_in) + end elseif constant_equals(hash, unistd.crypt(password, hash)) then ok = true end -- cgit