Hash editor passwords with Argon2id and upgrade old hashes.
Old hashes are replaced at login. Widen the column before deploying: ALTER TABLE editors MODIFY password VARCHAR(255) NOT NULL;
This commit is contained in:
+19
-2
@@ -5,6 +5,8 @@ if ( ! isset($_SERVER['HTTPS'])) {
|
|||||||
require_once("../session.lib.php");
|
require_once("../session.lib.php");
|
||||||
$added = FALSE;
|
$added = FALSE;
|
||||||
$csrf_failed = FALSE;
|
$csrf_failed = FALSE;
|
||||||
|
$hash_failed = FALSE;
|
||||||
|
$exists = FALSE;
|
||||||
if (!isset($_SESSION['editor'])) {
|
if (!isset($_SESSION['editor'])) {
|
||||||
header("Location: https://xxx.org.za/publish/login");
|
header("Location: https://xxx.org.za/publish/login");
|
||||||
return;
|
return;
|
||||||
@@ -16,11 +18,20 @@ if (!isset($_SESSION['editor'])) {
|
|||||||
$table = "editors";
|
$table = "editors";
|
||||||
|
|
||||||
$username = (string)($_POST['username'] ?? '');
|
$username = (string)($_POST['username'] ?? '');
|
||||||
$password = password_hash((string)($_POST['password'] ?? ''), PASSWORD_DEFAULT);
|
$password = password_hash((string)($_POST['password'] ?? ''), password_algorithm());
|
||||||
$email = (string)($_POST['email'] ?? '');
|
$email = (string)($_POST['email'] ?? '');
|
||||||
|
$found = $db->execute_query("SELECT username FROM $table WHERE username = ?", array($username));
|
||||||
$query = "INSERT INTO $table SET `username` = ?, `password` = ?, `email` = ?";
|
$query = "INSERT INTO $table SET `username` = ?, `password` = ?, `email` = ?";
|
||||||
if ($result = $db->execute_query($query, array($username, $password, $email))) {
|
if ($found && $found->fetch_assoc()) {
|
||||||
|
$exists = TRUE;
|
||||||
|
} else if ($result = $db->execute_query($query, array($username, $password, $email))) {
|
||||||
|
// If the password column cut the hash short, the editor could never log in
|
||||||
|
if (password_hash_is_stored($username, $password)) {
|
||||||
$added = TRUE;
|
$added = TRUE;
|
||||||
|
} else {
|
||||||
|
$db->execute_query("DELETE FROM $table WHERE username = ?", array($username));
|
||||||
|
$hash_failed = TRUE;
|
||||||
|
}
|
||||||
$is_admin = TRUE;
|
$is_admin = TRUE;
|
||||||
}
|
}
|
||||||
open_db('close');
|
open_db('close');
|
||||||
@@ -54,6 +65,12 @@ if (!isset($_SESSION['editor'])) {
|
|||||||
<?php if ($csrf_failed) { ?>
|
<?php if ($csrf_failed) { ?>
|
||||||
<p><strong>Your session has expired. Please try again.</strong></p>
|
<p><strong>Your session has expired. Please try again.</strong></p>
|
||||||
<?php } ?>
|
<?php } ?>
|
||||||
|
<?php if ($exists) { ?>
|
||||||
|
<p><strong>An editor with that username already exists.</strong></p>
|
||||||
|
<?php } ?>
|
||||||
|
<?php if ($hash_failed) { ?>
|
||||||
|
<p><strong>The editor was not added: the editors.password column is too short for the password hash. Widen it to VARCHAR(255).</strong></p>
|
||||||
|
<?php } ?>
|
||||||
|
|
||||||
<form action="<?= form_value("https://xxx.org.za".$_SERVER['REQUEST_URI']) ?>" method="post">
|
<form action="<?= form_value("https://xxx.org.za".$_SERVER['REQUEST_URI']) ?>" method="post">
|
||||||
<?= csrf_field() ?>
|
<?= csrf_field() ?>
|
||||||
|
|||||||
+5
-2
@@ -26,12 +26,15 @@ if (isset($_SESSION['editor'])) {
|
|||||||
$stored_password = $row['password'];
|
$stored_password = $row['password'];
|
||||||
else $denied = TRUE;
|
else $denied = TRUE;
|
||||||
$getid->close();
|
$getid->close();
|
||||||
open_db('close');
|
$compare_password = is_string($_POST['password'] ?? NULL) ? $_POST['password'] : '';
|
||||||
$compare_password = $_POST['password'] ?? '';
|
|
||||||
// password_verify() also accepts hashes made by the old crypt() call
|
// password_verify() also accepts hashes made by the old crypt() call
|
||||||
if (!password_verify($compare_password, $stored_password)) {
|
if (!password_verify($compare_password, $stored_password)) {
|
||||||
$denied = TRUE;
|
$denied = TRUE;
|
||||||
|
open_db('close');
|
||||||
} else {
|
} else {
|
||||||
|
// Move an old hash to the current algorithm while we have the password
|
||||||
|
rehash_password($username, $compare_password, $stored_password);
|
||||||
|
open_db('close');
|
||||||
login_editor($username);
|
login_editor($username);
|
||||||
// Keep user logged in with cookie, if they request it
|
// Keep user logged in with cookie, if they request it
|
||||||
if (isset($_POST['remember'])) {
|
if (isset($_POST['remember'])) {
|
||||||
|
|||||||
@@ -132,3 +132,44 @@ function login_editor($username) {
|
|||||||
unset($_SESSION['csrf_token']);
|
unset($_SESSION['csrf_token']);
|
||||||
$_SESSION['editor'] = $username;
|
$_SESSION['editor'] = $username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Password hashing: Argon2id where PHP was built with it, otherwise
|
||||||
|
* bcrypt. Argon2id hashes are about 100 characters long, so the
|
||||||
|
* editors.password column must hold at least that (VARCHAR(255) is best).
|
||||||
|
*/
|
||||||
|
function password_algorithm() {
|
||||||
|
return defined('PASSWORD_ARGON2ID') ? PASSWORD_ARGON2ID : PASSWORD_BCRYPT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Store $hash as the password of $username, and return TRUE if the database kept
|
||||||
|
* the whole hash. The database must be open.
|
||||||
|
*/
|
||||||
|
function store_password_hash($username, $hash) {
|
||||||
|
global $db;
|
||||||
|
$db->execute_query("UPDATE editors SET password = ? WHERE username = ?", array($hash, $username));
|
||||||
|
return password_hash_is_stored($username, $hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TRUE if the database holds exactly $hash for $username
|
||||||
|
function password_hash_is_stored($username, $hash) {
|
||||||
|
global $db;
|
||||||
|
$check = $db->execute_query("SELECT password FROM editors WHERE username = ?", array($username));
|
||||||
|
$row = $check ? $check->fetch_assoc() : NULL;
|
||||||
|
return $row && $row['password'] === $hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* After a successful login, move an old hash (MD5-crypt, bcrypt) to the
|
||||||
|
* current algorithm. If the column is too short and MySQL cut the new
|
||||||
|
* hash, put the old one back so that the editor can still log in.
|
||||||
|
*/
|
||||||
|
function rehash_password($username, $password, $stored_hash) {
|
||||||
|
if (!password_needs_rehash($stored_hash, password_algorithm()))
|
||||||
|
return;
|
||||||
|
if (!store_password_hash($username, password_hash($password, password_algorithm()))) {
|
||||||
|
store_password_hash($username, $stored_hash);
|
||||||
|
error_log("xxx: could not store a new password hash for $username; widen editors.password");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user