upper right bubble
ephort logo
lower left bubble

Laravel timing attack vulnerability

Timing attacks can be used to obtain sensitive information about users on a Laravel application. This article explains how.

Af Jens Just Iversen

Timing attacks are a side-channel attack that is used to obtain sensitive information by looking at how long requests took.
If a request was slow it meant that the script had more to process.
If a request was fast it meant that the script had less to process.

By this information an attacker is able to guess what goes on behind the surface - for instance if a user is registered on a site or not.

This is called a user enumeration vulnerability, and it is present in Laravel up until September 2022.

The problem with user enumeration

User enumeration is already taken serious in authentication in Laravel. That is why the error message when login failed is a generic "These credentials do not match our records."-message.

If the message had been "The email does not exist" or "The password is incorrect" it would have been a big help for users struggling to log in, but it would also be very easy for an attacker to harvest existing emails/usernames.

The problem with user enumeration is that when an attacker have a list of verified emails, they have to only try to crack the passwords for those existing emails - not all emails in the world. This makes the attack very practical.

Maybe the attacker even gets lucky and finds several leaked passwords from haveibeenpwn database matching the harvested emails?

Even though user enumeration is very sensitive for some sites, most sites are not directly vulnerable because of it. It is when user enumeration is used in tandem with other attacks it becomes a problem.

So is it a problem then?

‐ yes, because most attacks are done by combining different exploits.

Timeless timing attacks

When we started digging into timing attacks here at Ephort we quickly realized that traditional timing attacks were not that practical. Whitepapers, presentations at hacker conferences and blog posts revealed that to get something out of a traditional timing attack you had to have very large sample sizes and do a lot of advanced statistical analysis on the data.

This means that most traditional timing attacks have happened against hardware or software solutions where large sample sizes are not a problem to obtain.

Most web applications however do have some kind of throttling, max attempts, DDoS detection or the like that would make timing attacks impractical.

Discovering timeless timing attacks was another story though. Where timing attacks uses wall clock time to measure how long a request took, timeless timing attacks make use of HTTP/2 multiplexing protocol to send pairs of requests, and then only look at in which order the requests arrived back to the client.

This is possible because multiplexing allows the client to group multiple requests in one network package, let the server process them as fast as it can, and then let the server send them back to the client in the order it was done executing it. This means that all network jitter that make a traditional timing attack impractical are not part of the equation in timeless timing attacks.

That way timeless timing attacks can reliably measure timing differences in 2 different requests on 20 microseconds with a sample size of only 6. Throttling, max attempts and DDoS protection is therefor also a much smaller problem.

The Laravel timing attack

As we primarily work with Laravel we decided to look into the Laravel framework to see if it was vulnerable to timeless timing attacks. Usually authentication or forgot password-functionality are places where user enumeration through timing attacks are possible.

This was also the case with Laravel.

Looking through the source code scanning for early-returns we found this one in the Illuminate\Auth\SessionGuard class:

protected function hasValidCredentials($user, $credentials)
{
    $validated = ! is_null($user) && $this->provider->validateCredentials($user, $credentials);

    if ($validated) {
        $this->fireValidatedEvent($user);
    }

    return $validated;
}

The first line of the method checks if the user exists, but if the user does not exist, nothing more of that line as well as the $this->fireValidatedEvent($user); line are executed.

This means that there is less code to execute for the server if the user does not exist making the request take a tiny bit less time to execute compared to if the user did exist.

Tom Goethem and the three other authors of timeless timing attacks have published a proof of concept tool that can be used to test if a site is vulnerable to timeless timing attacks.

By using this tool against a fresh Laravel installation we were able to confirm that Laravel was vulnerable to timeless timing attacks. We did the following:

  • Created a lot of users on the database
  • One of these users are "known" to the hacker in our test (the hacker goes on to the site and creates a user). This is used for comparison on timing differences when sending request pairs - one of those requests in a request-pair is then always the known user.
  • For each 4 logins made by the proof of concept script we do an actual login on the site with the known user. This is done to reset the login rate limiting.
  • Disable CSRF protection (not all sites make use of this. If they do the script has to be amended so that it fetches a token before logging in)

The script then sent 5 request pairs.

Each pair contained:

  • one request to the login endpoint where the email was known, but the password was wrong.
  • one request to the login endpoint where the email was randomized, and the password was wrong.

The script very reliably determined that the second request in the pair was the one that took longer to execute, meaning the user exists.

Running the same script where both requests in the request pair contained emails that were registered on the test page, the script was not able to determine which request took longer to execute, meaning both emails exist (because the attacker knows that one of them exists).

Hackers are therefor able to harvest emails from a site by sending request pairs to the login endpoint containing one email that they have registered on the page and then the second request would contain emails that the hacker want to test if they exist.

The script we have used to test this vulnerability can be found on our GitHub.

The fix

To prevent timing attacks the time the execution of the code takes should not be dependent on the input.

That way an attacker is not able to connect input data with what actually goes on behind the surface.

We were therefor able to patch the exploit by wrapping the content of hasValidCredentials method in a "timebox". That is a method that takes a closure and a time limit, and then executes and waits until the time limit is reached.

The timebox therefor always returns in the same amount of time no matter how fast the content of the closure was executed.

The pull request to the Laravel repository can be seen here https://github.com/laravel/framework/pull/44069.

This fix does not slow down login attempts for users who type correct credentials. It only affects users who type a wrong email or password.

04. SEP 2022