1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-23 05:50:55 +01:00

Tighten some MFA/TOTP parameters to improve resistance to brute force attacks

Summary:
Depends on D19897. Ref T13222. See some discussion in D19890.

  - Only rate limit users if they're actually answering a challenge, not if they're just clicking "Wait Patiently".
  - Reduce the number of allowed attempts per hour from 100 back to 10.
  - Reduce the TOTP window from +/- 2 timesteps (allowing ~60 seconds of skew) to +/- 1 timestep (allowing ~30 seconds of skew).
  - Change the window where a TOTP response remains valid to a flat 60 seconds instead of a calculation based on windows and timesteps.

Test Plan:
  - Hit an MFA prompt.
  - Without typing in any codes, mashed "submit" as much as I wanted (>>10 times / hour).
  - Answered prompt correctly.
  - Mashed "Wait Patiently" as much as I wanted (>>10 times / hour).
  - Guessed random numbers, was rate limited after 10 attempts.

Reviewers: amckinley

Reviewed By: amckinley

Maniphest Tasks: T13222

Differential Revision: https://secure.phabricator.com/D19898
This commit is contained in:
epriestley 2018-12-18 05:55:17 -08:00
parent 543f2b6bf1
commit 3da9844564
4 changed files with 59 additions and 18 deletions

View file

@ -9,7 +9,7 @@ final class PhabricatorAuthTryFactorAction extends PhabricatorSystemAction {
}
public function getScoreThreshold() {
return 100 / phutil_units('1 hour in seconds');
return 10 / phutil_units('1 hour in seconds');
}
public function getLimitExplanation() {

View file

@ -567,10 +567,21 @@ final class PhabricatorAuthSessionEngine extends Phobject {
if ($request->getExists(AphrontRequest::TYPE_HISEC)) {
// Limit factor verification rates to prevent brute force attacks.
PhabricatorSystemActionEngine::willTakeAction(
array($viewer->getPHID()),
new PhabricatorAuthTryFactorAction(),
1);
$any_attempt = false;
foreach ($factors as $factor) {
$impl = $factor->requireImplementation();
if ($impl->getRequestHasChallengeResponse($factor, $request)) {
$any_attempt = true;
break;
}
}
if ($any_attempt) {
PhabricatorSystemActionEngine::willTakeAction(
array($viewer->getPHID()),
new PhabricatorAuthTryFactorAction(),
1);
}
foreach ($factors as $factor) {
$factor_phid = $factor->getPHID();
@ -610,10 +621,12 @@ final class PhabricatorAuthSessionEngine extends Phobject {
}
// Give the user a credit back for a successful factor verification.
PhabricatorSystemActionEngine::willTakeAction(
array($viewer->getPHID()),
new PhabricatorAuthTryFactorAction(),
-1);
if ($any_attempt) {
PhabricatorSystemActionEngine::willTakeAction(
array($viewer->getPHID()),
new PhabricatorAuthTryFactorAction(),
-1);
}
if ($session->getIsPartial() && !$jump_into_hisec) {
// If we have a partial session and are not jumping directly into

View file

@ -52,6 +52,10 @@ abstract class PhabricatorAuthFactor extends Phobject {
->setWorkflowKey($engine->getWorkflowKey());
}
abstract public function getRequestHasChallengeResponse(
PhabricatorAuthFactorConfig $config,
AphrontRequest $response);
final public function getNewIssuedChallenges(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,

View file

@ -80,7 +80,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
$okay = (bool)$this->getTimestepAtWhichResponseIsValid(
$this->getAllowedTimesteps($this->getCurrentTimestep()),
new PhutilOpaqueEnvelope($key),
(string)$code);
$code);
if ($okay) {
$config = $this->newConfigForUser($user)
@ -206,9 +206,10 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
if (!$control) {
$value = $result->getValue();
$error = $result->getErrorMessage();
$name = $this->getChallengeResponseParameterName($config);
$control = id(new PHUIFormNumberControl())
->setName($this->getParameterName($config, 'totpcode'))
->setName($name)
->setDisableAutocomplete(true)
->setValue($value)
->setError($error);
@ -221,6 +222,15 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
$form->appendChild($control);
}
public function getRequestHasChallengeResponse(
PhabricatorAuthFactorConfig $config,
AphrontRequest $request) {
$value = $this->getChallengeResponseFromRequest($config, $request);
return (bool)strlen($value);
}
protected function newResultFromIssuedChallenges(
PhabricatorAuthFactorConfig $config,
PhabricatorUser $viewer,
@ -301,7 +311,9 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
AphrontRequest $request,
array $challenges) {
$code = $request->getStr($this->getParameterName($config, 'totpcode'));
$code = $this->getChallengeResponseFromRequest(
$config,
$request);
$result = $this->newResult()
->setValue($code);
@ -335,13 +347,10 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
$valid_timestep = $this->getTimestepAtWhichResponseIsValid(
array_intersect_key($challenge_timesteps, $current_timesteps),
new PhutilOpaqueEnvelope($config->getFactorSecret()),
(string)$code);
$code);
if ($valid_timestep) {
$now = PhabricatorTime::getNow();
$step_duration = $this->getTimestepDuration();
$step_window = $this->getTimestepWindowSize();
$ttl = $now + ($step_duration * $step_window);
$ttl = PhabricatorTime::getNow() + 60;
$challenge
->setProperty('totp.timestep', $valid_timestep)
@ -475,7 +484,7 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
// The user is allowed to provide a code from the recent past or the
// near future to account for minor clock skew between the client
// and server, and the time it takes to actually enter a code.
return 2;
return 1;
}
private function getTimestepAtWhichResponseIsValid(
@ -493,6 +502,21 @@ final class PhabricatorTOTPAuthFactor extends PhabricatorAuthFactor {
return null;
}
private function getChallengeResponseParameterName(
PhabricatorAuthFactorConfig $config) {
return $this->getParameterName($config, 'totpcode');
}
private function getChallengeResponseFromRequest(
PhabricatorAuthFactorConfig $config,
AphrontRequest $request) {
$name = $this->getChallengeResponseParameterName($config);
$value = $request->getStr($name);
$value = (string)$value;
$value = trim($value);
return $value;
}
}