From 31e670c5ca006c2f711657596e856dae67785f5f Mon Sep 17 00:00:00 2001 From: Jake Dale Ovenden Date: Mon, 8 Jan 2018 13:53:37 +1300 Subject: [PATCH] Ensure consistent response times regardless of user existing (#122) * Ensure consistent response times regardless of user existing * Make consistent times yml configurable * Document consistent response times --- code/authenticators/LDAPLoginForm.php | 50 ++++++++++++++++++++++++--- docs/en/developer.md | 19 +++++++--- 2 files changed, 60 insertions(+), 9 deletions(-) diff --git a/code/authenticators/LDAPLoginForm.php b/code/authenticators/LDAPLoginForm.php index 38dfbb9..a935b62 100644 --- a/code/authenticators/LDAPLoginForm.php +++ b/code/authenticators/LDAPLoginForm.php @@ -22,6 +22,20 @@ class LDAPLoginForm extends MemberLoginForm */ protected $ldapSecController = null; + /** + * Time in seconds that we use to ensure consistent repsonse times + * + * @var int + */ + const RESPONSE_TIME = 2; + + /** + * Enables consistent handling times of password resets + * @config + */ + private static $consistent_password_times = false; + + /** * Constructor. * @@ -70,10 +84,10 @@ public function __construct($controller, $name, $fields = null, $actions = null, // Focus on the Username field when the page is loaded Requirements::block('MemberLoginFormFieldFocus'); $js = <<consistentResponseTime($startTime); $this->controller->redirect($this->controller->Link('lostpassword')); return; } @@ -120,6 +136,7 @@ public function forgotPassword($data) // Avoid information disclosure by displaying the same status, // regardless whether the email address actually exists if (!isset($userData['objectguid'])) { + $this->consistentResponseTime($startTime); return $this->controller->redirect($this->controller->Link('passwordsent/') . urlencode($data['Login'])); } @@ -138,6 +155,7 @@ public function forgotPassword($data) // Allow vetoing forgot password requests $results = $this->extend('forgotPassword', $member); if ($results && is_array($results) && in_array(false, $results, true)) { + $this->consistentResponseTime($startTime); return $this->controller->redirect($this->ldapSecController->Link('lostpassword')); } @@ -150,10 +168,12 @@ public function forgotPassword($data) ]); $e->setTo($member->Email); $e->send(); + $this->consistentResponseTime($startTime); $this->controller->redirect($this->controller->Link('passwordsent/') . urlencode($data['Login'])); } elseif ($data['Login']) { // Avoid information disclosure by displaying the same status, // regardless whether the email address actually exists + $this->consistentResponseTime($startTime); $this->controller->redirect($this->controller->Link('passwordsent/') . urlencode($data['Login'])); } else { if (Config::inst()->get('LDAPAuthenticator', 'allow_email_login')==='yes') { @@ -173,7 +193,29 @@ public function forgotPassword($data) 'bad' ); } + $this->consistentResponseTime($startTime); $this->controller->redirect($this->controller->Link('lostpassword')); } } + + /** + * Ensures response times are the same across all scenarios i.e. email exists or doesn't + * this helps to avoid issues where malicious users could find if an email is legitimate based on response time + * + * @param float $startTime + */ + protected function consistentResponseTime($startTime) + { + if (!Config::inst()->get('LDAPLoginForm', 'consistent_password_times')) { + return; + } + + $timeTaken = microtime() - $startTime; + if ($timeTaken < self::RESPONSE_TIME) { + $sleepTime = self::RESPONSE_TIME - $timeTaken; + // usleep takes microseconds, so we times our sleep period by 1mil (1mil ms = 1s) + usleep($sleepTime * 1000000); + } + + } } diff --git a/docs/en/developer.md b/docs/en/developer.md index 54c9916..5cf4438 100644 --- a/docs/en/developer.md +++ b/docs/en/developer.md @@ -75,12 +75,12 @@ Contact your system administrator if you are not sure how to install these. ### IdP certificate and other requirements The following pieces of information are required to configure our SP endpoint: - + - IdP certificate - IdP entityId (e.g. `https:///adfs/services/trust`) - IdP SSO URL (e.g. `https:///adfs/ls`) -Talk with your ADFS administrator to find out how to obtain these. If you are managing ADFS yourself, consult the +Talk with your ADFS administrator to find out how to obtain these. If you are managing ADFS yourself, consult the [ADFS administrator guide](adfs.md). It is also possible to extract this metadata from the IdP endpoint, if ADFS is already running and the domain is known. @@ -123,7 +123,7 @@ configuration. For ADFS it's possible to downgrade the default from SHA-256 to SAMLConfiguration: Security: signatureAlgorithm: "http://www.w3.org/2000/09/xmldsig#rsa-sha1" - + ### Authentication Context config It is possible to tweak Authentication Context for the SAML requests. In practice, this depends on the [ADFS @@ -140,7 +140,7 @@ SAML request. SAMLConfiguration: Security: requestedAuthnContextBool: false - + It is also possible to provide custom list of Authentication Contexts, as well as the value for the "Comparison" attribute: @@ -149,7 +149,7 @@ attribute: requestedAuthnContextArray: - 'urn:federation:authentication:windows', requestedAuthnContextComparison: 'maximum' - + ### Service Provider (SP) - `entityId`: URI that uniquely identifies the party. This *must* be set to the site URL (e.g. `https://`), because we reuse it as the base URL for the SAML endpoints. @@ -555,6 +555,15 @@ a Member object will push up the mapped fields to LDAP, assuming that Member rec See "Map AD attributes to Member fields" section above for more information on mapping fields. +### Ensuring consistent response times + +It is possible to figure out if a user exists in the database when sending a password reset link by observing the response time. To ensure response times are the same regardless of if the user exists you can enable the following configuration: + +```yaml +LDAPLoginForm: + consistent_password_times: true +``` + ## Resources - [ADFS Deep-Dive: Onboarding Applications](http://blogs.technet.com/b/askpfeplat/archive/2015/03/02/adfs-deep-dive-onboarding-applications.aspx)