From 6ba00e829a9fb360dfe5cb0bc3d4544016c82357 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Thu, 30 Nov 2017 15:50:36 +1300 Subject: [PATCH 1/5] [ss-2017-009] Prevent disclosure of sensitive information via LoginAttempt --- security/LoginAttempt.php | 48 ++++++++++++++-------- security/Member.php | 7 ++-- tests/security/MemberAuthenticatorTest.php | 3 +- tests/security/SecurityTest.php | 31 ++++++-------- 4 files changed, 51 insertions(+), 38 deletions(-) diff --git a/security/LoginAttempt.php b/security/LoginAttempt.php index 4da1e2440..ec5c19ca2 100644 --- a/security/LoginAttempt.php +++ b/security/LoginAttempt.php @@ -12,18 +12,20 @@ * @package framework * @subpackage security * - * @property string Email Email address used for login attempt - * @property string Status Status of the login attempt, either 'Success' or 'Failure' - * @property string IP IP address of user attempting to login + * @property string $Email Email address used for login attempt. @deprecated 3.0...5.0 + * @property string $EmailHashed sha1 hashed Email address used for login attempt + * @property string $Status Status of the login attempt, either 'Success' or 'Failure' + * @property string $IP IP address of user attempting to login * - * @property int MemberID ID of the Member, only if Member with Email exists + * @property int $MemberID ID of the Member, only if Member with Email exists * * @method Member Member() Member object of the user trying to log in, only if Member with Email exists */ class LoginAttempt extends DataObject { private static $db = array( - 'Email' => 'Varchar(255)', + 'Email' => 'Varchar(255)', // Remove in 5.0 + 'EmailHashed' => 'Varchar(255)', 'Status' => "Enum('Success,Failure')", 'IP' => 'Varchar(255)', ); @@ -32,24 +34,38 @@ class LoginAttempt extends DataObject { 'Member' => 'Member', // only linked if the member actually exists ); - private static $has_many = array(); - - private static $many_many = array(); - - private static $belongs_many_many = array(); - - /** - * - * @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields - * - */ public function fieldLabels($includerelations = true) { $labels = parent::fieldLabels($includerelations); $labels['Email'] = _t('LoginAttempt.Email', 'Email Address'); + $labels['EmailHashed'] = _t('LoginAttempt.EmailHashed', 'Email Address (hashed)'); $labels['Status'] = _t('LoginAttempt.Status', 'Status'); $labels['IP'] = _t('LoginAttempt.IP', 'IP Address'); return $labels; } + /** + * Set email used for this attempt + * + * @param string $email + * @return $this + */ + public function setEmail($email) { + // Store hashed email only + $this->EmailHashed = sha1($email); + return $this; + } + + /** + * Get all login attempts for the given email address + * + * @param string $email + * @return DataList + */ + public static function getByEmail($email) { + return static::get()->filterAny(array( + 'Email' => $email, + 'EmailHashed' => sha1($email), + )); + } } diff --git a/security/Member.php b/security/Member.php index 39573a4b9..a83a9d20e 100644 --- a/security/Member.php +++ b/security/Member.php @@ -407,9 +407,10 @@ class Member extends DataObject implements TemplateGlobalProvider { return false; } - $attempts = LoginAttempt::get()->filter($filter = array( - 'Email' => $this->{static::config()->unique_identifier_field}, - ))->sort('Created', 'DESC')->limit($this->config()->lock_out_after_incorrect_logins); + $email = $this->{static::config()->unique_identifier_field}; + $attempts = LoginAttempt::getByEmail($email) + ->sort('Created', 'DESC') + ->limit($this->config()->lock_out_after_incorrect_logins); if ($attempts->count() < $this->config()->lock_out_after_incorrect_logins) { return false; diff --git a/tests/security/MemberAuthenticatorTest.php b/tests/security/MemberAuthenticatorTest.php index f3e4598ca..bf6dd9942 100644 --- a/tests/security/MemberAuthenticatorTest.php +++ b/tests/security/MemberAuthenticatorTest.php @@ -196,7 +196,8 @@ class MemberAuthenticatorTest extends SapphireTest { $this->assertNull($response); $this->assertCount(1, LoginAttempt::get()); $attempt = LoginAttempt::get()->first(); - $this->assertEquals($email, $attempt->Email); + $this->assertEmpty($attempt->Email); // Doesn't store potentially sensitive data + $this->assertEquals(sha1($email), $attempt->EmailHashed); $this->assertEquals('Failure', $attempt->Status); } diff --git a/tests/security/SecurityTest.php b/tests/security/SecurityTest.php index 7c3283525..e123b8212 100644 --- a/tests/security/SecurityTest.php +++ b/tests/security/SecurityTest.php @@ -507,25 +507,21 @@ class SecurityTest extends FunctionalTest { /* UNSUCCESSFUL ATTEMPTS WITH WRONG PASSWORD FOR EXISTING USER ARE LOGGED */ $this->doTestLoginForm('testuser@example.com', 'wrongpassword'); - $attempt = DataObject::get_one('LoginAttempt', array( - '"LoginAttempt"."Email"' => 'testuser@example.com' - )); - $this->assertTrue(is_object($attempt)); - $member = DataObject::get_one('Member', array( - '"Member"."Email"' => 'testuser@example.com' - )); + $attempt = LoginAttempt::getByEmail('testuser@example.com')->first(); + $this->assertInstanceOf('LoginAttempt', $attempt); + $member = Member::get()->filter('Email', 'testuser@example.com')->first(); $this->assertEquals($attempt->Status, 'Failure'); - $this->assertEquals($attempt->Email, 'testuser@example.com'); + $this->assertEmpty($attempt->Email); // Doesn't store potentially sensitive data + $this->assertEquals($attempt->EmailHashed, sha1('testuser@example.com')); $this->assertEquals($attempt->Member(), $member); /* UNSUCCESSFUL ATTEMPTS WITH NONEXISTING USER ARE LOGGED */ $this->doTestLoginForm('wronguser@silverstripe.com', 'wrongpassword'); - $attempt = DataObject::get_one('LoginAttempt', array( - '"LoginAttempt"."Email"' => 'wronguser@silverstripe.com' - )); - $this->assertTrue(is_object($attempt)); + $attempt = LoginAttempt::getByEmail('wronguser@silverstripe.com')->first(); + $this->assertInstanceOf('LoginAttempt', $attempt); $this->assertEquals($attempt->Status, 'Failure'); - $this->assertEquals($attempt->Email, 'wronguser@silverstripe.com'); + $this->assertEmpty($attempt->Email); // Doesn't store potentially sensitive data + $this->assertEquals($attempt->EmailHashed, sha1('wronguser@silverstripe.com')); $this->assertNotNull( $this->loginErrorMessage(), 'An invalid email returns a message.' ); @@ -536,15 +532,14 @@ class SecurityTest extends FunctionalTest { /* SUCCESSFUL ATTEMPTS ARE LOGGED */ $this->doTestLoginForm('testuser@example.com', '1nitialPassword'); - $attempt = DataObject::get_one('LoginAttempt', array( - '"LoginAttempt"."Email"' => 'testuser@example.com' - )); + $attempt = LoginAttempt::getByEmail('testuser@example.com')->first(); $member = DataObject::get_one('Member', array( '"Member"."Email"' => 'testuser@example.com' )); - $this->assertTrue(is_object($attempt)); + $this->assertInstanceOf('LoginAttempt', $attempt); $this->assertEquals($attempt->Status, 'Success'); - $this->assertEquals($attempt->Email, 'testuser@example.com'); + $this->assertEmpty($attempt->Email); // Doesn't store potentially sensitive data + $this->assertEquals($attempt->EmailHashed, sha1('testuser@example.com')); $this->assertEquals($attempt->Member(), $member); } From 22ccf3e2f9092f51e7f7288ce108598c6f17b49c Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Wed, 29 Nov 2017 15:27:36 +1300 Subject: [PATCH 2/5] [ss-2017-007] Ensure xls formulae are safely sanitised on output CSVParser now strips leading tabs on cells --- dev/CSVParser.php | 4 ++- forms/gridfield/GridFieldExportButton.php | 24 +++++++++++++--- tests/dev/CSVParserTest.php | 28 +++++++++++-------- tests/dev/CsvBulkLoaderTest.php | 6 ++-- .../CsvBulkLoaderTest_PlayersWithHeader.csv | 1 + .../gridfield/GridFieldExportButtonTest.php | 16 +++++++++++ 6 files changed, 59 insertions(+), 20 deletions(-) diff --git a/dev/CSVParser.php b/dev/CSVParser.php index 158d9d863..4c1fdf3ba 100644 --- a/dev/CSVParser.php +++ b/dev/CSVParser.php @@ -247,7 +247,9 @@ class CSVParser extends Object implements Iterator { array($this->enclosure, $this->delimiter), $value ); - + // Trim leading tab + // [SS-2017-007] Ensure all cells with leading [@=+] have a leading tab + $value = ltrim($value, "\t"); if(array_key_exists($i, $this->headerRow)) { if($this->headerRow[$i]) { $row[$this->headerRow[$i]] = $value; diff --git a/forms/gridfield/GridFieldExportButton.php b/forms/gridfield/GridFieldExportButton.php index 30df9f01f..9c865b6a1 100644 --- a/forms/gridfield/GridFieldExportButton.php +++ b/forms/gridfield/GridFieldExportButton.php @@ -30,6 +30,15 @@ class GridFieldExportButton implements GridField_HTMLProvider, GridField_ActionP */ protected $targetFragment; + /** + * Set to true to disable XLS sanitisation + * [SS-2017-007] Ensure all cells with leading [@=+] have a leading tab + * + * @config + * @var bool + */ + private static $xls_export_disabled = false; + /** * @param string $targetFragment The HTML fragment to write the button into * @param array $exportColumns The columns to include in the export @@ -91,12 +100,12 @@ class GridFieldExportButton implements GridField_HTMLProvider, GridField_ActionP return SS_HTTPRequest::send_file($fileData, $fileName, 'text/csv'); } } - + /** * Return the columns to export - * - * @param GridField $gridField - * + * + * @param GridField $gridField + * * @return array */ protected function getExportColumnsForGridField(GridField $gridField) { @@ -174,6 +183,13 @@ class GridFieldExportButton implements GridField_HTMLProvider, GridField_ActionP } $value = str_replace(array("\r", "\n"), "\n", $value); + + // [SS-2017-007] Sanitise XLS executable column values with a leading tab + if (!Config::inst()->get(get_class($this), 'xls_export_disabled') + && preg_match('/^[-@=+].*/', $value) + ) { + $value = "\t" . $value; + } $columnData[] = '"' . str_replace('"', '""', $value) . '"'; } diff --git a/tests/dev/CSVParserTest.php b/tests/dev/CSVParserTest.php index c895b2f8b..512717521 100644 --- a/tests/dev/CSVParserTest.php +++ b/tests/dev/CSVParserTest.php @@ -21,16 +21,18 @@ class CSVParserTest extends SapphireTest { $registered[] = $record['IsRegistered']; } - $this->assertEquals(array('John','Jane','Jamie','Järg'), $firstNames); + $this->assertEquals(array('John','Jane','Jamie','Järg','Jacob'), $firstNames); $this->assertEquals(array( "He's a good guy", "She is awesome." . PHP_EOL . "So awesome that she gets multiple rows and \"escaped\" strings in her biography", "Pretty old, with an escaped comma", - "Unicode FTW"), $biographies); - $this->assertEquals(array("31/01/1988","31/01/1982","31/01/1882","31/06/1982"), $birthdays); - $this->assertEquals(array('1', '0', '1', '1'), $registered); + "Unicode FTW", + "Likes leading tabs in his biography", + ), $biographies); + $this->assertEquals(array("31/01/1988","31/01/1982","31/01/1882","31/06/1982","31/4/2000"), $birthdays); + $this->assertEquals(array('1', '0', '1', '1', '0'), $registered); } public function testParsingWithHeadersAndColumnMap() { @@ -54,15 +56,16 @@ class CSVParserTest extends SapphireTest { $registered[] = $record['IsRegistered']; } - $this->assertEquals(array('John','Jane','Jamie','Järg'), $firstNames); + $this->assertEquals(array('John','Jane','Jamie','Järg','Jacob'), $firstNames); $this->assertEquals(array( "He's a good guy", "She is awesome." . PHP_EOL . "So awesome that she gets multiple rows and \"escaped\" strings in her biography", "Pretty old, with an escaped comma", - "Unicode FTW"), $biographies); - $this->assertEquals(array("31/01/1988","31/01/1982","31/01/1882","31/06/1982"), $birthdays); - $this->assertEquals(array('1', '0', '1', '1'), $registered); + "Unicode FTW", + "Likes leading tabs in his biography"), $biographies); + $this->assertEquals(array("31/01/1988","31/01/1982","31/01/1882","31/06/1982","31/4/2000"), $birthdays); + $this->assertEquals(array('1', '0', '1', '1', '0'), $registered); } public function testParsingWithExplicitHeaderRow() { @@ -82,15 +85,16 @@ class CSVParserTest extends SapphireTest { } /* And the first row will be returned in the data */ - $this->assertEquals(array('FirstName','John','Jane','Jamie','Järg'), $firstNames); + $this->assertEquals(array('FirstName','John','Jane','Jamie','Järg','Jacob'), $firstNames); $this->assertEquals(array( 'Biography', "He's a good guy", "She is awesome." . PHP_EOL . "So awesome that she gets multiple rows and \"escaped\" strings in her biography", "Pretty old, with an escaped comma", - "Unicode FTW"), $biographies); - $this->assertEquals(array("Birthday","31/01/1988","31/01/1982","31/01/1882","31/06/1982"), $birthdays); - $this->assertEquals(array('IsRegistered', '1', '0', '1', '1'), $registered); + "Unicode FTW", + "Likes leading tabs in his biography"), $biographies); + $this->assertEquals(array("Birthday","31/01/1988","31/01/1982","31/01/1882","31/06/1982","31/4/2000"), $birthdays); + $this->assertEquals(array('IsRegistered', '1', '0', '1', '1', '0'), $registered); } } diff --git a/tests/dev/CsvBulkLoaderTest.php b/tests/dev/CsvBulkLoaderTest.php index 7b1d15a5b..bb21b98f2 100644 --- a/tests/dev/CsvBulkLoaderTest.php +++ b/tests/dev/CsvBulkLoaderTest.php @@ -27,7 +27,7 @@ class CsvBulkLoaderTest extends SapphireTest { $results = $loader->load($filepath); // Test that right amount of columns was imported - $this->assertEquals(4, $results->Count(), 'Test correct count of imported data'); + $this->assertEquals(5, $results->Count(), 'Test correct count of imported data'); // Test that columns were correctly imported $obj = DataObject::get_one("CsvBulkLoaderTest_Player", array( @@ -49,14 +49,14 @@ class CsvBulkLoaderTest extends SapphireTest { $filepath = $this->getCurrentAbsolutePath() . '/CsvBulkLoaderTest_PlayersWithHeader.csv'; $loader->deleteExistingRecords = true; $results1 = $loader->load($filepath); - $this->assertEquals(4, $results1->Count(), 'Test correct count of imported data on first load'); + $this->assertEquals(5, $results1->Count(), 'Test correct count of imported data on first load'); //delete existing data before doing second CSV import $results2 = $loader->load($filepath, '512MB', true); //get all instances of the loaded DataObject from the database and count them $resultDataObject = DataObject::get('CsvBulkLoaderTest_Player'); - $this->assertEquals(4, $resultDataObject->Count(), + $this->assertEquals(5, $resultDataObject->Count(), 'Test if existing data is deleted before new data is added'); } diff --git a/tests/dev/CsvBulkLoaderTest_PlayersWithHeader.csv b/tests/dev/CsvBulkLoaderTest_PlayersWithHeader.csv index 2536266fc..f8e101f08 100644 --- a/tests/dev/CsvBulkLoaderTest_PlayersWithHeader.csv +++ b/tests/dev/CsvBulkLoaderTest_PlayersWithHeader.csv @@ -4,3 +4,4 @@ So awesome that she gets multiple rows and \"escaped\" strings in her biography","31/01/1982","0" "Jamie","Pretty old\, with an escaped comma","31/01/1882","1" "Järg","Unicode FTW","31/06/1982","1" +"Jacob"," Likes leading tabs in his biography","31/4/2000","0" diff --git a/tests/forms/gridfield/GridFieldExportButtonTest.php b/tests/forms/gridfield/GridFieldExportButtonTest.php index 42ef28e22..516a2bc13 100644 --- a/tests/forms/gridfield/GridFieldExportButtonTest.php +++ b/tests/forms/gridfield/GridFieldExportButtonTest.php @@ -53,6 +53,22 @@ class GridFieldExportButtonTest extends SapphireTest { ); } + public function testXLSSanitisation() { + // Create risky object + $object = new GridFieldExportButtonTest_Team(); + $object->Name = '=SUM(1, 2)'; + $object->write(); + + // Export + $button = new GridFieldExportButton(); + $button->setExportColumns(array('Name' => 'My Name')); + + $this->assertEquals( + "\"My Name\"\n\"\t=SUM(1, 2)\"\n\"Test\"\n\"Test2\"\n", + $button->generateExportFileData($this->gridField) + ); + } + public function testGenerateFileDataAnonymousFunctionField() { $button = new GridFieldExportButton(); $button->setExportColumns(array( From 25e276cf3784dc1ab3a38252192ccd61f9d63121 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Thu, 30 Nov 2017 14:48:36 +1300 Subject: [PATCH 3/5] [ss-2017-006] Fix user agent invalidation on session startup --- control/Session.php | 40 +++++++++++++++++++++++++++-------- tests/control/SessionTest.php | 16 ++++++++++++-- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/control/Session.php b/control/Session.php index e40018a4f..a4ad854f0 100644 --- a/control/Session.php +++ b/control/Session.php @@ -145,15 +145,7 @@ class Session { if($data instanceof Session) $data = $data->inst_getAll(); $this->data = $data; - - if (isset($this->data['HTTP_USER_AGENT'])) { - if ($this->data['HTTP_USER_AGENT'] != $this->userAgent()) { - // Funny business detected! - $this->inst_clearAll(); - $this->inst_destroy(); - $this->inst_start(); - } - } + $this->expireIfInvalid(); } /** @@ -392,6 +384,9 @@ class Session { $this->data = isset($_SESSION) ? $_SESSION : array(); } + // Ensure session is validated on start + $this->expireIfInvalid(); + // Modify the timeout behaviour so it's the *inactive* time before the session expires. // By default it's the total session lifetime if($timeout && !headers_sent()) { @@ -631,4 +626,31 @@ class Session { Deprecation::notice('4.0', 'Use the "Session.timeout" config setting instead'); return Config::inst()->get('Session', 'timeout'); } + + /** + * Validate the user agent against the current data, resetting the + * current session if a mismatch is detected. + * + * @deprecated 3.0..4.0 Removed in 4.0 + * @return bool If user agent has been set against this session, returns + * the valid state of this session as either true or false. If the agent + * isn't set it is assumed valid and returns true. + */ + private function expireIfInvalid() { + // If not set, indeterminable; Assume true as safe default + if (!isset($this->data['HTTP_USER_AGENT'])) { + return true; + } + + // Agents match, deterministically true + if ($this->data['HTTP_USER_AGENT'] === $this->userAgent()) { + return true; + } + + // Funny business detected! + $this->inst_clearAll(); + $this->inst_destroy(); + $this->inst_start(); + return false; + } } diff --git a/tests/control/SessionTest.php b/tests/control/SessionTest.php index 1b5c5ee45..3b54f6200 100644 --- a/tests/control/SessionTest.php +++ b/tests/control/SessionTest.php @@ -97,15 +97,27 @@ class SessionTest extends SapphireTest { $_SERVER['HTTP_USER_AGENT'] = 'Test Agent'; // Generate our session + /** @var Session $s */ $s = Injector::inst()->create('Session', array()); $s->inst_set('val', 123); $s->inst_finalize(); + $data = $s->inst_getAll(); // Change our UA $_SERVER['HTTP_USER_AGENT'] = 'Fake Agent'; - // Verify the new session reset our values - $s2 = Injector::inst()->create('Session', $s); + // Verify the new session reset our values (passed by constructor) + /** @var Session $s2 */ + $s2 = Injector::inst()->create('Session', $data); $this->assertNotEquals($s2->inst_get('val'), 123); + + // Verify a started session resets our values (initiated by $_SESSION object) + /** @var Session $s3 */ + $s3 = Injector::inst()->create('Session', []); + foreach ($data as $key => $value) { + $s3->inst_set($key, $value); + } + $s3->inst_start(); + $this->assertNotEquals($s3->inst_get('val'), 123); } } From 5f7f1ea150911765d5d5830b3b45591fde351a8d Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Wed, 6 Dec 2017 16:23:43 +1300 Subject: [PATCH 4/5] Added 3.5.6-rc1 changelog --- docs/en/04_Changelogs/rc/3.5.6-rc1.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 docs/en/04_Changelogs/rc/3.5.6-rc1.md diff --git a/docs/en/04_Changelogs/rc/3.5.6-rc1.md b/docs/en/04_Changelogs/rc/3.5.6-rc1.md new file mode 100644 index 000000000..4bb118918 --- /dev/null +++ b/docs/en/04_Changelogs/rc/3.5.6-rc1.md @@ -0,0 +1,25 @@ +# 3.5.6-rc1 + + + +## Change Log + +### Bugfixes + + * 2017-11-30 [84d7afb34]() Use baseDataClass for allVersions as with other methods (Daniel Hensby) + * 2017-11-24 [09a003bc1]() deprecated usage of getMock in unit tests (Daniel Hensby) + * 2017-11-23 [2ad3cc07d]() Update meber passwordencryption to default on password change (Daniel Hensby) + * 2017-11-16 [dda14e895]() HTTP::get_mime_type with uppercase filenames. (Roman Schmid) + * 2017-11-16 [52f0eadd3]() for #7606: Ensure the object we're handling is actually an Image instance before calling methods specific to that class (e.g. in case of using SVG's in <img> tag which may be File instances). (Patrick Nelson) + * 2017-11-15 [ce3fd370f]() ManyMany link table joined with LEFT JOIN (Daniel Hensby) + * 2017-11-09 [1053de7ec]() Don't redirect in force_redirect() in CLI (Damian Mooyman) + * 2017-10-25 [cbac37559]() Helpful warning when phpunit bootstrap appears misconfigured (Daniel Hensby) + * 2017-10-25 [32cef975e]() Use self::inst() for Injector/Config nest methods (Daniel Hensby) + * 2017-10-19 [a73d5b41](https://github.com/silverstripe/silverstripe-cms/commit/a73d5b4177be445128a6fa42e20dd8df13eaf554) revert to this button after archiving (Christopher Joe) + * 2017-10-12 [fd39faee](https://github.com/silverstripe/silverstripe-cms/commit/fd39faeefd5241cf96313e968142183de767c51b) UploadField overwriteWarning isn't working in AssetAdmin (Jason) + * 2017-10-09 [264cec123]() Dont use var_export for cache key generation as it fails on circular references (Daniel Hensby) + * 2017-10-04 [24e190ea](https://github.com/silverstripe/silverstripe-cms/commit/24e190ea8265d16445a3210f7b06de191e474004) TreeDropdownField showing broken page icons (fixes silverstripe/silverstripe-framework#7420) (Loz Calver) + * 2017-09-12 [0aac4ddb](https://github.com/silverstripe/silverstripe-cms/commit/0aac4ddb7ecf0f17eda8add235017c10c9f57255) Default LoginForm generated from default_authenticator (Daniel Hensby) + * 2017-08-13 [2f579b64c]() Files without extensions (folders) do not have a trailing period added (Robbie Averill) + * 2017-07-04 [00f1ad5d6]() Fixes #7116 Improves server requirements docs viz: OpCaches. (Russell Michell) + * 2016-03-20 [805c38f10]() don't try and switch out of context of the tab system (Stevie Mayhew) From d09c2d7b03680f9a3930cd5697196e303a2c8a1a Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Wed, 6 Dec 2017 16:27:12 +1300 Subject: [PATCH 5/5] Added 3.5.6-rc1 changelog --- docs/en/04_Changelogs/rc/3.5.6-rc1.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/en/04_Changelogs/rc/3.5.6-rc1.md b/docs/en/04_Changelogs/rc/3.5.6-rc1.md index 4bb118918..765adb95c 100644 --- a/docs/en/04_Changelogs/rc/3.5.6-rc1.md +++ b/docs/en/04_Changelogs/rc/3.5.6-rc1.md @@ -4,6 +4,12 @@ ## Change Log +### Security + + * 2017-11-30 [6ba00e829]() Prevent disclosure of sensitive information via LoginAttempt (Damian Mooyman) - See [ss-2017-009](http://www.silverstripe.org/download/security-releases/ss-2017-009) + * 2017-11-30 [25e276cf3]() user agent invalidation on session startup (Damian Mooyman) - See [ss-2017-006](http://www.silverstripe.org/download/security-releases/ss-2017-006) + * 2017-11-29 [22ccf3e2f]() Ensure xls formulae are safely sanitised on output (Damian Mooyman) - See [ss-2017-007](http://www.silverstripe.org/download/security-releases/ss-2017-007) + ### Bugfixes * 2017-11-30 [84d7afb34]() Use baseDataClass for allVersions as with other methods (Daniel Hensby)