Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge pull request #2095 from ezsystems/ezp24744-increase_password_se…
…curity_6.11

EZP-24744 - Increase password security
  • Loading branch information
andrerom committed Oct 3, 2017
2 parents 3a9edbf + 3135fb1 commit 9a7c988
Show file tree
Hide file tree
Showing 16 changed files with 96 additions and 72 deletions.
1 change: 1 addition & 0 deletions composer.json
Expand Up @@ -29,6 +29,7 @@
"ocramius/proxy-manager": "^1.0|^2.0",
"doctrine/doctrine-bundle": "~1.3",
"liip/imagine-bundle": "~1.0",
"ircmaxell/password-compat": "^1.0",
"oneup/flysystem-bundle": "^1.0",
"friendsofsymfony/http-cache-bundle": "~1.2|^1.3.8",
"sensio/framework-extra-bundle": "~3.0",
Expand Down
4 changes: 2 additions & 2 deletions data/cleandata.sql
Expand Up @@ -1970,8 +1970,8 @@ INSERT INTO `ezurlalias_ml_incr` (`id`) VALUES (35);
INSERT INTO `ezurlalias_ml_incr` (`id`) VALUES (36);
INSERT INTO `ezurlalias_ml_incr` (`id`) VALUES (37);

INSERT INTO `ezuser` (`contentobject_id`, `email`, `login`, `password_hash`, `password_hash_type`) VALUES (10,'nospam@ez.no','anonymous','4e6f6184135228ccd45f8233d72a0363',2);
INSERT INTO `ezuser` (`contentobject_id`, `email`, `login`, `password_hash`, `password_hash_type`) VALUES (14,'nospam@ez.no','admin','c78e3b0f3d9244ed8c6d1c29464bdff9',2);
INSERT INTO `ezuser` (`contentobject_id`, `email`, `login`, `password_hash`, `password_hash_type`) VALUES (10,'nospam@ez.no','anonymous','$2y$10$35gOSQs6JK4u4whyERaeUuVeQBi2TUBIZIfP7HEj7sfz.MxvTuOeC',7);
INSERT INTO `ezuser` (`contentobject_id`, `email`, `login`, `password_hash`, `password_hash_type`) VALUES (14,'nospam@ez.no','admin','$2y$10$FDn9NPwzhq85cLLxfD5Wu.L3SL3Z/LNCvhkltJUV0wcJj7ciJg2oy',7);

