Skip to content
This repository has been archived by the owner on Dec 21, 2022. It is now read-only.

Commit

Permalink
Ensure consistent response times regardless of user existing (#122)
Browse files Browse the repository at this point in the history
* Ensure consistent response times regardless of user existing
* Make consistent times yml configurable
* Document consistent response times
  • Loading branch information
jakedaleweb authored and Stig Lindqvist committed Jan 8, 2018
1 parent 255e0c4 commit 31e670c
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 9 deletions.
50 changes: 46 additions & 4 deletions code/authenticators/LDAPLoginForm.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*
Expand Down Expand Up @@ -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 = <<<JS
(function() {
var el = document.getElementById("Login");
if(el && el.focus && (typeof jQuery == 'undefined' || jQuery(el).is(':visible'))) el.focus();
})();
(function() {
var el = document.getElementById("Login");
if(el && el.focus && (typeof jQuery == 'undefined' || jQuery(el).is(':visible'))) el.focus();
})();
JS;
Requirements::customScript($js, 'LDAPLoginFormFieldFocus');
}
Expand All @@ -96,6 +110,7 @@ public function __construct($controller, $name, $fields = null, $actions = null,
*/
public function forgotPassword($data)
{
$startTime = microtime();
// No need to protect against injections, LDAPService will ensure that this is safe
$login = trim($data['Login']);

Expand All @@ -109,6 +124,7 @@ public function forgotPassword($data)
),
'bad'
);
$this->consistentResponseTime($startTime);
$this->controller->redirect($this->controller->Link('lostpassword'));
return;
}
Expand All @@ -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']));
}
Expand All @@ -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'));
}

Expand All @@ -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') {
Expand All @@ -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);
}

}
}
19 changes: 14 additions & 5 deletions docs/en/developer.md
Original file line number Diff line number Diff line change
Expand Up @@ -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://<idp-domain>/adfs/services/trust`)
- IdP SSO URL (e.g. `https://<idp-domain>/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.
Expand Down Expand Up @@ -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
Expand All @@ -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:

Expand All @@ -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://<your-site-domain>`), because we reuse it as the base URL for the SAML endpoints.
Expand Down Expand Up @@ -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)

0 comments on commit 31e670c

Please sign in to comment.