@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator

Use phutil_hashes_are_identical() when comparing hashes in Phabricator

Summary: See D14025. In all cases where we compare hashes, use strict, constant-time comparisons.

Test Plan: Logged in, logged out, added TOTP, ran Conduit, terminated sessions, submitted forms, changed password. Tweaked CSRF token, got rejected.

Reviewers: chad

Reviewed By: chad

Subscribers: chenxiruanhai

Differential Revision: https://secure.phabricator.com/D14026

+33 -35
+1 -1
src/applications/auth/controller/PhabricatorAuthController.php
··· 209 209 210 210 $actual = $account->getProperty('registrationKey'); 211 211 $expect = PhabricatorHash::digest($registration_key); 212 - if ($actual !== $expect) { 212 + if (!phutil_hashes_are_identical($actual, $expect)) { 213 213 $response = $this->renderError( 214 214 pht( 215 215 'Your browser submitted a different registration key than the one '.
+4 -1
src/applications/auth/controller/PhabricatorAuthTerminateSessionController.php
··· 21 21 22 22 $sessions = $query->execute(); 23 23 foreach ($sessions as $key => $session) { 24 - if ($session->getSessionKey() == $current_key) { 24 + $is_current = phutil_hashes_are_identical( 25 + $session->getSessionKey(), 26 + $current_key); 27 + if ($is_current) { 25 28 // Don't terminate the current login session. 26 29 unset($sessions[$key]); 27 30 }
+4 -1
src/applications/auth/engine/PhabricatorAuthSessionEngine.php
··· 296 296 297 297 foreach ($sessions as $key => $session) { 298 298 if ($except_session !== null) { 299 - if ($except_session == $session->getSessionKey()) { 299 + $is_except = phutil_hashes_are_identical( 300 + $session->getSessionKey(), 301 + $except_session); 302 + if ($is_except) { 300 303 continue; 301 304 } 302 305 }
+1 -1
src/applications/auth/factor/PhabricatorTOTPAuthFactor.php
··· 201 201 // case the server or client has some clock skew. 202 202 for ($offset = -2; $offset <= 2; $offset++) { 203 203 $real = self::getTOTPCode($key, $now + $offset); 204 - if ($real === $code) { 204 + if (phutil_hashes_are_identical($real, $code)) { 205 205 return true; 206 206 } 207 207 }
+1 -1
src/applications/auth/provider/PhabricatorAuthProvider.php
··· 482 482 'problem persists, you may need to clear your cookies.')); 483 483 } 484 484 485 - if ($actual !== $expect) { 485 + if (!phutil_hashes_are_identical($actual, $expect)) { 486 486 throw new Exception( 487 487 pht( 488 488 'The authentication provider did not return the correct client '.
+2 -1
src/applications/conduit/controller/PhabricatorConduitAPIController.php
··· 434 434 $token = idx($metadata, 'authToken'); 435 435 $signature = idx($metadata, 'authSignature'); 436 436 $certificate = $user->getConduitCertificate(); 437 - if (sha1($token.$certificate) !== $signature) { 437 + $hash = sha1($token.$certificate); 438 + if (!phutil_hashes_are_identical($hash, $signature)) { 438 439 return array( 439 440 'ERR-INVALID-AUTH', 440 441 pht('Authentication is invalid.'),
+1 -1
src/applications/conduit/method/ConduitConnectConduitAPIMethod.php
··· 142 142 $threshold)); 143 143 } 144 144 $valid = sha1($token.$user->getConduitCertificate()); 145 - if ($valid != $signature) { 145 + if (!phutil_hashes_are_identical($valid, $signature)) { 146 146 throw new ConduitException('ERR-INVALID-CERTIFICATE'); 147 147 } 148 148 $session_key = id(new PhabricatorAuthSessionEngine())->establishSession(
+3 -1
src/applications/metamta/controller/PhabricatorMetaMTAMailgunReceiveController.php
··· 13 13 $timestamp = $request->getStr('timestamp'); 14 14 $token = $request->getStr('token'); 15 15 $sig = $request->getStr('signature'); 16 - return hash_hmac('sha256', $timestamp.$token, $api_key) == $sig; 16 + $hash = hash_hmac('sha256', $timestamp.$token, $api_key); 17 17 18 + return phutil_hashes_are_identical($sig, $hash); 18 19 } 20 + 19 21 public function processRequest() { 20 22 21 23 // No CSRF for Mailgun.
+11 -25
src/applications/people/storage/PhabricatorUser.php
··· 365 365 } 366 366 367 367 public function validateCSRFToken($token) { 368 - $salt = null; 369 - $version = 'plain'; 370 - 371 - // This is a BREACH-mitigating token. See T3684. 368 + // We expect a BREACH-mitigating token. See T3684. 372 369 $breach_prefix = self::CSRF_BREACH_PREFIX; 373 370 $breach_prelen = strlen($breach_prefix); 371 + if (strncmp($token, $breach_prefix, $breach_prelen) !== 0) { 372 + return false; 373 + } 374 374 375 - if (!strncmp($token, $breach_prefix, $breach_prelen)) { 376 - $version = 'breach'; 377 - $salt = substr($token, $breach_prelen, self::CSRF_SALT_LENGTH); 378 - $token = substr($token, $breach_prelen + self::CSRF_SALT_LENGTH); 379 - } 375 + $salt = substr($token, $breach_prelen, self::CSRF_SALT_LENGTH); 376 + $token = substr($token, $breach_prelen + self::CSRF_SALT_LENGTH); 380 377 381 378 // When the user posts a form, we check that it contains a valid CSRF token. 382 379 // Tokens cycle each hour (every CSRF_CYLCE_FREQUENCY seconds) and we accept ··· 407 404 408 405 for ($ii = -$csrf_window; $ii <= 1; $ii++) { 409 406 $valid = $this->getRawCSRFToken($ii); 410 - switch ($version) { 411 - // TODO: We can remove this after the BREACH version has been in the 412 - // wild for a while. 413 - case 'plain': 414 - if ($token == $valid) { 415 - return true; 416 - } 417 - break; 418 - case 'breach': 419 - $digest = PhabricatorHash::digest($valid, $salt); 420 - if (substr($digest, 0, self::CSRF_TOKEN_LENGTH) == $token) { 421 - return true; 422 - } 423 - break; 424 - default: 425 - throw new Exception(pht('Unknown CSRF token format!')); 407 + 408 + $digest = PhabricatorHash::digest($valid, $salt); 409 + $digest = substr($digest, 0, self::CSRF_TOKEN_LENGTH); 410 + if (phutil_hashes_are_identical($digest, $token)) { 411 + return true; 426 412 } 427 413 } 428 414
+4 -1
src/applications/settings/panel/PhabricatorSessionsSettingsPanel.php
··· 50 50 $rows = array(); 51 51 $rowc = array(); 52 52 foreach ($sessions as $session) { 53 - if ($session->getSessionKey() == $current_key) { 53 + $is_current = phutil_hashes_are_identical( 54 + $session->getSessionKey(), 55 + $current_key); 56 + if ($is_current) { 54 57 $rowc[] = 'highlighted'; 55 58 $button = phutil_tag( 56 59 'a',
+1 -1
src/infrastructure/util/password/PhabricatorPasswordHasher.php
··· 126 126 $actual_hash = $this->getPasswordHash($password)->openEnvelope(); 127 127 $expect_hash = $hash->openEnvelope(); 128 128 129 - return ($actual_hash === $expect_hash); 129 + return phutil_hashes_are_identical($actual_hash, $expect_hash); 130 130 } 131 131 132 132