INSERT INTO `ezuser_role` (`contentobject_id`, `id`, `limit_identifier`, `limit_value`, `role_id`) VALUES (11,28,'','',1);
INSERT INTO `ezuser_role` (`contentobject_id`, `id`, `limit_identifier`, `limit_value`, `role_id`) VALUES (42,31,'','',1);
Expand Down
2 changes: 1 addition & 1 deletion data/mysql/schema.sql
Expand Up @@ -2222,7 +2222,7 @@ CREATE TABLE `ezuser` (
`contentobject_id` int(11) NOT NULL DEFAULT '0',
`email` varchar(150) NOT NULL DEFAULT '',
`login` varchar(150) NOT NULL DEFAULT '',
`password_hash` varchar(50) DEFAULT NULL,
`password_hash` varchar(255) DEFAULT NULL,
`password_hash_type` int(11) NOT NULL DEFAULT '1',
PRIMARY KEY (`contentobject_id`),
UNIQUE KEY `ezuser_login` (`login`)
Expand Down
18 changes: 18 additions & 0 deletions doc/bc/changes-6.12.md
@@ -0,0 +1,18 @@
# Backwards compatibility changes

Changes affecting version compatibility with former or future versions.

## Changes

- Increase password security
EZP-24744 - Increase password security introduced two new user password hash types, `PASSWORD_HASH_BCRYPT` and
`PASSWORD_HASH_PHP_DEFAULT`. Either one allows the password to be stored with encryption instead of the previous
default, which uses MD5 hashing. `PASSWORD_HASH_BCRYPT` uses Blowfish (bcrypt). `PASSWORD_HASH_PHP_DEFAULT` is
the new default setting, this uses whichever method PHP considers appropriate. Currently this is bcrypt, but
this may change over time.
Caution - Using either of these new types requires that the length of the `password_hash` column of the `ezuser`
database table is increased from the current value of 50 to 255, see the updated database schema.

## Deprecations

## Removed features
Expand Up @@ -9,6 +9,7 @@
namespace eZ\Publish\API\Repository\Tests\FieldType;

use eZ\Publish\Core\FieldType\User\Value as UserValue;
use eZ\Publish\Core\Repository\Values\User\User;
use eZ\Publish\API\Repository\Values\Content\Field;

/**
Expand Down Expand Up @@ -132,8 +133,7 @@ public function assertFieldDataLoadedCorrect(Field $field)
'hasStoredLogin' => true,
'login' => 'hans',
'email' => 'hans@example.com',
'passwordHash' => '680869a9873105e365d39a6d14e68e46',
'passwordHashType' => 2,
'passwordHashType' => User::PASSWORD_HASH_PHP_DEFAULT,
'enabled' => true,
);

Expand Down
33 changes: 0 additions & 33 deletions eZ/Publish/API/Repository/Tests/UserServiceTest.php
Expand Up @@ -955,17 +955,11 @@ public function testCreateUserSetsExpectedProperties(User $user)
array(
'login' => 'user',
'email' => 'user@example.com',
'passwordHash' => $this->createHash(
'user',
'secret',
$user->hashAlgorithm
),
'mainLanguageCode' => 'eng-US',
),
array(
'login' => $user->login,
'email' => $user->email,
'passwordHash' => $user->passwordHash,
'mainLanguageCode' => $user->contentInfo->mainLanguageCode,
)
);
Expand Down Expand Up @@ -1593,18 +1587,12 @@ public function testUpdateUserUpdatesExpectedProperties(User $user)
array(
'login' => 'user',
'email' => 'user@example.com',
'passwordHash' => $this->createHash(
'user',
'my-new-password',
$user->hashAlgorithm
),
'maxLogin' => 42,
'enabled' => false,
),
array(
'login' => $user->login,
'email' => $user->email,
'passwordHash' => $user->passwordHash,
'maxLogin' => $user->maxLogin,
'enabled' => $user->enabled,
)
Expand Down Expand Up @@ -2490,25 +2478,4 @@ private function createMultiLanguageUser($userGroupId = 13)
// Create a new user
return $userService->createUser($userCreateStruct, [$group]);
}

private function createHash($login, $password, $type)
{
switch ($type) {
case 2:
/* PASSWORD_HASH_MD5_USER */
return md5("{$login}\n{$password}");

case 3:
/* PASSWORD_HASH_MD5_SITE */
$site = null;

return md5("{$login}\n{$password}\n{$site}");

case 5:
/* PASSWORD_HASH_PLAINTEXT */
return $password;
}
/* PASSWORD_HASH_MD5_PASSWORD (1) */
return md5($password);
}
}
Expand Up @@ -9,6 +9,7 @@
namespace eZ\Publish\Core\FieldType\Tests\Integration\User\UserStorage;

use eZ\Publish\Core\FieldType\Tests\Integration\BaseCoreFieldTypeIntegrationTest;
use eZ\Publish\Core\Repository\Values\User\User;

