diff options
Diffstat (limited to 'extensions/auth-file.lua')
| -rw-r--r-- | extensions/auth-file.lua | 36 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
1 file changed, 31 insertions, 5 deletions
diff --git a/extensions/auth-file.lua b/extensions/auth-file.lua index 1f435c6..71c8bc6 100644 --- a/extensions/auth-file.lua +++ b/extensions/auth-file.lua @@ -322,8 +322,8 @@ function validate_value(expected_field, cookie) return nil end - -- Lua hashes strings, so these comparisons are time invariant. - if chmac ~= tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt)) then + -- Compare the HMAC without short-circuiting on the first mismatch. + if not constant_equals(chmac, tohex(hmac.new(get_secret(), "sha256"):final(field .. "|" .. value .. "|" .. tostring(expiration) .. "|" .. salt))) then return nil end @@ -335,7 +335,13 @@ function validate_value(expected_field, cookie) return nil end - return url_decode(value) + local decoded = url_decode(value) + -- Reject values carrying control characters so a signed cookie or + -- redirect target cannot smuggle CR or LF into a response header. + if decoded:find("%c") then + return nil + end + return decoded end function secure_value(field, value, expiration) @@ -352,8 +358,28 @@ function secure_value(field, value, expiration) return authstr end +-- Strip control characters that could split an HTTP response header. +function strip_ctl(s) + return (string.gsub(s or "", "%c", "")) +end + +-- Compare two strings without stopping at the first mismatch, so the time +-- taken does not reveal how many leading bytes already matched. +function constant_equals(a, b) + if a == nil or b == nil or #a ~= #b then + return false + end + local diff = 0 + for i = 1, #a do + if a:byte(i) ~= b:byte(i) then + diff = diff + 1 + end + end + return diff == 0 +end + function set_cookie(cookie, value) - html("Set-Cookie: " .. cookie .. "=" .. value .. "; HttpOnly") + html("Set-Cookie: " .. cookie .. "=" .. strip_ctl(value) .. "; HttpOnly; SameSite=Lax") if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then html("; secure") end @@ -363,7 +389,7 @@ end function redirect_to(url) html("Status: 302 Redirect\n") html("Cache-Control: no-cache, no-store\n") - html("Location: " .. url .. "\n") + html("Location: " .. strip_ctl(url) .. "\n") end function not_found() |
