User Enumeration - Timing Discrepancies

I find myself writing this blog today as there are only a few references on the internet to user enumeration attacks via timing discrepancies, despite almost every site I've tested in my career being vulnerable to the weakness.

The issue is fairly obvious from the title; an application log-in response takes differing amount of times depending on whether or not the user is valid. But why?

Let's take a look at a typical login sequence (from the current Drupal login function):

  public function authenticate($username, $password) {
    $uid = FALSE;
    if (!empty($username) && strlen($password) > 0) {
      $account_search = $this->entityManager->getStorage('user')->loadByProperties(['name' => $username]);
      if ($account = reset($account_search)) {
        if ($this->passwordChecker->check($password, $account->getPassword())) {
          // Successful authentication.
          $uid = $account->id();
          // Update user to new password scheme if needed.
          if ($this->passwordChecker->needsRehash($account->getPassword())) {
            $account->setPassword($password);
            $account->save();
          }
        }
      }
    }
    return $uid;
  }

So what's happening here? Firstly, a database search is carried out for the supplied username. This will of course happen regardless of whether or not the user exists. However, there still may be some timing discrepancies at this point, as a positive result will include the requested account information, whilst a non-existent account will simply return zero rows.

The next step adds to the "delay" encountered if a valid account is provided. A password hash is calculated from the supplied password and compared to the result previously obtained from the database (some applications may even do an additional SQL query at this stage for the password, adding even further delay). The trouble is, password hashing alogirthms are designed to be very slow and computational expensive - it makes it harder to brute force/guess the associated password. The "stronger" the chosen hash, the longer it's going to take to compute by the application

I've observed various degrees of timing discrepancies, but it's really not uncommon to see valid user accounts take twice as long to respond as non-existent accounts. The difference may only be a couple of hundred milliseconds, but if it's consistent, an attacker can use it to their advantage.

Exploitation isn't necessarily straight forward, as the attacker would typically need to target the site when the load is quiet, and would typically need to repeat positive requests to ensure consistent results - possibly triggering account lockout thresholds.

Fixing isn't straight forward either. I'd personally recommend hashing the supplied password before the database query, that way you can ensure the hashing algorithim isn't the problem. You could go one step further and also add a random "typical" delay into the function if the number of rows returned is zero - replicating the latency that would be encountered if the user account was found in the database.

tldr; Be careful your secure password hashing function does not introduce timing discrepancies that can be exploited!