From b80d0e8ce8c41fd1b6736a5966924937b8f99da2 Mon Sep 17 00:00:00 2001 From: David Gnedt Date: Fri, 16 Jun 2017 22:53:17 +0200 Subject: [PATCH 1/3] Implement new password hashing (PBKDF2-SHA1) This change modifies the database and requires a database upgrade from within the lansuite admin area. After the database upgrade, the old MD5 user passwords will be automatically converted to PBKDF2-SHA1 on the next user login. Clan and team passwords will only be upgraded, when new passwords are set. PBKDF2-SHA1 was choosen for compatibility with the ejabberd XMPP server password storage. --- inc/Classes/Auth.php | 11 +- inc/Classes/MasterForm.php | 2 +- inc/Classes/PasswordHash.php | 171 ++++++++++++++++++ modules/clanmgr/Functions/CheckClanPW.php | 4 +- modules/clanmgr/mod_settings/db.xml | 2 +- modules/install/adminaccount.php | 9 +- modules/install/mod_settings/config.xml | 31 ++++ modules/install/wizard.php | 6 +- modules/tournament2/Classes/Team.php | 8 +- modules/tournament2/mod_settings/db.xml | 4 +- .../usrmgr/Functions/CheckClanPWUsrMgr.php | 4 +- modules/usrmgr/Functions/CheckOldPW.php | 4 +- modules/usrmgr/add.php | 2 + modules/usrmgr/mod_settings/db.xml | 2 +- modules/usrmgr/newpwd.php | 6 +- modules/usrmgr/remind.php | 5 +- 16 files changed, 251 insertions(+), 20 deletions(-) create mode 100644 inc/Classes/PasswordHash.php diff --git a/inc/Classes/Auth.php b/inc/Classes/Auth.php index c63247ddb..9f20eaadb 100644 --- a/inc/Classes/Auth.php +++ b/inc/Classes/Auth.php @@ -289,7 +289,7 @@ public function login($email, $password, $show_confirmation = 1) $func->log_event(t('Login fehlgeschlagen. Email (%1) nicht verifiziert', $user['email']), "2", "Authentifikation"); // User login and wrong password? - } elseif ($user["user_login"] and $tmp_login_pass != $user["password"]) { + } elseif ($user["user_login"] and !PasswordHash::verify($password, $user["password"])) { ($cfg["sys_internet"])? $remindtext = t('Hast du dein Passwort vergessen?
Hier kannst du ein neues Passwort generieren.') : $remindtext = t('Solltest du dein Passwort vergessen haben, wende dich bitte an die Organisation.'); $func->information(t('Die von dir eingebenen Login-Daten sind fehlerhaft. Bitte überprüfe deine Eingaben.') . HTML_NEWLINE . HTML_NEWLINE . $remindtext, '', 1); $func->log_event(t('Login für %1 fehlgeschlagen (Passwort-Fehler).', $tmp_login_email), "2", "Authentifikation"); @@ -315,6 +315,15 @@ public function login($email, $password, $show_confirmation = 1) // Everything fine! } else { + if ($user["user_login"] and PasswordHash::needsRehash($user["password"])) { + try { + $db->qry('UPDATE %prefix%user SET password = %string% WHERE userid = %int%', PasswordHash::hash($password), $user["userid"]); + $func->information(t('Es wurde ein Sicherheitsupgrade von deinem Passwort durchgeführt.'), '', 1); + } catch (Exception $e) { + $func->error(t('Sicherheitsupgrade von deinem Passwort ist fehlgeschlagen!')); + } + } + // Set Logonstats $db->qry('UPDATE %prefix%user SET logins = logins + 1, changedate = changedate, lastlogin = NOW() WHERE userid = %int%', $user['userid']); diff --git a/inc/Classes/MasterForm.php b/inc/Classes/MasterForm.php index 3e1f672c3..b91ac62ca 100644 --- a/inc/Classes/MasterForm.php +++ b/inc/Classes/MasterForm.php @@ -1071,7 +1071,7 @@ public function SendForm($BaseURL, $table, $idname = '', $id = 0) // Convert Passwords if ($field['type'] == self::IS_NEW_PASSWORD && $_POST[$field['name']] != '') { $_POST[$field['name'] .'_original'] = $_POST[$field['name']]; - $_POST[$field['name']] = md5($_POST[$field['name']]); + $_POST[$field['name']] = PasswordHash::hash($_POST[$field['name']]); } } } diff --git a/inc/Classes/PasswordHash.php b/inc/Classes/PasswordHash.php new file mode 100644 index 000000000..a4b786502 --- /dev/null +++ b/inc/Classes/PasswordHash.php @@ -0,0 +1,171 @@ + $value) { + if ($key === 'hash' || $key === 'salt') { + continue; + } + + if ($algo_cfg[$key] !== $value) { + return true; + } + } + + return false; + } + + private static function getInfo($hash) + { + if ($hash[0] === '$') { + $parts = explode('$', $hash); + if (count($parts) !== 5) { + throw new Exception('Unexpected hash format'); + } + list($dummy, $algo, $iterations, $b64salt, $b64hash) = $parts; + + if (!is_numeric($iterations) || $iterations < 1) { + throw new Exception('Unexpected iterations value'); + } + $rawsalt = base64_decode($b64salt, true); + $rawhash = base64_decode($b64hash, true); + if ($rawsalt === false || $rawhash === false) { + throw new Exception('Unexpected base64_decode error'); + } + + return array('algo' => $algo, 'iterations' => $iterations, 'hash' => $rawhash, 'salt' => $rawsalt); + } else { + return array('algo' => 'md5', 'hash' => $hash); + } + } + + private static function getAlgo() + { + global $cfg; + + if (array_key_exists('pwhash_algo', $cfg)) { + $pwhash_algo = $cfg['pwhash_algo']; + } else { + $pwhash_algo = 'md5'; + } + + if ($pwhash_algo === 'default') { + $pwhash_algo = 'pbkdf2-sha1'; + } + + return $pwhash_algo; + } + + private static function getDefaultAlgoCfg($algo) + { + switch ($algo) { + case 'pbkdf2-sha1': + return array('iterations' => '500000'); + } + + return array(); + } + + private static function parseAlgoCfg($algo_cfg_str) + { + $algo_cfg = array(); + + $pairs = explode(',', $algo_cfg_str); + + foreach ($pairs as $pair) { + $parts = explode('=', $pair, 2); + + $key = $parts[0]; + + if (count($parts) > 1) { + $value = $parts[1]; + } else { + $value = null; + } + + $algo_cfg[$key] = $value; + } + + return $algo_cfg; + } + + private static function getAlgoCfg() + { + global $cfg; + + $algo = self::getAlgo(); + $algo_cfg = self::getDefaultAlgoCfg($algo); + + if (array_key_exists('pwhash_algo_cfg', $cfg)) { + $custom_algo_cfg = self::parseAlgoCfg($cfg['pwhash_algo_cfg']); + $algo_cfg = array_merge($algo_cfg, $custom_algo_cfg); + } + + return array_merge($algo_cfg, array('algo' => $algo)); + } +} diff --git a/modules/clanmgr/Functions/CheckClanPW.php b/modules/clanmgr/Functions/CheckClanPW.php index 9c10d26db..1751b2bd2 100644 --- a/modules/clanmgr/Functions/CheckClanPW.php +++ b/modules/clanmgr/Functions/CheckClanPW.php @@ -1,5 +1,7 @@ qry_first("SELECT password FROM %prefix%clan WHERE clanid = %int%", $_GET['clanid']); - if ($clan['password'] and $clan['password'] == md5($clanpw)) { + if ($clan['password'] and PasswordHash::verify($clanpw, $clan['password'])) { return true; } return false; diff --git a/modules/clanmgr/mod_settings/db.xml b/modules/clanmgr/mod_settings/db.xml index 6a770decd..cc78be305 100755 --- a/modules/clanmgr/mod_settings/db.xml +++ b/modules/clanmgr/mod_settings/db.xml @@ -30,7 +30,7 @@ password - char(32) + varchar(255) diff --git a/modules/install/adminaccount.php b/modules/install/adminaccount.php index c34806ee9..c32db0429 100644 --- a/modules/install/adminaccount.php +++ b/modules/install/adminaccount.php @@ -1,7 +1,10 @@ connect(); -$dsp->NewContent(t('Adminaccount anlegen'), t('Lege hier einen Adminaccount an, über welchen du Zugriff auf diese Admin-Seite erh�lst. Wenn du bereits Benutzer-Daten importiert hast musst du hier keinen weiteren Account anlegen.')); +$dsp->NewContent(t('Adminaccount anlegen'), t('Lege hier einen Adminaccount an, �ber welchen du Zugriff auf diese Admin-Seite erh?lst. Wenn du bereits Benutzer-Daten importiert hast musst du hier keinen weiteren Account anlegen.')); $find = $db->qry("SELECT * FROM %prefix%user"); if ($db->num_rows($find) == 0) { @@ -16,7 +19,7 @@ } elseif ($_POST["password"] == "") { $func->error(t('Bitte gib ein Kennwort ein!'), "index.php?mod=install&action=adminaccount"); } elseif ($_POST["password"] != $_POST["password2"]) { - $func->error(t('Das Passwort und seine Verifizierung stimmen nicht überein!'), "index.php?mod=install&action=adminaccount"); + $func->error(t('Das Passwort und seine Verifizierung stimmen nicht �berein!'), "index.php?mod=install&action=adminaccount"); } else { $db->qry(" INSERT INTO %prefix%user @@ -24,7 +27,7 @@ username = 'ADMIN', email=%string%, password = %string%, - type = '3'", $_POST["email"], md5($_POST["password"])); + type = '3'", $_POST["email"], PasswordHash::hash($_POST["password"])); $userid = $db->insert_id(); // Add administrator to party diff --git a/modules/install/mod_settings/config.xml b/modules/install/mod_settings/config.xml index ae8a6e5e5..489d16c16 100644 --- a/modules/install/mod_settings/config.xml +++ b/modules/install/mod_settings/config.xml @@ -174,6 +174,25 @@ + + + passwordhashalgo + + + + default + Standard + + + pbkdf2-sha1 + PBKDF2-SHA1 (kompatibel mit ejabberd) + + + md5 + MD5 (Legacy, DANGEROUS!) + + + email_dns_verification @@ -310,6 +329,18 @@ eintagsmail.de nospamfor.us Email-Domain-Blacklist (Um Wegwerf-Mailadressen zu vermeiden) + + pwhash_algo + passwordhashalgo + default + Passwort Hashing Algorithmus + + + pwhash_algo_cfg + string + + Parameter für Passwort Hashing Algorithmus + diff --git a/modules/install/wizard.php b/modules/install/wizard.php index c3a25e816..12921336b 100644 --- a/modules/install/wizard.php +++ b/modules/install/wizard.php @@ -1,5 +1,7 @@ success = 0; } @@ -29,7 +31,7 @@ if ($row['email']) { $db->qry( "UPDATE %prefix%user SET password = %string%, type = '3' WHERE email=%string%", - md5($_POST["password"]), + PasswordHash::hash($_POST["password"]), $_POST["email"] ); // If not found, insert @@ -37,7 +39,7 @@ $db->qry( "INSERT INTO %prefix%user SET username = 'ADMIN', firstname = 'ADMIN', name = 'ADMIN', email=%string%, password = %string%, type = '3'", $_POST["email"], - md5($_POST["password"]) + PasswordHash::hash($_POST["password"]) ); $userid = $db->insert_id(); } diff --git a/modules/tournament2/Classes/Team.php b/modules/tournament2/Classes/Team.php index edba76956..5e54b84f1 100644 --- a/modules/tournament2/Classes/Team.php +++ b/modules/tournament2/Classes/Team.php @@ -2,6 +2,8 @@ namespace LanSuite\Module\Tournament2; +use LanSuite\PasswordHash; + class Team { @@ -247,7 +249,7 @@ public function join($teamid, $userid, $password = null) team.teamid = %int%", $teamid); // Check password, if set and if acction is not performed, by teamadmin or ls-admin - if (($auth['userid'] != $team['leaderid']) and ($auth['type'] <= 1) and ($team['password'] != '') and (md5($password) != $team['password'])) { + if (($auth['userid'] != $team['leaderid']) and ($auth['type'] <= 1) and ($team['password'] != '') and !PasswordHash::verify($password, $team['password'])) { $func->information(t('Das eingegebene Kennwort ist nicht korrekt')); return false; @@ -363,7 +365,7 @@ public function create($tournamentid, $leaderid, $name = null, $password = null, leaderid = %int%, comment = %string%, banner = %string%, - password = %string%", $tournamentid, $name, $leaderid, $comment, $_FILES[$banner]["name"], md5($password)); + password = %string%", $tournamentid, $name, $leaderid, $comment, $_FILES[$banner]["name"], PasswordHash::hash($password)); $func->log_event(t('Der Benutzer %1 hat sich zum Turnier %2 angemeldet', $auth["username"], $t["name"]), 1, t('Turnier Teamverwaltung')); } @@ -408,7 +410,7 @@ public function edit($teamid, $name = null, $password = null, $comment = null, $ // Set Password if ($password != "") { - $db->qry("UPDATE %prefix%t2_teams SET password = %string% WHERE teamid = %int%", md5($password), $_GET["teamid"]); + $db->qry("UPDATE %prefix%t2_teams SET password = %string% WHERE teamid = %int%", PasswordHash::hash($password), $_GET["teamid"]); } $db->qry(" diff --git a/modules/tournament2/mod_settings/db.xml b/modules/tournament2/mod_settings/db.xml index 83a31d71b..d71fc0d66 100644 --- a/modules/tournament2/mod_settings/db.xml +++ b/modules/tournament2/mod_settings/db.xml @@ -219,7 +219,7 @@ password - char(32) + varchar(255) @@ -530,4 +530,4 @@ - \ No newline at end of file + diff --git a/modules/usrmgr/Functions/CheckClanPWUsrMgr.php b/modules/usrmgr/Functions/CheckClanPWUsrMgr.php index 92af44db0..9712c2461 100644 --- a/modules/usrmgr/Functions/CheckClanPWUsrMgr.php +++ b/modules/usrmgr/Functions/CheckClanPWUsrMgr.php @@ -1,5 +1,7 @@ qry_first("SELECT password FROM %prefix%clan WHERE clanid = %int%", $_POST['clan']); - if ($clan['password'] and $clan['password'] != md5($clanpw)) { + if ($clan['password'] and !PasswordHash::verify($clanpw, $clan['password'])) { return t('Passwort falsch!'); } } diff --git a/modules/usrmgr/Functions/CheckOldPW.php b/modules/usrmgr/Functions/CheckOldPW.php index 9fb2816e5..93a5ad5c3 100644 --- a/modules/usrmgr/Functions/CheckOldPW.php +++ b/modules/usrmgr/Functions/CheckOldPW.php @@ -1,5 +1,7 @@ qry_first("SELECT password FROM %prefix%user WHERE userid = %int%", $auth["userid"]); - if ($get_dbpwd["password"] != md5($old_password)) { + if (!PasswordHash::verify($old_password, $get_dbpwd["password"])) { return t('Passwort inkorrekt'); } diff --git a/modules/usrmgr/add.php b/modules/usrmgr/add.php index 733545954..56e26b321 100644 --- a/modules/usrmgr/add.php +++ b/modules/usrmgr/add.php @@ -1,5 +1,7 @@ password - char(32) + varchar(255) diff --git a/modules/usrmgr/newpwd.php b/modules/usrmgr/newpwd.php index 352dc1c4f..8483e192b 100644 --- a/modules/usrmgr/newpwd.php +++ b/modules/usrmgr/newpwd.php @@ -1,5 +1,7 @@ qry_first("SELECT name, firstname, username, type FROM %prefix%user WHERE userid = %int%", $_GET['userid']); switch ($_GET['step']) { @@ -13,12 +15,12 @@ case 3: $password = rand(1000, 9999); - $md5_password = md5($password); + $hash = PasswordHash::hash($password); if ($_SESSION["auth"]["type"] < $userdata["type"]) { $func->information(t('Du verfügst über ein geringeres Benutzerlevel, als derjenige, auf den du diese Funktion anwenden möchten. Es wurde kein neues Passwort generiert')); } else { - $db->qry("UPDATE %prefix%user SET password = %string% WHERE userid = %int%", $md5_password, $_GET['userid']); + $db->qry("UPDATE %prefix%user SET password = %string% WHERE userid = %int%", $hash, $_GET['userid']); $func->confirmation(t('Das Passwort von %2 %3 (%4) wurde erfolgreich geändert.
Das neue Passwort lautet %1.', $password, $user_data["firstname"], $user_data["name"], $user_data["username"]), "index.php?mod=usrmgr&action=details&userid=". $_GET['userid']); } diff --git a/modules/usrmgr/remind.php b/modules/usrmgr/remind.php index d363964d7..bf9f64434 100644 --- a/modules/usrmgr/remind.php +++ b/modules/usrmgr/remind.php @@ -1,4 +1,7 @@ NewContent(t('Passwort vergessen'), t('Mit diesem Modul kannst du dir ein neues Passwort generieren lassen')); if (!$cfg['sys_internet']) { @@ -58,7 +61,7 @@ $new_pwd .= chr(mt_rand(65, 90)); } - $db->qry("UPDATE %prefix%user SET password = %string%, fcode = '' WHERE fcode = %string%", md5($new_pwd), $_GET['fcode']); + $db->qry("UPDATE %prefix%user SET password = %string%, fcode = '' WHERE fcode = %string%", PasswordHash::hash($new_pwd), $_GET['fcode']); $func->confirmation(t('Das neue Kennwort wurde erfolgreich generiert.
Es lautet:') . "\"$new_pwd\"", "index.php"); } else { From cc4335fcb628844678f5561436bfc9461d3d1401 Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Sun, 25 Jun 2023 13:45:37 +0200 Subject: [PATCH 2/3] Add Root-Namespace to \Exception --- inc/Classes/Auth.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inc/Classes/Auth.php b/inc/Classes/Auth.php index 568eda02c..24cee27e6 100644 --- a/inc/Classes/Auth.php +++ b/inc/Classes/Auth.php @@ -323,7 +323,7 @@ public function login($email, $password, $show_confirmation = 1) try { $db->qry('UPDATE %prefix%user SET password = %string% WHERE userid = %int%', PasswordHash::hash($password), $user["userid"]); $func->information(t('Es wurde ein Sicherheitsupgrade von deinem Passwort durchgeführt.'), '', 1); - } catch (Exception $e) { + } catch (\Exception $e) { $func->error(t('Sicherheitsupgrade von deinem Passwort ist fehlgeschlagen!')); } } From 1b57f1b2f2fa9a070c2800deb90064086cf70cbf Mon Sep 17 00:00:00 2001 From: Andy Grunwald Date: Wed, 28 Jun 2023 08:51:17 +0200 Subject: [PATCH 3/3] Fix broken german umlaut --- modules/install/adminaccount.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/install/adminaccount.php b/modules/install/adminaccount.php index 1b564b534..70e4550e1 100644 --- a/modules/install/adminaccount.php +++ b/modules/install/adminaccount.php @@ -19,7 +19,7 @@ } elseif ($_POST["password"] == "") { $func->error(t('Bitte gib ein Kennwort ein!'), "index.php?mod=install&action=adminaccount"); } elseif ($_POST["password"] != $_POST["password2"]) { - $func->error(t('Das Passwort und seine Verifizierung stimmen nicht �berein!'), "index.php?mod=install&action=adminaccount"); + $func->error(t('Das Passwort und seine Verifizierung stimmen nicht überein!'), "index.php?mod=install&action=adminaccount"); } else { $db->qry(" INSERT INTO %prefix%user