Merge pull request #43 from silverstripe-security/pulls/3.5/member-enumeration-timing-attack

[SS-2017-005] User enumeration via timing attack mitigated
This commit is contained in:
Daniel Hensby 2017-09-20 11:39:39 +01:00 committed by GitHub
commit 72702dbd50
3 changed files with 29 additions and 5 deletions

View File

@ -18,9 +18,18 @@ abstract class LoginForm extends Form {
* form. * form.
* @var string * @var string
*/ */
protected $authenticator_class; protected $authenticator_class;
/**
* The minimum amount of time authenticating is allowed to take in milliseconds.
*
* Protects against timing enumeration attacks
*
* @config
* @var int
*/
private static $min_auth_time = 350;
/** /**
* Get the authenticator instance * Get the authenticator instance
* *

View File

@ -150,6 +150,10 @@ class MemberAuthenticator extends Authenticator {
* @see Security::setDefaultAdmin() * @see Security::setDefaultAdmin()
*/ */
public static function authenticate($data, Form $form = null) { public static function authenticate($data, Form $form = null) {
// minimum execution time for authenticating a member
$minExecTime = LoginForm::config()->min_auth_time / 1000;
$startTime = microtime(true);
// Find authenticated member // Find authenticated member
$member = static::authenticate_member($data, $form, $success); $member = static::authenticate_member($data, $form, $success);
@ -170,6 +174,11 @@ class MemberAuthenticator extends Authenticator {
if($success) Session::clear('BackURL'); if($success) Session::clear('BackURL');
$waitFor = $minExecTime - (microtime(true) - $startTime);
if ($waitFor > 0) {
usleep($waitFor * 1000000);
}
return $success ? $member : null; return $success ? $member : null;
} }

View File

@ -294,6 +294,10 @@ JS;
* @param array $data Submitted data * @param array $data Submitted data
*/ */
public function forgotPassword($data) { public function forgotPassword($data) {
// minimum execution time for authenticating a member
$minExecTime = self::config()->min_auth_time / 1000;
$startTime = microtime(true);
// Ensure password is given // Ensure password is given
if(empty($data['Email'])) { if(empty($data['Email'])) {
$this->sessionMessage( $this->sessionMessage(
@ -311,10 +315,8 @@ JS;
// Allow vetoing forgot password requests // Allow vetoing forgot password requests
$results = $this->extend('forgotPassword', $member); $results = $this->extend('forgotPassword', $member);
if($results && is_array($results) && in_array(false, $results, true)) { if($results && is_array($results) && in_array(false, $results, true)) {
return $this->controller->redirect('Security/lostpassword'); $this->controller->redirect('Security/lostpassword');
} } elseif ($member) {
if($member) {
$token = $member->generateAutologinTokenAndStoreHash(); $token = $member->generateAutologinTokenAndStoreHash();
$e = Member_ForgotPasswordEmail::create(); $e = Member_ForgotPasswordEmail::create();
@ -338,6 +340,10 @@ JS;
$this->controller->redirect('Security/lostpassword'); $this->controller->redirect('Security/lostpassword');
} }
$waitFor = $minExecTime - (microtime(true) - $startTime);
if ($waitFor > 0) {
usleep($waitFor * 1000000);
}
} }
} }