ModernUO/Projects
Kamron Batman dce677b8c3 fix(accounts): gate the password repair on a per-account tag
The repair retried a failed verify against username + plainPassword, and
nothing in a stored hash distinguishes "this is H(username + password)
because SetPassword was buggy" from "this password merely begins with the
username". With repairMigratedPasswords on, that is a cheap targeted attack
against every account on the shard:

  Account 'bob', real password 'bob123', stored correctly as argon2("bob123").
  Someone submits '123'. The primary verify against "123" fails. The repair
  verifies "bob" + "123" == "bob123" and succeeds, forceRehash rewrites the
  credential to argon2("123"), and the real owner can never log in again --
  their password has been silently replaced by the attacker's guess.

Passwords that start with the username are common enough that this is worth
doing at scale, and it is silent: the victim only finds out later.

Add a second gate on top of the shard-wide switch. The account must also
carry a RepairMigratedPassword tag, which an operator sets from the admin
gump (Account Details -> Tags -> Add Tag) for someone who has actually
reported being locked out. That reduces exposure from every account on the
shard to one an operator deliberately marked, and in the real workflow the
collision cannot arise at all: a colliding account still logs in fine, so
nobody would ever mark it. The tag is removed on a successful repair, so the
window closes for that account immediately.

Also repair the other corruption shape. The old guard short-circuited on
UsesUsernamePhrase(_passwordAlgorithm), so SHA1/SHA2 accounts were never
repaired -- but SetPassword also runs from the Account(string, string)
constructor before _passwordAlgorithm is assigned, so an account created
while CurrentAlgorithm was SHA1 or SHA2 holds H(bare password) tagged
SHA1/SHA2, whose rule adds the username. Those accounts could never log in
at all and the repair declined to rescue them. RepairPhrase now tries
whichever rule the stored algorithm does not use, covering both directions.
That direction has no truncation collision -- exploiting it would need the
attacker to already know the real password -- but it stays behind both gates.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-07 17:30:45 -07:00
..
Application fix: Bumps dependencies. (#2531) 2026-07-14 15:17:55 -07:00
BuildTool fix: Require only runtime packages on Linux, and check ICU and tzdata the way the runtime does (#2561) 2026-08-07 15:03:08 -07:00
Logger fix: Bumps dependencies. (#2531) 2026-07-14 15:17:55 -07:00
Server fix: Require only runtime packages on Linux, and check ICU and tzdata the way the runtime does (#2561) 2026-08-07 15:03:08 -07:00
Server.Tests feat(network): allowlist false-positive IPs, escalate on behavior (#2556) 2026-07-30 23:12:17 -07:00
UOContent fix(accounts): gate the password repair on a per-account tag 2026-08-07 17:30:45 -07:00
UOContent.Tests fix(accounts): gate the password repair on a per-account tag 2026-08-07 17:30:45 -07:00