@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
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+59 -18
+1 -1
src/applications/auth/action/PhabricatorAuthTryFactorAction.php
··· 9 9 } 10 10 11 11 public function getScoreThreshold() { 12 - return 100 / phutil_units('1 hour in seconds'); 12 + return 10 / phutil_units('1 hour in seconds'); 13 13 } 14 14 15 15 public function getLimitExplanation() {
+21 -8
src/applications/auth/engine/PhabricatorAuthSessionEngine.php
··· 567 567 if ($request->getExists(AphrontRequest::TYPE_HISEC)) { 568 568 569 569 // Limit factor verification rates to prevent brute force attacks. 570 - PhabricatorSystemActionEngine::willTakeAction( 571 - array($viewer->getPHID()), 572 - new PhabricatorAuthTryFactorAction(), 573 - 1); 570 + $any_attempt = false; 571 + foreach ($factors as $factor) { 572 + $impl = $factor->requireImplementation(); 573 + if ($impl->getRequestHasChallengeResponse($factor, $request)) { 574 + $any_attempt = true; 575 + break; 576 + } 577 + } 578 + 579 + if ($any_attempt) { 580 + PhabricatorSystemActionEngine::willTakeAction( 581 + array($viewer->getPHID()), 582 + new PhabricatorAuthTryFactorAction(), 583 + 1); 584 + } 574 585 575 586 foreach ($factors as $factor) { 576 587 $factor_phid = $factor->getPHID(); ··· 610 621 } 611 622 612 623 // Give the user a credit back for a successful factor verification. 613 - PhabricatorSystemActionEngine::willTakeAction( 614 - array($viewer->getPHID()), 615 - new PhabricatorAuthTryFactorAction(), 616 - -1); 624 + if ($any_attempt) { 625 + PhabricatorSystemActionEngine::willTakeAction( 626 + array($viewer->getPHID()), 627 + new PhabricatorAuthTryFactorAction(), 628 + -1); 629 + } 617 630 618 631 if ($session->getIsPartial() && !$jump_into_hisec) { 619 632 // If we have a partial session and are not jumping directly into
+4
src/applications/auth/factor/PhabricatorAuthFactor.php
··· 52 52 ->setWorkflowKey($engine->getWorkflowKey()); 53 53 } 54 54 55 + abstract public function getRequestHasChallengeResponse( 56 + PhabricatorAuthFactorConfig $config, 57 + AphrontRequest $response); 58 + 55 59 final public function getNewIssuedChallenges( 56 60 PhabricatorAuthFactorConfig $config, 57 61 PhabricatorUser $viewer,
+33 -9
src/applications/auth/factor/PhabricatorTOTPAuthFactor.php
··· 80 80 $okay = (bool)$this->getTimestepAtWhichResponseIsValid( 81 81 $this->getAllowedTimesteps($this->getCurrentTimestep()), 82 82 new PhutilOpaqueEnvelope($key), 83 - (string)$code); 83 + $code); 84 84 85 85 if ($okay) { 86 86 $config = $this->newConfigForUser($user) ··· 206 206 if (!$control) { 207 207 $value = $result->getValue(); 208 208 $error = $result->getErrorMessage(); 209 + $name = $this->getChallengeResponseParameterName($config); 209 210 210 211 $control = id(new PHUIFormNumberControl()) 211 - ->setName($this->getParameterName($config, 'totpcode')) 212 + ->setName($name) 212 213 ->setDisableAutocomplete(true) 213 214 ->setValue($value) 214 215 ->setError($error); ··· 221 222 $form->appendChild($control); 222 223 } 223 224 225 + public function getRequestHasChallengeResponse( 226 + PhabricatorAuthFactorConfig $config, 227 + AphrontRequest $request) { 228 + 229 + $value = $this->getChallengeResponseFromRequest($config, $request); 230 + return (bool)strlen($value); 231 + } 232 + 233 + 224 234 protected function newResultFromIssuedChallenges( 225 235 PhabricatorAuthFactorConfig $config, 226 236 PhabricatorUser $viewer, ··· 301 311 AphrontRequest $request, 302 312 array $challenges) { 303 313 304 - $code = $request->getStr($this->getParameterName($config, 'totpcode')); 314 + $code = $this->getChallengeResponseFromRequest( 315 + $config, 316 + $request); 305 317 306 318 $result = $this->newResult() 307 319 ->setValue($code); ··· 335 347 $valid_timestep = $this->getTimestepAtWhichResponseIsValid( 336 348 array_intersect_key($challenge_timesteps, $current_timesteps), 337 349 new PhutilOpaqueEnvelope($config->getFactorSecret()), 338 - (string)$code); 350 + $code); 339 351 340 352 if ($valid_timestep) { 341 - $now = PhabricatorTime::getNow(); 342 - $step_duration = $this->getTimestepDuration(); 343 - $step_window = $this->getTimestepWindowSize(); 344 - $ttl = $now + ($step_duration * $step_window); 353 + $ttl = PhabricatorTime::getNow() + 60; 345 354 346 355 $challenge 347 356 ->setProperty('totp.timestep', $valid_timestep) ··· 475 484 // The user is allowed to provide a code from the recent past or the 476 485 // near future to account for minor clock skew between the client 477 486 // and server, and the time it takes to actually enter a code. 478 - return 2; 487 + return 1; 479 488 } 480 489 481 490 private function getTimestepAtWhichResponseIsValid( ··· 493 502 return null; 494 503 } 495 504 505 + private function getChallengeResponseParameterName( 506 + PhabricatorAuthFactorConfig $config) { 507 + return $this->getParameterName($config, 'totpcode'); 508 + } 496 509 510 + private function getChallengeResponseFromRequest( 511 + PhabricatorAuthFactorConfig $config, 512 + AphrontRequest $request) { 497 513 514 + $name = $this->getChallengeResponseParameterName($config); 515 + 516 + $value = $request->getStr($name); 517 + $value = (string)$value; 518 + $value = trim($value); 519 + 520 + return $value; 521 + } 498 522 }