fix: logout CSRF (A-1) and login user-enumeration timing (A-2)#76
Merged
Conversation
…ration Two low-severity hardenings from the security audit: A-1 (logout CSRF): /Account/SignOutAndRedirect is a GET that signs out. It exists because an interactive Blazor circuit cannot write the sign-out cookie (the DeletePersonalData flow force-navigates to it), so it can't simply become a POST. A cross-site page could force a sign-out via <img>/link/prefetch. Reject cross-site requests via Sec-Fetch-Site (mirroring the login-handoff same-origin check); the legitimate same-origin navigation still works. A-2 (user enumeration): the login form returned immediately when the email did not match any account, skipping the password hash that a real attempt performs — a timing oracle distinguishing registered from unregistered emails. Perform a throwaway password hash on the not-found path so both cost the same (CWE-208). Register IPasswordHasher<User> in the LoginForm bUnit test host.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two low-severity hardenings from the security audit.
A-1 — Logout CSRF on
/Account/SignOutAndRedirect/Account/SignOutAndRedirectis aMapGetthat callsSignOutAsync(). A GET with a side effect is triggerable cross-site (<img src>, prefetch, a crafted link) with no antiforgery token, so any page the victim visits can silently sign them out.It can't simply become a POST: the endpoint exists because an interactive Blazor circuit cannot write the sign-out cookie itself —
DeletePersonalData.razorforce-navigates here after erasing the account. So instead this adds a same-origin gate usingSec-Fetch-Site(the same mechanismCookieLoginMiddlewarealready uses for the login handoff): a cross-site request is rejected and redirected home without signing out, while the legitimate same-origin navigation still works. Fails open only for legacy clients that don't sendSec-Fetch-Site(acceptable for a logout-CSRF nuisance).A-2 — User enumeration via login timing
LoginFormreturned immediately whenFindByEmailAsyncfound no account, skipping the password hash that a real attempt runs viaCheckPasswordAsync. The measurable timing delta lets an attacker distinguish registered from unregistered emails (CWE-208) even though the response message is already uniform.Fix: on the not-found path, perform a throwaway
PasswordHasher.HashPassword(new User(), password)so both paths pay ~one KDF pass. (HashPasswordavoids the hash-format concerns ofVerifyHashedPasswordagainst a canned hash.) Per-account lockout and host rate limiting remain the primary throttles; this closes the timing side channel.Tests
IPasswordHasher<User>registered in theLoginFormbUnit host; the 5 LoginForm tests and all 68 Blazor tests pass.Both were flagged Low in the audit and excluded from the earlier Medium+ issue batch, so there are no tracking issues to close.