/**
* User Field Type external storage gateway tests.
Expand All @@ -31,8 +32,8 @@ public function providerForGetFieldData()
'contentId' => 10,
'login' => 'anonymous',
'email' => 'nospam@ez.no',
'passwordHash' => '4e6f6184135228ccd45f8233d72a0363',
'passwordHashType' => '2',
'passwordHash' => '$2y$10$35gOSQs6JK4u4whyERaeUuVeQBi2TUBIZIfP7HEj7sfz.MxvTuOeC',
'passwordHashType' => User::PASSWORD_HASH_PHP_DEFAULT,
'enabled' => true,
'maxLogin' => 1000,
],
Expand All @@ -41,8 +42,8 @@ public function providerForGetFieldData()
'contentId' => 14,
'login' => 'admin',
'email' => 'spam@ez.no',
'passwordHash' => 'c78e3b0f3d9244ed8c6d1c29464bdff9',
'passwordHashType' => '2',
'passwordHash' => '$2y$10$FDn9NPwzhq85cLLxfD5Wu.L3SL3Z/LNCvhkltJUV0wcJj7ciJg2oy',
'passwordHashType' => User::PASSWORD_HASH_PHP_DEFAULT,
'enabled' => true,
'maxLogin' => 10,
],
Expand Down
Expand Up @@ -482,7 +482,7 @@ CREATE TABLE ezuser (
contentobject_id int(11) NOT NULL DEFAULT 0,
email varchar(150) NOT NULL DEFAULT '',
login varchar(150) NOT NULL DEFAULT '',
password_hash varchar(50) DEFAULT NULL,
password_hash varchar(255) DEFAULT NULL,
password_hash_type int(11) NOT NULL DEFAULT 1,
PRIMARY KEY (contentobject_id),
UNIQUE KEY `ezuser_login` (`login`)
Expand Down
Expand Up @@ -512,7 +512,7 @@ CREATE TABLE ezuser (
contentobject_id integer DEFAULT 0 NOT NULL,
email character varying(150) DEFAULT ''::character varying NOT NULL,
login character varying(150) DEFAULT ''::character varying NOT NULL,
password_hash character varying(50),
password_hash character varying(255),
password_hash_type integer DEFAULT 1 NOT NULL
);

Expand Down
Expand Up @@ -432,7 +432,7 @@ CREATE TABLE ezuser (
contentobject_id integer NOT NULL DEFAULT 0,
email text(150) NOT NULL,
login text(150) NOT NULL,
password_hash text(50),
password_hash text(255),
password_hash_type integer NOT NULL DEFAULT 1,
PRIMARY KEY (contentobject_id)
);
Expand Down
2 changes: 1 addition & 1 deletion eZ/Publish/Core/Persistence/Legacy/User/Mapper.php
Expand Up @@ -33,7 +33,7 @@ public function mapUser(array $data)
$user->login = $data['login'];
$user->email = $data['email'];
$user->passwordHash = $data['password_hash'];
$user->hashAlgorithm = $data['password_hash_type'];
$user->hashAlgorithm = (int)$data['password_hash_type'];
$user->isEnabled = (bool)$data['is_enabled'];
$user->maxLogin = $data['max_login'];

Expand Down
Expand Up @@ -11469,15 +11469,15 @@
'contentobject_id' => '10',
'email' => 'nospam@ez.no',
'login' => 'anonymous',
'password_hash' => '4e6f6184135228ccd45f8233d72a0363',
'password_hash_type' => '2',
'password_hash' => '$2y$10$35gOSQs6JK4u4whyERaeUuVeQBi2TUBIZIfP7HEj7sfz.MxvTuOeC',
'password_hash_type' => '7',
),
1 => array(
'contentobject_id' => '14',
'email' => 'spam@ez.no',
'login' => 'admin',
'password_hash' => 'c78e3b0f3d9244ed8c6d1c29464bdff9',
'password_hash_type' => '2',
'password_hash' => '$2y$10$FDn9NPwzhq85cLLxfD5Wu.L3SL3Z/LNCvhkltJUV0wcJj7ciJg2oy',
'password_hash_type' => '7',
),
),
'ezuser_accountkey' => array(
Expand Down
Expand Up @@ -13089,15 +13089,15 @@
'contentobject_id' => '10',
'email' => 'nospam@ez.no',
'login' => 'anonymous',
'password_hash' => '4e6f6184135228ccd45f8233d72a0363',
'password_hash_type' => '2',
'password_hash' => '$2y$10$35gOSQs6JK4u4whyERaeUuVeQBi2TUBIZIfP7HEj7sfz.MxvTuOeC',
'password_hash_type' => '7',
),
1 => array(
'contentobject_id' => '14',
'email' => 'spam@ez.no',
'login' => 'admin',
'password_hash' => 'c78e3b0f3d9244ed8c6d1c29464bdff9',
'password_hash_type' => '2',
'password_hash' => '$2y$10$FDn9NPwzhq85cLLxfD5Wu.L3SL3Z/LNCvhkltJUV0wcJj7ciJg2oy',
'password_hash_type' => '7',
),
),
'ezuser_accountkey' => array(
Expand Down
51 changes: 39 additions & 12 deletions eZ/Publish/Core/Repository/UserService.php
Expand Up @@ -77,7 +77,7 @@ public function __construct(RepositoryInterface $repository, Handler $userHandle
'defaultUserPlacement' => 12,
'userClassID' => 4, // @todo Rename this settings to swap out "Class" for "Type"
'userGroupClassID' => 3,
'hashType' => User::PASSWORD_HASH_MD5_USER,
'hashType' => User::PASSWORD_HASH_PHP_DEFAULT,
'siteName' => 'ez.no',
);
}
Expand Down Expand Up @@ -565,18 +565,8 @@ public function loadUserByCredentials($login, $password, array $prioritizedLangu
throw new InvalidArgumentValue('password', $password);
}

// Randomize login time to protect against timing attacks
usleep(mt_rand(0, 30000));

$spiUser = $this->userHandler->loadByLogin($login);
$passwordHash = $this->createPasswordHash(
$login,
$password,
$this->settings['siteName'],
$spiUser->hashAlgorithm
);

if ($spiUser->passwordHash !== $passwordHash) {
if (!$this->verifyPassword($login, $password, $spiUser)) {
throw new NotFoundException('user', $login);
}

Expand Down Expand Up @@ -1120,6 +1110,37 @@ protected function buildDomainUserObject(
);
}

/**
* Verifies if the provided login and password are valid.
*
* @param string $login User login
* @param string $password User password
* @param \eZ\Publish\SPI\Persistence\User $spiUser Loaded user handler
*
* @return bool return true if the login and password are sucessfully
* validate and false, if not.
*/
protected function verifyPassword($login, $password, $spiUser)
{
// In case of bcrypt let php's password functionality do it's magic
if ($spiUser->hashAlgorithm === User::PASSWORD_HASH_BCRYPT ||
$spiUser->hashAlgorithm === User::PASSWORD_HASH_PHP_DEFAULT) {
return password_verify($password, $spiUser->passwordHash);
}

// Randomize login time to protect against timing attacks
usleep(mt_rand(0, 30000));

$passwordHash = $this->createPasswordHash(
$login,
$password,
$this->settings['siteName'],
$spiUser->hashAlgorithm
);

return $passwordHash === $spiUser->passwordHash;
}

