From: Christian Flothmann <christian.flothmann@xabbuh.de>
Date: Tue, 10 Nov 2015 16:18:07 +0100
Subject: CVE-2015-8125: Vulnerability in Security Remember-Me Service

Origin: upstream, https://github.com/symfony/symfony/pull/16630
---
 .../Csrf/CsrfProvider/DefaultCsrfProvider.php      | 14 ++++++++++-
 .../Http/Firewall/DigestAuthenticationListener.php |  3 ++-
 .../PersistentTokenBasedRememberMeServices.php     |  3 ++-
 .../RememberMe/TokenBasedRememberMeServices.php    | 28 ++--------------------
 4 files changed, 19 insertions(+), 29 deletions(-)

diff --git a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php
index f0a74b4..b9b0508 100644
--- a/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php
+++ b/src/Symfony/Component/Form/Extension/Csrf/CsrfProvider/DefaultCsrfProvider.php
@@ -11,6 +11,8 @@
 
 namespace Symfony\Component\Form\Extension\Csrf\CsrfProvider;
 
+use Symfony\Component\Security\Core\Util\StringUtils;
+
 /**
  * Default implementation of CsrfProviderInterface.
  *
@@ -53,7 +55,17 @@ class DefaultCsrfProvider implements CsrfProviderInterface
      */
     public function isCsrfTokenValid($intention, $token)
     {
-        return $token === $this->generateCsrfToken($intention);
+        $expectedToken = $this->generateCsrfToken($intention);
+
+        if (function_exists('hash_equals')) {
+            return hash_equals($expectedToken, $token);
+        }
+
+        if (class_exists('Symfony\Component\Security\Core\Util\StringUtils')) {
+            return StringUtils::equals($expectedToken, $token);
+        }
+
+        return $token === $expectedToken;
     }
 
     /**
diff --git a/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php b/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php
index ea85e77..dced9a7 100644
--- a/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php
+++ b/src/Symfony/Component/Security/Http/Firewall/DigestAuthenticationListener.php
@@ -13,6 +13,7 @@ namespace Symfony\Component\Security\Http\Firewall;
 
 use Symfony\Component\Security\Core\SecurityContextInterface;
 use Symfony\Component\Security\Core\User\UserProviderInterface;
+use Symfony\Component\Security\Core\Util\StringUtils;
 use Symfony\Component\Security\Http\EntryPoint\DigestAuthenticationEntryPoint;
 use Psr\Log\LoggerInterface;
 use Symfony\Component\HttpKernel\Event\GetResponseEvent;
@@ -99,7 +100,7 @@ class DigestAuthenticationListener implements ListenerInterface
             return;
         }
 
-        if ($serverDigestMd5 !== $digestAuth->getResponse()) {
+        if (!StringUtils::equals($serverDigestMd5, $digestAuth->getResponse())) {
             if (null !== $this->logger) {
                 $this->logger->debug(sprintf("Expected response: '%s' but received: '%s'; is AuthenticationDao returning clear text passwords?", $serverDigestMd5, $digestAuth->getResponse()));
             }
diff --git a/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php b/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php
index 12478dc..5d9f096 100644
--- a/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php
+++ b/src/Symfony/Component/Security/Http/RememberMe/PersistentTokenBasedRememberMeServices.php
@@ -21,6 +21,7 @@ use Symfony\Component\Security\Core\Authentication\RememberMe\PersistentToken;
 use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
 use Symfony\Component\Security\Core\Util\SecureRandomInterface;
 use Psr\Log\LoggerInterface;
+use Symfony\Component\Security\Core\Util\StringUtils;
 
 /**
  * Concrete implementation of the RememberMeServicesInterface which needs
@@ -90,7 +91,7 @@ class PersistentTokenBasedRememberMeServices extends AbstractRememberMeServices
         list($series, $tokenValue) = $cookieParts;
         $persistentToken = $this->tokenProvider->loadTokenBySeries($series);
 
-        if ($persistentToken->getTokenValue() !== $tokenValue) {
+        if (!StringUtils::equals($persistentToken->getTokenValue(), $tokenValue)) {
             throw new CookieTheftException('This token was already used. The account is possibly compromised.');
         }
 
diff --git a/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php b/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php
index 7581a68..7190c32 100644
--- a/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php
+++ b/src/Symfony/Component/Security/Http/RememberMe/TokenBasedRememberMeServices.php
@@ -17,6 +17,7 @@ use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
 use Symfony\Component\Security\Core\Exception\AuthenticationException;
 use Symfony\Component\Security\Core\User\UserInterface;
+use Symfony\Component\Security\Core\Util\StringUtils;
 
 /**
  * Concrete implementation of the RememberMeServicesInterface providing
@@ -53,7 +54,7 @@ class TokenBasedRememberMeServices extends AbstractRememberMeServices
             throw new \RuntimeException(sprintf('The UserProviderInterface implementation must return an instance of UserInterface, but returned "%s".', get_class($user)));
         }
 
-        if (true !== $this->compareHashes($hash, $this->generateCookieHash($class, $username, $expires, $user->getPassword()))) {
+        if (!StringUtils::equals($this->generateCookieHash($class, $username, $expires, $user->getPassword()), $hash)) {
             throw new AuthenticationException('The cookie\'s hash is invalid.');
         }
 
@@ -65,31 +66,6 @@ class TokenBasedRememberMeServices extends AbstractRememberMeServices
     }
 
     /**
-     * Compares two hashes using a constant-time algorithm to avoid (remote)
-     * timing attacks.
-     *
-     * This is the same implementation as used in the BasePasswordEncoder.
-     *
-     * @param string $hash1 The first hash
-     * @param string $hash2 The second hash
-     *
-     * @return bool    true if the two hashes are the same, false otherwise
-     */
-    private function compareHashes($hash1, $hash2)
-    {
-        if (strlen($hash1) !== $c = strlen($hash2)) {
-            return false;
-        }
-
-        $result = 0;
-        for ($i = 0; $i < $c; $i++) {
-            $result |= ord($hash1[$i]) ^ ord($hash2[$i]);
-        }
-
-        return 0 === $result;
-    }
-
-    /**
      * {@inheritdoc}
      */
     protected function onLoginSuccess(Request $request, Response $response, TokenInterface $token)
