mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-27 01:02:42 +01:00
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
This commit is contained in:
parent
13516cf35f
commit
29948eaa5b
11 changed files with 33 additions and 35 deletions
|
@ -209,7 +209,7 @@ abstract class PhabricatorAuthController extends PhabricatorController {
|
||||||
|
|
||||||
$actual = $account->getProperty('registrationKey');
|
$actual = $account->getProperty('registrationKey');
|
||||||
$expect = PhabricatorHash::digest($registration_key);
|
$expect = PhabricatorHash::digest($registration_key);
|
||||||
if ($actual !== $expect) {
|
if (!phutil_hashes_are_identical($actual, $expect)) {
|
||||||
$response = $this->renderError(
|
$response = $this->renderError(
|
||||||
pht(
|
pht(
|
||||||
'Your browser submitted a different registration key than the one '.
|
'Your browser submitted a different registration key than the one '.
|
||||||
|
|
|
@ -21,7 +21,10 @@ final class PhabricatorAuthTerminateSessionController
|
||||||
|
|
||||||
$sessions = $query->execute();
|
$sessions = $query->execute();
|
||||||
foreach ($sessions as $key => $session) {
|
foreach ($sessions as $key => $session) {
|
||||||
if ($session->getSessionKey() == $current_key) {
|
$is_current = phutil_hashes_are_identical(
|
||||||
|
$session->getSessionKey(),
|
||||||
|
$current_key);
|
||||||
|
if ($is_current) {
|
||||||
// Don't terminate the current login session.
|
// Don't terminate the current login session.
|
||||||
unset($sessions[$key]);
|
unset($sessions[$key]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -296,7 +296,10 @@ final class PhabricatorAuthSessionEngine extends Phobject {
|
||||||
|
|
||||||
foreach ($sessions as $key => $session) {
|
foreach ($sessions as $key => $session) {
|
||||||
if ($except_session !== null) {
|
if ($except_session !== null) {
|
||||||
if ($except_session == $session->getSessionKey()) {
|
$is_except = phutil_hashes_are_identical(
|
||||||
|
$session->getSessionKey(),
|
||||||
|
$except_session);
|
||||||
|
if ($is_except) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -201,7 +201,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
|
||||||
// case the server or client has some clock skew.
|
// case the server or client has some clock skew.
|
||||||
for ($offset = -2; $offset <= 2; $offset++) {
|
for ($offset = -2; $offset <= 2; $offset++) {
|
||||||
$real = self::getTOTPCode($key, $now + $offset);
|
$real = self::getTOTPCode($key, $now + $offset);
|
||||||
if ($real === $code) {
|
if (phutil_hashes_are_identical($real, $code)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -482,7 +482,7 @@ abstract class PhabricatorAuthProvider extends Phobject {
|
||||||
'problem persists, you may need to clear your cookies.'));
|
'problem persists, you may need to clear your cookies.'));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($actual !== $expect) {
|
if (!phutil_hashes_are_identical($actual, $expect)) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
'The authentication provider did not return the correct client '.
|
'The authentication provider did not return the correct client '.
|
||||||
|
|
|
@ -434,7 +434,8 @@ final class PhabricatorConduitAPIController
|
||||||
$token = idx($metadata, 'authToken');
|
$token = idx($metadata, 'authToken');
|
||||||
$signature = idx($metadata, 'authSignature');
|
$signature = idx($metadata, 'authSignature');
|
||||||
$certificate = $user->getConduitCertificate();
|
$certificate = $user->getConduitCertificate();
|
||||||
if (sha1($token.$certificate) !== $signature) {
|
$hash = sha1($token.$certificate);
|
||||||
|
if (!phutil_hashes_are_identical($hash, $signature)) {
|
||||||
return array(
|
return array(
|
||||||
'ERR-INVALID-AUTH',
|
'ERR-INVALID-AUTH',
|
||||||
pht('Authentication is invalid.'),
|
pht('Authentication is invalid.'),
|
||||||
|
|
|
@ -142,7 +142,7 @@ final class ConduitConnectConduitAPIMethod extends ConduitAPIMethod {
|
||||||
$threshold));
|
$threshold));
|
||||||
}
|
}
|
||||||
$valid = sha1($token.$user->getConduitCertificate());
|
$valid = sha1($token.$user->getConduitCertificate());
|
||||||
if ($valid != $signature) {
|
if (!phutil_hashes_are_identical($valid, $signature)) {
|
||||||
throw new ConduitException('ERR-INVALID-CERTIFICATE');
|
throw new ConduitException('ERR-INVALID-CERTIFICATE');
|
||||||
}
|
}
|
||||||
$session_key = id(new PhabricatorAuthSessionEngine())->establishSession(
|
$session_key = id(new PhabricatorAuthSessionEngine())->establishSession(
|
||||||
|
|
|
@ -13,9 +13,11 @@ final class PhabricatorMetaMTAMailgunReceiveController
|
||||||
$timestamp = $request->getStr('timestamp');
|
$timestamp = $request->getStr('timestamp');
|
||||||
$token = $request->getStr('token');
|
$token = $request->getStr('token');
|
||||||
$sig = $request->getStr('signature');
|
$sig = $request->getStr('signature');
|
||||||
return hash_hmac('sha256', $timestamp.$token, $api_key) == $sig;
|
$hash = hash_hmac('sha256', $timestamp.$token, $api_key);
|
||||||
|
|
||||||
|
return phutil_hashes_are_identical($sig, $hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function processRequest() {
|
public function processRequest() {
|
||||||
|
|
||||||
// No CSRF for Mailgun.
|
// No CSRF for Mailgun.
|
||||||
|
|
|
@ -365,19 +365,16 @@ final class PhabricatorUser
|
||||||
}
|
}
|
||||||
|
|
||||||
public function validateCSRFToken($token) {
|
public function validateCSRFToken($token) {
|
||||||
$salt = null;
|
// We expect a BREACH-mitigating token. See T3684.
|
||||||
$version = 'plain';
|
|
||||||
|
|
||||||
// This is a BREACH-mitigating token. See T3684.
|
|
||||||
$breach_prefix = self::CSRF_BREACH_PREFIX;
|
$breach_prefix = self::CSRF_BREACH_PREFIX;
|
||||||
$breach_prelen = strlen($breach_prefix);
|
$breach_prelen = strlen($breach_prefix);
|
||||||
|
if (strncmp($token, $breach_prefix, $breach_prelen) !== 0) {
|
||||||
if (!strncmp($token, $breach_prefix, $breach_prelen)) {
|
return false;
|
||||||
$version = 'breach';
|
|
||||||
$salt = substr($token, $breach_prelen, self::CSRF_SALT_LENGTH);
|
|
||||||
$token = substr($token, $breach_prelen + self::CSRF_SALT_LENGTH);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$salt = substr($token, $breach_prelen, self::CSRF_SALT_LENGTH);
|
||||||
|
$token = substr($token, $breach_prelen + self::CSRF_SALT_LENGTH);
|
||||||
|
|
||||||
// When the user posts a form, we check that it contains a valid CSRF token.
|
// When the user posts a form, we check that it contains a valid CSRF token.
|
||||||
// Tokens cycle each hour (every CSRF_CYLCE_FREQUENCY seconds) and we accept
|
// Tokens cycle each hour (every CSRF_CYLCE_FREQUENCY seconds) and we accept
|
||||||
// either the current token, the next token (users can submit a "future"
|
// either the current token, the next token (users can submit a "future"
|
||||||
|
@ -407,22 +404,11 @@ final class PhabricatorUser
|
||||||
|
|
||||||
for ($ii = -$csrf_window; $ii <= 1; $ii++) {
|
for ($ii = -$csrf_window; $ii <= 1; $ii++) {
|
||||||
$valid = $this->getRawCSRFToken($ii);
|
$valid = $this->getRawCSRFToken($ii);
|
||||||
switch ($version) {
|
|
||||||
// TODO: We can remove this after the BREACH version has been in the
|
$digest = PhabricatorHash::digest($valid, $salt);
|
||||||
// wild for a while.
|
$digest = substr($digest, 0, self::CSRF_TOKEN_LENGTH);
|
||||||
case 'plain':
|
if (phutil_hashes_are_identical($digest, $token)) {
|
||||||
if ($token == $valid) {
|
return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'breach':
|
|
||||||
$digest = PhabricatorHash::digest($valid, $salt);
|
|
||||||
if (substr($digest, 0, self::CSRF_TOKEN_LENGTH) == $token) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new Exception(pht('Unknown CSRF token format!'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,7 +50,10 @@ final class PhabricatorSessionsSettingsPanel extends PhabricatorSettingsPanel {
|
||||||
$rows = array();
|
$rows = array();
|
||||||
$rowc = array();
|
$rowc = array();
|
||||||
foreach ($sessions as $session) {
|
foreach ($sessions as $session) {
|
||||||
if ($session->getSessionKey() == $current_key) {
|
$is_current = phutil_hashes_are_identical(
|
||||||
|
$session->getSessionKey(),
|
||||||
|
$current_key);
|
||||||
|
if ($is_current) {
|
||||||
$rowc[] = 'highlighted';
|
$rowc[] = 'highlighted';
|
||||||
$button = phutil_tag(
|
$button = phutil_tag(
|
||||||
'a',
|
'a',
|
||||||
|
|
|
@ -126,7 +126,7 @@ abstract class PhabricatorPasswordHasher extends Phobject {
|
||||||
$actual_hash = $this->getPasswordHash($password)->openEnvelope();
|
$actual_hash = $this->getPasswordHash($password)->openEnvelope();
|
||||||
$expect_hash = $hash->openEnvelope();
|
$expect_hash = $hash->openEnvelope();
|
||||||
|
|
||||||
return ($actual_hash === $expect_hash);
|
return phutil_hashes_are_identical($actual_hash, $expect_hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue