Skip to content

Move Timebox for Authentication and add to password resets #55701

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
May 10, 2025

Conversation

valorin
Copy link
Contributor

@valorin valorin commented May 10, 2025

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 call hasValidCredentials: validate, attempt, and attemptWhen. 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(), and PasswordBroker::reset(), both of which could be used to enumerate user accounts.

Notes

  • Preventing timing attacks is virtually impossible, as there will always been some variations between different requests, however detecting these variations can be extremely difficult, so if enumeration is a concern then adding the Timebox can make a difference and slow down an attacker.
  • The default Starter Kits leak user existence in other ways, regardless of these changes. This fix is for the devs who have implemented their own authentication system on top of the framework and expect the Timebox to prevent enumeration.

Copy link
Contributor

@JurianArie JurianArie left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried getting meaningful but simple benchmarks but the timing differences were too subtle.

James Kettle has a great write-up on this topic that you might find interesting: https://portswigger.net/research/listen-to-the-whispers-web-timing-attacks-that-actually-work

@valorin
Copy link
Contributor Author

valorin commented May 10, 2025

I tried getting meaningful but simple benchmarks but the timing differences were too subtle.

James Kettle has a great write-up on this topic that you might find interesting: https://portswigger.net/research/listen-to-the-whispers-web-timing-attacks-that-actually-work

Oh nice, I'll take a look, thanks! 👍

valorin and others added 2 commits May 10, 2025 23:55
Co-authored-by: JurianArie <28654085+JurianArie@users.noreply.github.com>
@taylorotwell taylorotwell merged commit df70ad9 into laravel:12.x May 10, 2025
43 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants