diff --git a/docs/en/04_Changelogs/beta/3.5.5-beta1.md b/docs/en/04_Changelogs/beta/3.5.5-beta1.md new file mode 100644 index 000000000..f563dcb08 --- /dev/null +++ b/docs/en/04_Changelogs/beta/3.5.5-beta1.md @@ -0,0 +1,27 @@ +# 3.5.5-beta1 + + + +## Change Log + +### Bugfixes + + * 2017-08-28 [7b200a2](https://github.com/silverstripe/silverstripe-framework/commit/7b200a2a642a78bffcf0a2f417a4757fb216ecfb) add combinedFiles to clear logic (Christopher Joe) + * 2017-08-16 [eb80a5f](https://github.com/silverstripe/silverstripe-framework/commit/eb80a5f9e89e69480edc7f1c9c66cc7403f547f1) LastEdited no longer updated on skipped writes (Daniel Hensby) + * 2017-08-14 [b04a1ab](https://github.com/silverstripe/silverstripe-framework/commit/b04a1ab41c4051923e9d9a9af5dedfa5a3ef67d8) Truncate Error Issue when using views in a Unittest. (James Pluck) + * 2017-07-26 [31c5eeb](https://github.com/silverstripe/silverstripe-framework/commit/31c5eebda089867d61546106b36ca20b21a00026) Avoid JS errors for HTMLEditorFields in small holders (Daniel Hensby) + * 2017-07-26 [82c0632](https://github.com/silverstripe/silverstripe-framework/commit/82c0632f46e00a251d287811652429036d200eff) Use Config API for MemberAuthenticator::$migrate_legacy_hashes (fixes #7208) (Loz Calver) + * 2017-07-19 [292aaf6](https://github.com/silverstripe/silverstripe-framework/commit/292aaf65301b2be4bb5e6e1505ccbe98b8ade67f) Cache IDs grouped by site first (Daniel Hensby) + * 2017-07-18 [b77274c](https://github.com/silverstripe/silverstripe-framework/commit/b77274c1a3c3ab8cfa0abf939aa2e4735e534171) Add unique prefix to cache stores to prevent cache leak (Daniel Hensby) + * 2017-07-17 [515a7cb](https://github.com/silverstripe/silverstripe-cms/commit/515a7cb569f0cf90787b44fca8845760b539fabe) Make sure VirtualPage renders correct templates (Daniel Hensby) + * 2017-07-06 [a6db16b](https://github.com/silverstripe/silverstripe-framework/commit/a6db16b2298738e1ef1329329cbef7c6b33f993e) OS X issue with `Convert::html2raw`, `HTMLText::FirstSentence`, `HTMLText::Summary` and `Text::FirstSentence`. (Roman Schmid) + * 2017-06-29 [79a7b10](https://github.com/silverstripe/silverstripe-framework/commit/79a7b1016e6046af4f07fcd8bfb40773d1066b7e) add missing $rootCall param from LeftAndMain (Daniel Hensby) + * 2017-06-20 [e2116a7](https://github.com/silverstripe/silverstripe-framework/commit/e2116a70ef34433bfe712b4164ae416a76d4430d) Text colour in GridField filter headers for dropdown fields (Robbie Averill) + * 2017-06-14 [2afe018](https://github.com/silverstripe/silverstripe-framework/commit/2afe018dc7e380ac84f8e1f7986ce0247e9a254b) Ensure HasManyList foreign ID filter includes table name (fixes #7023) (Loz Calver) + * 2017-06-12 [53c84d9](https://github.com/silverstripe/silverstripe-framework/commit/53c84d93da0f0681fdcb3a061ebe529fd3cd9a9e) changetracker checkbox bugs (Brian Cairns) + * 2017-06-12 [a5c84b1](https://github.com/silverstripe/silverstripe-framework/commit/a5c84b12ab3c0759f696fc48fee3475bab6b3e20) Order of conditionals for getting default admin (Daniel Hensby) + * 2017-06-06 [4ad2cae](https://github.com/silverstripe/silverstripe-framework/commit/4ad2cae8642d21e37b5132e4040ca45d2d66c193) Upload_Validator failed to fetch max size from PHP ini values (fixes #6999) (Loz Calver) + * 2017-06-05 [5f5bfa5](https://github.com/silverstripe/silverstripe-framework/commit/5f5bfa5e7045cc96f89fca417f0a7d99dc662fab) create temp folder if it does not exist (Christopher Joe) + * 2017-06-02 [4b9d5dc](https://github.com/silverstripe/silverstripe-framework/commit/4b9d5dceb892a9c41925d058d953a8849b407276) tinymce image selection issue in newer versions of Chrome (Christopher Joe) + * 2017-05-29 [b436819](https://github.com/silverstripe/silverstripe-framework/commit/b4368196d1bcee9fd1714b044c8ae6580c7941c9) Use plural name for ModelAdmin tab name (Robbie Averill) + * 2017-05-09 [3dd3036](https://github.com/silverstripe/silverstripe-framework/commit/3dd3036792962d5384a72aa0132a64aca7d2ebc2) Ensure GridState_Component is added to GridField config even if we set config with GridField::setConfig (Klemen Dolinsek) diff --git a/docs/en/04_Changelogs/beta/3.5.5-beta2.md b/docs/en/04_Changelogs/beta/3.5.5-beta2.md new file mode 100644 index 000000000..f6d15f25a --- /dev/null +++ b/docs/en/04_Changelogs/beta/3.5.5-beta2.md @@ -0,0 +1,33 @@ +# 3.5.5-beta2 + + + +## Change Log + +### Security + + * 2017-09-04 [f0262a8](https://github.com/silverstripe/silverstripe-framework/commit/f0262a8fd9ab5fb51b178ace3c3487351217f5a0) User enumeration via timing attack mitigated (Daniel Hensby) - See [ss-2017-005](http://www.silverstripe.org/download/security-releases/ss-2017-005) + +### Bugfixes + + * 2017-09-12 [0aac4dd](https://github.com/silverstripe/silverstripe-cms/commit/0aac4ddb7ecf0f17eda8add235017c10c9f57255) Default LoginForm generated from default_authenticator (Daniel Hensby) + * 2017-09-12 [091d99f](https://github.com/silverstripe/silverstripe-framework/commit/091d99f599dcacf6aef2ad1df48311c2399f150c) Authenticators are more resilient to incomplete configuration (Daniel Hensby) + * 2017-08-28 [7b200a2](https://github.com/silverstripe/silverstripe-framework/commit/7b200a2a642a78bffcf0a2f417a4757fb216ecfb) add combinedFiles to clear logic (Christopher Joe) + * 2017-08-16 [eb80a5f](https://github.com/silverstripe/silverstripe-framework/commit/eb80a5f9e89e69480edc7f1c9c66cc7403f547f1) LastEdited no longer updated on skipped writes (Daniel Hensby) + * 2017-08-14 [b04a1ab](https://github.com/silverstripe/silverstripe-framework/commit/b04a1ab41c4051923e9d9a9af5dedfa5a3ef67d8) Truncate Error Issue when using views in a Unittest. (James Pluck) + * 2017-07-26 [31c5eeb](https://github.com/silverstripe/silverstripe-framework/commit/31c5eebda089867d61546106b36ca20b21a00026) Avoid JS errors for HTMLEditorFields in small holders (Daniel Hensby) + * 2017-07-26 [82c0632](https://github.com/silverstripe/silverstripe-framework/commit/82c0632f46e00a251d287811652429036d200eff) Use Config API for MemberAuthenticator::$migrate_legacy_hashes (fixes #7208) (Loz Calver) + * 2017-07-19 [292aaf6](https://github.com/silverstripe/silverstripe-framework/commit/292aaf65301b2be4bb5e6e1505ccbe98b8ade67f) Cache IDs grouped by site first (Daniel Hensby) + * 2017-07-18 [b77274c](https://github.com/silverstripe/silverstripe-framework/commit/b77274c1a3c3ab8cfa0abf939aa2e4735e534171) Add unique prefix to cache stores to prevent cache leak (Daniel Hensby) + * 2017-07-17 [515a7cb](https://github.com/silverstripe/silverstripe-cms/commit/515a7cb569f0cf90787b44fca8845760b539fabe) Make sure VirtualPage renders correct templates (Daniel Hensby) + * 2017-07-06 [a6db16b](https://github.com/silverstripe/silverstripe-framework/commit/a6db16b2298738e1ef1329329cbef7c6b33f993e) OS X issue with `Convert::html2raw`, `HTMLText::FirstSentence`, `HTMLText::Summary` and `Text::FirstSentence`. (Roman Schmid) + * 2017-06-29 [79a7b10](https://github.com/silverstripe/silverstripe-framework/commit/79a7b1016e6046af4f07fcd8bfb40773d1066b7e) add missing $rootCall param from LeftAndMain (Daniel Hensby) + * 2017-06-20 [e2116a7](https://github.com/silverstripe/silverstripe-framework/commit/e2116a70ef34433bfe712b4164ae416a76d4430d) Text colour in GridField filter headers for dropdown fields (Robbie Averill) + * 2017-06-14 [2afe018](https://github.com/silverstripe/silverstripe-framework/commit/2afe018dc7e380ac84f8e1f7986ce0247e9a254b) Ensure HasManyList foreign ID filter includes table name (fixes #7023) (Loz Calver) + * 2017-06-12 [53c84d9](https://github.com/silverstripe/silverstripe-framework/commit/53c84d93da0f0681fdcb3a061ebe529fd3cd9a9e) changetracker checkbox bugs (Brian Cairns) + * 2017-06-12 [a5c84b1](https://github.com/silverstripe/silverstripe-framework/commit/a5c84b12ab3c0759f696fc48fee3475bab6b3e20) Order of conditionals for getting default admin (Daniel Hensby) + * 2017-06-06 [4ad2cae](https://github.com/silverstripe/silverstripe-framework/commit/4ad2cae8642d21e37b5132e4040ca45d2d66c193) Upload_Validator failed to fetch max size from PHP ini values (fixes #6999) (Loz Calver) + * 2017-06-05 [5f5bfa5](https://github.com/silverstripe/silverstripe-framework/commit/5f5bfa5e7045cc96f89fca417f0a7d99dc662fab) create temp folder if it does not exist (Christopher Joe) + * 2017-06-02 [4b9d5dc](https://github.com/silverstripe/silverstripe-framework/commit/4b9d5dceb892a9c41925d058d953a8849b407276) tinymce image selection issue in newer versions of Chrome (Christopher Joe) + * 2017-05-29 [b436819](https://github.com/silverstripe/silverstripe-framework/commit/b4368196d1bcee9fd1714b044c8ae6580c7941c9) Use plural name for ModelAdmin tab name (Robbie Averill) + * 2017-05-09 [3dd3036](https://github.com/silverstripe/silverstripe-framework/commit/3dd3036792962d5384a72aa0132a64aca7d2ebc2) Ensure GridState_Component is added to GridField config even if we set config with GridField::setConfig (Klemen Dolinsek) diff --git a/security/Authenticator.php b/security/Authenticator.php index de3619c82..b1b98f0e3 100644 --- a/security/Authenticator.php +++ b/security/Authenticator.php @@ -102,9 +102,12 @@ abstract class Authenticator extends Object { if(is_subclass_of($authenticator, 'Authenticator') == false) return false; - if(in_array($authenticator, self::$authenticators) == false) { + $authenticators = Config::inst()->get(__CLASS__, 'authenticators'); + if(in_array($authenticator, $authenticators) == false) { if(call_user_func(array($authenticator, 'on_register')) === true) { - array_push(self::$authenticators, $authenticator); + Config::inst()->update(__CLASS__, 'authenticators', array( + $authenticator, + )); } else { return false; } @@ -125,8 +128,11 @@ abstract class Authenticator extends Object { */ public static function unregister_authenticator($authenticator) { if(call_user_func(array($authenticator, 'on_unregister')) === true) { - if(in_array($authenticator, self::$authenticators)) { - unset(self::$authenticators[array_search($authenticator, self::$authenticators)]); + $authenticators = Config::inst()->get(__CLASS__, 'authenticators'); + if(($key = array_search($authenticator, $authenticators)) !== false) { + unset($authenticators[$key]); + Config::inst()->remove(__CLASS__, 'authenticators'); + Config::inst()->update(__CLASS__, 'authenticators', $authenticators); } } } @@ -140,7 +146,7 @@ abstract class Authenticator extends Object { * otherwise. */ public static function is_registered($authenticator) { - return in_array($authenticator, self::$authenticators); + return in_array($authenticator, Config::inst()->get(__CLASS__, 'authenticators')); } @@ -151,13 +157,16 @@ abstract class Authenticator extends Object { * authenticators. */ public static function get_authenticators() { - // put default authenticator first (mainly for tab-order on loginform) - if($key = array_search(self::$default_authenticator,self::$authenticators)) { - unset(self::$authenticators[$key]); - array_unshift(self::$authenticators, self::$default_authenticator); + $authenticators = Config::inst()->get(__CLASS__, 'authenticators'); + $defaultAuthenticator = Config::inst()->get(__CLASS__, 'default_authenticator'); + + // put default authenticator first if it isn't already + if (reset($authenticators) !== $defaultAuthenticator && ($key = array_search($defaultAuthenticator, $authenticators)) !== false) { + unset($authenticators[$key]); + array_unshift($authenticators, $defaultAuthenticator); } - return self::$authenticators; + return $authenticators; } /** @@ -175,7 +184,9 @@ abstract class Authenticator extends Object { * @return string */ public static function get_default_authenticator() { - return self::$default_authenticator; + $authenticators = static::get_authenticators(); + // the first authenticator is the default one + return reset($authenticators); } diff --git a/security/LoginForm.php b/security/LoginForm.php index dbc047ec7..f3f7b6142 100644 --- a/security/LoginForm.php +++ b/security/LoginForm.php @@ -18,9 +18,18 @@ abstract class LoginForm extends Form { * form. * @var string */ - 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 * @@ -44,5 +53,15 @@ abstract class LoginForm extends Form { return $authClass::get_name(); } + public function setAuthenticatorClass($class) + { + $this->authenticator_class = $class; + $authenticatorField = $this->Fields()->dataFieldByName('AuthenticationMethod'); + if ($authenticatorField) { + $authenticatorField->setValue($class); + } + return $this; + } + } diff --git a/security/MemberAuthenticator.php b/security/MemberAuthenticator.php index 29005bb35..11005d930 100644 --- a/security/MemberAuthenticator.php +++ b/security/MemberAuthenticator.php @@ -150,6 +150,10 @@ class MemberAuthenticator extends Authenticator { * @see Security::setDefaultAdmin() */ 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 $member = static::authenticate_member($data, $form, $success); @@ -170,6 +174,11 @@ class MemberAuthenticator extends Authenticator { if($success) Session::clear('BackURL'); + $waitFor = $minExecTime - (microtime(true) - $startTime); + if ($waitFor > 0) { + usleep($waitFor * 1000000); + } + return $success ? $member : null; } @@ -179,14 +188,18 @@ class MemberAuthenticator extends Authenticator { * * @param Controller The parent controller, necessary to create the * appropriate form action tag - * @return Form Returns the login form to use with this authentication + * @return MemberLoginForm Returns the login form to use with this authentication * method */ public static function get_login_form(Controller $controller) { return MemberLoginForm::create($controller, "LoginForm"); } - public static function get_cms_login_form(\Controller $controller) { + /** + * @param Controller $controller + * @return CMSMemberLoginForm + */ + public static function get_cms_login_form(Controller $controller) { return CMSMemberLoginForm::create($controller, "LoginForm"); } diff --git a/security/MemberLoginForm.php b/security/MemberLoginForm.php index dfacee77a..a9146f7f9 100644 --- a/security/MemberLoginForm.php +++ b/security/MemberLoginForm.php @@ -294,6 +294,10 @@ JS; * @param array $data Submitted 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 if(empty($data['Email'])) { $this->sessionMessage( @@ -311,10 +315,8 @@ JS; // Allow vetoing forgot password requests $results = $this->extend('forgotPassword', $member); if($results && is_array($results) && in_array(false, $results, true)) { - return $this->controller->redirect('Security/lostpassword'); - } - - if($member) { + $this->controller->redirect('Security/lostpassword'); + } elseif ($member) { $token = $member->generateAutologinTokenAndStoreHash(); $e = Member_ForgotPasswordEmail::create(); @@ -338,6 +340,10 @@ JS; $this->controller->redirect('Security/lostpassword'); } + $waitFor = $minExecTime - (microtime(true) - $startTime); + if ($waitFor > 0) { + usleep($waitFor * 1000000); + } } } diff --git a/tests/security/MemberAuthenticatorTest.php b/tests/security/MemberAuthenticatorTest.php index 8606e6f58..f3e4598ca 100644 --- a/tests/security/MemberAuthenticatorTest.php +++ b/tests/security/MemberAuthenticatorTest.php @@ -221,4 +221,10 @@ class MemberAuthenticatorTest extends SapphireTest { $this->assertTrue($member->isLockedOut()); $this->assertFalse($member->canLogIn()->valid()); } + + public function testDefaultAuthenticatorWontReturnIfDisabled() + { + Authenticator::unregister('MemberAuthenticator'); + $this->assertNotEquals('MemberAuthenticator', Authenticator::get_default_authenticator()); + } }