/**
* Returns password hash based on user data and site settings.
*
Expand All @@ -1145,6 +1166,12 @@ protected function createPasswordHash($login, $password, $site, $type)
case User::PASSWORD_HASH_PLAINTEXT:
return $password;

case User::PASSWORD_HASH_BCRYPT:
return password_hash($password, PASSWORD_BCRYPT);

case User::PASSWORD_HASH_PHP_DEFAULT:
return password_hash($password, PASSWORD_DEFAULT);

default:
return md5($password);
}
Expand Down
10 changes: 10 additions & 0 deletions eZ/Publish/Core/Repository/Values/User/User.php
Expand Up @@ -37,6 +37,16 @@ class User extends APIUser
*/
const PASSWORD_HASH_PLAINTEXT = 5;

/**
* @var int Passwords in bcrypt
*/
const PASSWORD_HASH_BCRYPT = 6;

/**
* @var int Passwords hashed by PHPs default algorithm, which may change over time
*/
const PASSWORD_HASH_PHP_DEFAULT = 7;

/**
* Internal content representation.
*
Expand Down
12 changes: 6 additions & 6 deletions eZ/Publish/Core/Search/Legacy/Tests/_fixtures/full_dump.php
Expand Up @@ -47667,24 +47667,24 @@
'contentobject_id' => '10',
'email' => 'nospam@ez.no',
'login' => 'anonymous',
'password_hash' => '4e6f6184135228ccd45f8233d72a0363',
'password_hash_type' => '2',
'password_hash' => '$2y$10$35gOSQs6JK4u4whyERaeUuVeQBi2TUBIZIfP7HEj7sfz.MxvTuOeC',
'password_hash_type' => '7',
),
1 =>
array (
'contentobject_id' => '14',
'email' => 'kn@ez.no',
'login' => 'admin',
'password_hash' => 'c78e3b0f3d9244ed8c6d1c29464bdff9',
'password_hash_type' => '2',
'password_hash' => '$2y$10$FDn9NPwzhq85cLLxfD5Wu.L3SL3Z/LNCvhkltJUV0wcJj7ciJg2oy',
'password_hash_type' => '7',
),
2 =>
array (
'contentobject_id' => '226',
'email' => 'pa@ez.no',
'login' => 'a_member',
'password_hash' => 'c78e3b0f3d9244ed8c6d1c29464bdff9',
'password_hash_type' => '2',
'password_hash' => '$2y$10$FDn9NPwzhq85cLLxfD5Wu.L3SL3Z/LNCvhkltJUV0wcJj7ciJg2oy',
'password_hash_type' => '7',
),
),
'ezuser_role' =>
Expand Down

0 comments on commit 9a7c988

Please sign in to comment.