Broken Authentication

Broken Authentication and Laravel naturally [CVE-2017-14775]

[Updated month year] Updated CVE-2017-14775

Overview

The CVE-2017-14775 vulnerability affected Laravel before 5.5.10, where the remember_me token verification in DatabaseUserProvider did not use constant-time comparison. This allowed timing-based information disclosure (CWE-200) that could enable an attacker to brute-force the remember_token and hijack a user session. In real-world terms, a persistent remember-me cookie could be exploited to gain unauthorized access without a password if an attacker could observe or influence the token verification timing. The issue hinges on non-constant-time string comparison during token validation, which can leak information about the token bits through response timing differences. While tokens are random, the imperfect comparison provides a potential side channel that skilled attackers could exploit across repeated requests. This vulnerability is categorized under CWE-200 for information exposure due to improper cryptographic handling and improper token verification.

Affected Versions

Laravel 5.5.x before 5.5.10

Code Fix Example

Laravel API Security Remediation
<?php
// Demonstration of vulnerable vs fixed remember_token verification in Laravel-like code.

class VulnerableDatabaseUserProvider
{
    public function retrieveByToken($identifier, $token)
    {
        $user = $this->getUserById($identifier);
        if (!$user) { return null; }
        // Vulnerable: non-constant-time comparison using loose equality
        if ($token == $user->remember_token) {
            return $user;
        }
        return null;
    }

    protected function getUserById($id)
    {
        $users = [
            1 => (object)[ 'id' => 1, 'remember_token' => 'abcd1234efgh5678ijkl9012mnop3456' ],
        ];
        return $users[$id] ?? null;
    }
}

class FixedDatabaseUserProvider
{
    public function retrieveByToken($identifier, $token)
    {
        $user = $this->getUserById($identifier);
        if (!$user) { return null; }
        // Fixed: use constant-time comparison
        if (hash_equals((string)$user->remember_token, (string)$token)) {
            return $user;
        }
        return null;
    }

    protected function getUserById($id)
    {
        $users = [
            1 => (object)[ 'id' => 1, 'remember_token' => 'abcd1234efgh5678ijkl9012mnop3456' ],
        ];
        return $users[$id] ?? null;
    }
}

// Demonstration (simulated cookies and identifiers)
$cookieToken = isset($_COOKIE['remember_token']) ? $_COOKIE['remember_token'] : '';
$identifier = 1; // simulated user id

$vul = (new VulnerableDatabaseUserProvider())->retrieveByToken($identifier, $cookieToken);
$fix = (new FixedDatabaseUserProvider())->retrieveByToken($identifier, $cookieToken);

echo $vul ? 'VULNERABLE: token matched' : 'VULNERABLE: no match';
echo PHP_EOL;
echo $fix ? 'FIXED: token matched' : 'FIXED: no match';
?>

CVE References

Choose which optional cookies to allow. You can change this any time.