Security hardening: CSRF, rate limiting, session/password policy, audit log
Fixes critical pre-existing issues found during review: bulk_action.php had no auth check at all (unauthenticated download/delete of any qrcode) and built a table name from unwhitelisted user input (SQL injection); the QR generator classes wrote files from unvalidated filename/format, allowing path traversal and arbitrary file writes. Also pins chillerlan/php-qrcode to 5.0.5 since master now requires PHP 8.4, breaking the PHP 8.3 build. - CSRF tokens on all POST forms and the bulk_action.php JSON endpoint - Login rate limiting (5 attempts / 15 min) via new login_attempts table - Hardened sessions: httponly/samesite cookies, 30 min idle timeout, session regeneration on login - Forced password change for the default superadmin/superadmin account - Server-side validation in Users/DynamicQrcode/Qrcode classes - Audit log table for auth, user, and qrcode actions - Checked-in db schema (db/init.sql, migrations/) instead of relying on an opaque prebuilt db image - Production docker-compose with Nginx + php-fpm instead of the PHP dev server
This commit is contained in:
+57
-34
@@ -1,66 +1,89 @@
|
||||
<?php
|
||||
|
||||
|
||||
require_once 'config/config.php';
|
||||
session_start();
|
||||
require_once 'includes/bootstrap.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST')
|
||||
{
|
||||
csrf_verify_or_die();
|
||||
|
||||
$username = filter_input(INPUT_POST, 'username');
|
||||
$password = filter_input(INPUT_POST, 'password');
|
||||
$remember = filter_input(INPUT_POST, 'remember');
|
||||
|
||||
if (!$username || !$password) {
|
||||
$_SESSION['login_failure'] = 'Invalid username or password';
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
if (qr_is_login_locked_out($username)) {
|
||||
$_SESSION['login_failure'] = 'Too many failed login attempts. Try again in 15 minutes.';
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
}
|
||||
|
||||
// Get DB instance.
|
||||
$db = getDbInstance();
|
||||
|
||||
$db->where('username', $username);
|
||||
$row = $db->getOne('users');
|
||||
|
||||
if ($db->count >= 1)
|
||||
if ($db->count >= 1 && password_verify($password, $row['password']))
|
||||
{
|
||||
$db_password = $row['password'];
|
||||
qr_record_login_attempt($username, true);
|
||||
|
||||
// Voorkom session fixation: nieuwe sessie-id na een geslaagde login.
|
||||
session_regenerate_id(true);
|
||||
|
||||
$_SESSION['user_logged_in'] = TRUE;
|
||||
$_SESSION['type'] = $row['type'];
|
||||
$_SESSION['user_id'] = $row['id'];
|
||||
$_SESSION['username'] = $row['username'];
|
||||
$_SESSION['must_change_password'] = !empty($row['must_change_password']);
|
||||
$_SESSION['last_activity'] = time();
|
||||
|
||||
audit_log('login_success');
|
||||
|
||||
$user_id = $row['id'];
|
||||
|
||||
if (password_verify($password, $db_password))
|
||||
if ($remember)
|
||||
{
|
||||
$_SESSION['user_logged_in'] = TRUE;
|
||||
$_SESSION['type'] = $row['type'];
|
||||
$_SESSION['user_id'] = $row['id'];
|
||||
$series_id = randomString(16);
|
||||
$remember_token = getSecureRandomToken(20);
|
||||
$encryted_remember_token = password_hash($remember_token,PASSWORD_DEFAULT);
|
||||
|
||||
if ($remember)
|
||||
{
|
||||
$series_id = randomString(16);
|
||||
$remember_token = getSecureRandomToken(20);
|
||||
$encryted_remember_token = password_hash($remember_token,PASSWORD_DEFAULT);
|
||||
$expiry_time = date('Y-m-d H:i:s', strtotime(' + 30 days'));
|
||||
$expires = strtotime($expiry_time);
|
||||
$is_https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off';
|
||||
|
||||
$expiry_time = date('Y-m-d H:i:s', strtotime(' + 30 days'));
|
||||
$expires = strtotime($expiry_time);
|
||||
$cookie_options = [
|
||||
'expires' => $expires,
|
||||
'path' => '/',
|
||||
'secure' => $is_https,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
];
|
||||
|
||||
setcookie('series_id', $series_id, $expires, '/');
|
||||
setcookie('remember_token', $remember_token, $expires, '/');
|
||||
setcookie('series_id', $series_id, $cookie_options);
|
||||
setcookie('remember_token', $remember_token, $cookie_options);
|
||||
|
||||
$db = getDbInstance();
|
||||
$db->where ('id',$user_id);
|
||||
$db = getDbInstance();
|
||||
$db->where ('id',$user_id);
|
||||
|
||||
$update_remember = array(
|
||||
'series_id'=> $series_id,
|
||||
'remember_token' => $encryted_remember_token,
|
||||
'expires' =>$expiry_time
|
||||
);
|
||||
$db->update('users', $update_remember);
|
||||
}
|
||||
// Authentication successfull redirect user
|
||||
header('Location: index.php');
|
||||
}
|
||||
else
|
||||
{
|
||||
$_SESSION['login_failure'] = 'Invalid username or password';
|
||||
header('Location: login.php');
|
||||
$update_remember = array(
|
||||
'series_id'=> $series_id,
|
||||
'remember_token' => $encryted_remember_token,
|
||||
'expires' =>$expiry_time
|
||||
);
|
||||
$db->update('users', $update_remember);
|
||||
}
|
||||
// Authentication successfull redirect user
|
||||
header('Location: index.php');
|
||||
exit;
|
||||
}
|
||||
else
|
||||
{
|
||||
qr_record_login_attempt($username, false);
|
||||
$_SESSION['login_failure'] = 'Invalid username or password';
|
||||
header('Location: login.php');
|
||||
exit;
|
||||
|
||||
Reference in New Issue
Block a user