Implements security helper and CSRF middleware

main
Yasen Pramatarov 2025-01-30 18:47:13 +02:00
parent be77376d85
commit 9c9a306f55
3 changed files with 154 additions and 0 deletions

View File

@ -0,0 +1,115 @@
<?php
// Security helper, to be used with all the forms in the app
class SecurityHelper {
private static $instance = null;
private $session;
private function __construct() {
// Don't start a new session, just reference the existing one
$this->session = &$_SESSION;
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new SecurityHelper();
}
return self::$instance;
}
// Generate CSRF token
public function generateCsrfToken() {
if (empty($this->session['csrf_token'])) {
$this->session['csrf_token'] = bin2hex(random_bytes(32));
}
return $this->session['csrf_token'];
}
// Verify CSRF token
public function verifyCsrfToken($token) {
if (empty($this->session['csrf_token']) || empty($token)) {
return false;
}
return hash_equals($this->session['csrf_token'], $token);
}
// Sanitize string input
public function sanitizeString($input) {
if (is_string($input)) {
return htmlspecialchars(strip_tags(trim($input)), ENT_QUOTES, 'UTF-8');
}
return '';
}
// Validate email
public function validateEmail($email) {
return filter_var($email, FILTER_VALIDATE_EMAIL) !== false;
}
// Validate integer
public function validateInt($input) {
return filter_var($input, FILTER_VALIDATE_INT) !== false;
}
// Validate URL
public function validateUrl($url) {
return filter_var($url, FILTER_VALIDATE_URL) !== false;
}
// Sanitize array of inputs
public function sanitizeArray($array, $allowedKeys = []) {
$sanitized = [];
foreach ($array as $key => $value) {
if (empty($allowedKeys) || in_array($key, $allowedKeys)) {
if (is_array($value)) {
$sanitized[$key] = $this->sanitizeArray($value);
} else {
$sanitized[$key] = $this->sanitizeString($value);
}
}
}
return $sanitized;
}
// Validate form data based on rules
public function validateFormData($data, $rules) {
$errors = [];
foreach ($rules as $field => $rule) {
if (!isset($data[$field]) && $rule['required']) {
$errors[$field] = "Field is required";
continue;
}
if (isset($data[$field])) {
$value = $data[$field];
switch ($rule['type']) {
case 'email':
if (!$this->validateEmail($value)) {
$errors[$field] = "Invalid email format";
}
break;
case 'integer':
if (!$this->validateInt($value)) {
$errors[$field] = "Must be a valid integer";
}
break;
case 'url':
if (!$this->validateUrl($value)) {
$errors[$field] = "Invalid URL format";
}
break;
case 'string':
if (isset($rule['min']) && strlen($value) < $rule['min']) {
$errors[$field] = "Minimum length is {$rule['min']} characters";
}
if (isset($rule['max']) && strlen($value) > $rule['max']) {
$errors[$field] = "Maximum length is {$rule['max']} characters";
}
break;
}
}
}
return $errors;
}
}

View File

@ -0,0 +1,35 @@
<?php
require_once __DIR__ . '/../helpers/securityhelper.php';
function verifyCsrfToken() {
$security = SecurityHelper::getInstance();
// Skip CSRF check for GET requests
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
return true;
}
// Skip CSRF check for initial login attempt
if ($_SERVER['REQUEST_METHOD'] === 'POST' &&
isset($_GET['page']) && $_GET['page'] === 'login' &&
!isset($_SESSION['username'])) {
return true;
}
// Check CSRF token for all other POST requests
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$token = $_POST['csrf_token'] ?? '';
if (!$security->verifyCsrfToken($token)) {
// Log CSRF attempt
error_log("CSRF attempt detected from IP: " . $_SERVER['REMOTE_ADDR']);
$logObject->insertLog(0, "CSRF attempt detected from IP: " . $_SERVER['REMOTE_ADDR'], 'system');
// Return error message
http_response_code(403);
die('Invalid CSRF token. Please try again.');
}
}
return true;
}

View File

@ -0,0 +1,4 @@
<?php
$token = SecurityHelper::getInstance()->generateCsrfToken();
?>
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($token) ?>" />