Move Timebox for Authentication and add to password resets #55701
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.
This PR fixes two related issues, and should be back-ported to all versions where security fixes are applied.
1. Authentication flow Timebox in wrong spot
The Timebox class was introduced in #44069 inside the
SessionGuard::hasValidCredentials()
method as a way to mask timing differences within the authentication flow that can lead to user enumeration attacks. However it was implemented in the wrong spot - it is applied after the database is queried for a user record. As such, the response time for a request where the user record doesn't exist will be slightly shorter than that where the user does exist - making it measurable and vulnerable to an enumeration attack.To fix the issue, and maintain backwards compatibility, I've moved the Timebox from
SessionGuard::hasValidCredentials()
to the three methods which callhasValidCredentials
:validate
,attempt
, andattemptWhen
. This allows the Timebox to encapsulate the database queries, masking user existence and making user enumeration significantly more difficult.I also added the
auth.timebox_duration
configuration option, as in my tests the default 200,000 microsecond wait time wasn't sufficient for a slow server to mask the timing differences. By making this configurable, devs can easily tweak it to optimise for their infrastructure.I tried getting meaningful but simple benchmarks but the timing differences were too subtle. The differences are easy to observe in isolation - such as the difference in query time, but tying them together to benchmark the whole request was difficult. I can provide more details as needed.
2. Adding Timebox to the Password Reset
The Timebox was also missing from password reset requests, which would allow an attacker to easily enumerate user accounts based on that response time too. I added the Timebox to
PasswordBroker::sendResetLink()
, andPasswordBroker::reset()
, both of which could be used to enumerate user accounts.Notes