Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .tools/visual-tests/visual-record.js
Original file line number Diff line number Diff line change
Expand Up @@ -173,10 +173,10 @@ async function createScreenshots(page, screenshotName) {
await processScreenshot(page, screenshotName.replace('.png', '--dark.png'));
}

async function logIntoBackend(page, username = 'myusername', password = '91dfd9ddb4198affc5c194cd8ce6d338fde470e2') {
async function logIntoBackend(page, username = 'myusername', password = 'mypassword') {
await goToUrlOrThrow(page, START_URL, { waitUntil: 'load' });
await page.type('#rex-id-login-user', username);
await page.type('#rex-id-login-password', password); // sha1('mypassword')
await page.type('#rex-id-login-password', password);
await Promise.all([
page.waitForNavigation({ waitUntil: 'load' }),
page.$eval('#rex-form-login', form => form.submit()),
Expand Down
12 changes: 0 additions & 12 deletions assets/sha1.js

This file was deleted.

5 changes: 1 addition & 4 deletions boot/backend.php
Original file line number Diff line number Diff line change
Expand Up @@ -150,9 +150,7 @@
if (($rexUserLogin || $passkey) && !CsrfToken::factory('backend_login')->isValid()) {
$loginCheck = I18n::msg('csrf_token_invalid');
} else {
// the server side encryption of pw is only required
// when not already encrypted by client using javascript
$login->setLogin($rexUserLogin, $rexUserPsw, Request::post('javascript', 'boolean'));
$login->setLogin($rexUserLogin, $rexUserPsw);
$login->setPasskey('' === $passkey ? null : $passkey);
$login->setStayLoggedIn($rexUserStayLoggedIn);
$loginCheck = $login->checkLogin();
Expand Down Expand Up @@ -234,7 +232,6 @@
Asset::addJsFile(Url::coreAssets('jquery-ui.custom.min.js'), [Asset::JS_IMMUTABLE => true]);
Asset::addJsFile(Url::coreAssets('jquery-pjax.min.js'), [Asset::JS_IMMUTABLE => true]);
Asset::addJsFile(Url::coreAssets('standard.js'), [Asset::JS_IMMUTABLE => true]);
Asset::addJsFile(Url::coreAssets('sha1.js'), [Asset::JS_IMMUTABLE => true]);
Asset::addJsFile(Url::coreAssets('clipboard-copy-element.js'), [Asset::JS_IMMUTABLE => true]);
Asset::addJsFile(Url::coreAssets('js/mediapool.js'), [Asset::JS_IMMUTABLE]);

Expand Down
11 changes: 1 addition & 10 deletions pages/login.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ function disableLogin() {

$content .= '
<fieldset>
<input type="hidden" name="javascript" value="0" id="javascript" />';
';

$formElements = [];

Expand Down Expand Up @@ -132,15 +132,6 @@ function disableLogin() {
<script type="text/javascript" nonce="' . Response::getNonce() . '">
<!--
jQuery(function($) {
$("#rex-form-login")
.submit(function(){
var pwInp = $("#rex-id-login-password");
if(pwInp.val() != "") {
$("#rex-form-login").append(\'<input type="hidden" name="\'+pwInp.attr("name")+\'" value="\'+Sha1.hash(pwInp.val())+\'" />\');
}
});

$("#javascript").val("1");
' . $js . '
});
//-->
Expand Down
6 changes: 3 additions & 3 deletions pages/profile/index.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
use Redaxo\Core\Form\Select\Select;
use Redaxo\Core\Http\Request;
use Redaxo\Core\Http\Response;
use Redaxo\Core\Security\BackendLogin;
use Redaxo\Core\Security\BackendPasswordPolicy;
use Redaxo\Core\Security\CsrfToken;
use Redaxo\Core\Security\Login;
use Redaxo\Core\Security\User;
use Redaxo\Core\Security\WebAuthn;
use Redaxo\Core\Translation\I18n;
Expand Down Expand Up @@ -126,7 +126,7 @@

$verifyLogin = static function () use ($user, $login, $userpsw, $webauthn): bool|string {
if (!$login->getPasskey()) {
if (!$userpsw || !Login::passwordVerify($userpsw, $user->getValue('password'))) {
if (!$userpsw || !BackendLogin::passwordVerify($userpsw, $user->getValue('password'))) {
return I18n::msg('user_psw_verify_error');
}

Expand Down Expand Up @@ -157,7 +157,7 @@
} elseif ($passwordChangeRequired && $userpsw === $userpswNew1) {
$error = I18n::msg('password_not_changed');
} else {
$userpswNew1 = Login::passwordHash($userpswNew1);
$userpswNew1 = BackendLogin::passwordHash($userpswNew1);

$updateuser = Sql::factory();
$updateuser->setTable(Core::getTablePrefix() . 'user');
Expand Down
31 changes: 30 additions & 1 deletion src/Security/BackendLogin.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Redaxo\Core\Security;

use DateTimeImmutable;
use Override;
use Redaxo\Core\Core;
use Redaxo\Core\Database\Sql;
use Redaxo\Core\Exception\LogicException;
Expand All @@ -28,6 +29,8 @@ class BackendLogin extends Login

private const SESSION_PASSWORD_CHANGE_REQUIRED = 'password_change_required';

private static ?string $legacySha1Hash = null;

private string $tableName;
private ?string $passkey = null;
private bool $stayLoggedIn = false;
Expand Down Expand Up @@ -144,7 +147,7 @@ public function checkLogin()
$password = $this->user->getValue('password');
if ($password && $this->userLogin && $this->userPassword && self::passwordNeedsRehash($password)) {
$add .= 'password = ?, ';
$params[] = $password = self::passwordHash($this->userPassword, true);
$params[] = $password = self::passwordHash($this->userPassword);
}
array_push($params, Sql::datetime(), Sql::datetime(), session_id(), $this->getSessionVar(self::SESSION_USER_ID));
$sql->setQuery('UPDATE ' . $this->tableName . ' SET ' . $add . 'login_tries=0, lasttrydate=?, lastlogin=?, session_id=? WHERE id=? LIMIT 1', $params);
Expand Down Expand Up @@ -339,6 +342,32 @@ public static function createUser()
return null;
}

#[Override]
public static function passwordVerify(#[SensitiveParameter] string $password, #[SensitiveParameter] string $hash): bool
{
if (parent::passwordVerify($password, $hash)) {
return true;
}

// Fallback for legacy passwords from REDAXO 5.x which were sha1-prehashed
if (password_verify(sha1($password), $hash)) {
self::$legacySha1Hash = $hash;
return true;
}

return false;
}

#[Override]
public static function passwordNeedsRehash(#[SensitiveParameter] string $hash): bool
{
if ($hash === self::$legacySha1Hash) {
return true;
}

return parent::passwordNeedsRehash($hash);
}

/**
* returns the backends session namespace.
*
Expand Down
3 changes: 1 addition & 2 deletions src/Security/BackendPasswordPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,11 @@ public function check(#[SensitiveParameter] $password, $id = null)
return true;
}

$password = sha1($password);
$previousPasswords = Type::array(json_decode($previousPasswords, true));
$previousPasswords = $this->cleanUpPreviousPasswords($previousPasswords);

foreach ($previousPasswords as $previousPassword) {
if (BackendLogin::passwordVerify($password, $previousPassword[0], true)) {
if (BackendLogin::passwordVerify($password, $previousPassword[0])) {
return I18n::msg('password_already_used');
}
}
Expand Down
33 changes: 6 additions & 27 deletions src/Security/Login.php
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,12 @@ public function setSessionDuration($sessionDuration)
*
* @param string $login
* @param string $password
* @param bool $isPreHashed
* @return void
*/
public function setLogin(#[SensitiveParameter] $login, #[SensitiveParameter] $password, $isPreHashed = false)
public function setLogin(#[SensitiveParameter] $login, #[SensitiveParameter] $password)
{
$this->userLogin = $login;
$this->userPassword = $isPreHashed ? $password : sha1($password);
$this->userPassword = $password;
}

/**
Expand Down Expand Up @@ -283,7 +282,7 @@ public function checkLogin()
$this->user = Sql::factory($this->DB);

$this->user->setQuery($this->loginQuery, [':login' => $this->userLogin]);
if (1 == $this->user->getRows() && self::passwordVerify($this->userPassword, (string) $this->user->getValue($this->passwordColumn), true)) {
if (1 == $this->user->getRows() && static::passwordVerify($this->userPassword, (string) $this->user->getValue($this->passwordColumn))) {
$ok = true;
static::regenerateSessionId();
$this->setSessionVar(self::SESSION_START_TIME, time());
Expand Down Expand Up @@ -595,37 +594,17 @@ public static function getCookieParams()
return $cookieParams;
}

/**
* Verschlüsselt den übergebnen String.
*
* @param string $password
* @param bool $isPreHashed
* @return string Returns the hashed password
*/
public static function passwordHash(#[SensitiveParameter] $password, $isPreHashed = false)
public static function passwordHash(#[SensitiveParameter] string $password): string
{
$password = $isPreHashed ? $password : sha1($password);

return password_hash($password, PASSWORD_DEFAULT);
}

/**
* @param string $password
* @param string $hash
* @param bool $isPreHashed
* @return bool returns TRUE if the password and hash match, or FALSE otherwise
*/
public static function passwordVerify(#[SensitiveParameter] $password, #[SensitiveParameter] $hash, $isPreHashed = false)
public static function passwordVerify(#[SensitiveParameter] string $password, #[SensitiveParameter] string $hash): bool
{
$password = $isPreHashed ? $password : sha1($password);
return password_verify($password, $hash);
}

/**
* @param string $hash
* @return bool returns TRUE if the hash should be rehashed to match the given algo and options, or FALSE otherwise
*/
public static function passwordNeedsRehash(#[SensitiveParameter] $hash)
public static function passwordNeedsRehash(#[SensitiveParameter] string $hash): bool
{
return password_needs_rehash($hash, PASSWORD_DEFAULT);
}
Expand Down
18 changes: 9 additions & 9 deletions tests/Security/BackendLoginTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,14 @@ protected function tearDown(): void
public function testSuccessfullLogin(): void
{
$login = new BackendLogin();
$login->setLogin(self::LOGIN, self::PASSWORD, false);
$login->setLogin(self::LOGIN, self::PASSWORD);
self::assertTrue($login->checkLogin());
}

public function testFailedLogin(): void
{
$login = new BackendLogin();
$login->setLogin(self::LOGIN, 'somethingwhichisnotcorrect', false);
$login->setLogin(self::LOGIN, 'somethingwhichisnotcorrect');
self::assertFalse($login->checkLogin());
}

Expand All @@ -56,10 +56,10 @@ public function testSuccessfullReLogin(): void
{
$login = new BackendLogin();

$login->setLogin(self::LOGIN, 'somethingwhichisnotcorrect', false);
$login->setLogin(self::LOGIN, 'somethingwhichisnotcorrect');
self::assertFalse($login->checkLogin());

$login->setLogin(self::LOGIN, self::PASSWORD, false);
$login->setLogin(self::LOGIN, self::PASSWORD);
self::assertTrue($login->checkLogin());
}

Expand All @@ -70,32 +70,32 @@ public function testSuccessfullReLoginAfterLoginTriesSeconds(): void
$tries = $login->getLoginPolicy()->getMaxTriesUntilDelay();

for ($i = 0; $i < $tries; ++$i) {
$login->setLogin(self::LOGIN, 'somethingwhichisnotcorrect', false);
$login->setLogin(self::LOGIN, 'somethingwhichisnotcorrect');
self::assertFalse($login->checkLogin());
}

// we need to re-create login-objects because the time component is static in their sql queries
$login = new BackendLogin();
$login->setLogin(self::LOGIN, self::PASSWORD, false);
$login->setLogin(self::LOGIN, self::PASSWORD);
self::assertFalse($login->checkLogin(), 'account locked after fast login attempts');

sleep(1);

$login = new BackendLogin();
$login->setLogin(self::LOGIN, self::PASSWORD, false);
$login->setLogin(self::LOGIN, self::PASSWORD);
self::assertFalse($login->checkLogin(), 'even seconds later account is locked');

sleep($login->getLoginPolicy()->getReloginDelay() + 1);

$login = new BackendLogin();
$login->setLogin(self::LOGIN, self::PASSWORD, false);
$login->setLogin(self::LOGIN, self::PASSWORD);
self::assertTrue($login->checkLogin(), 'after waiting the account should be unlocked');
}

public function testLogout(): void
{
$login = new BackendLogin();
$login->setLogin(self::LOGIN, self::PASSWORD, false);
$login->setLogin(self::LOGIN, self::PASSWORD);
self::assertTrue($login->checkLogin());
$login->setLogout(true);
self::assertFalse($login->checkLogin());
Expand Down
Loading