Implements security helper and CSRF middleware
parent
be77376d85
commit
9c9a306f55
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?php
|
||||||
|
$token = SecurityHelper::getInstance()->generateCsrfToken();
|
||||||
|
?>
|
||||||
|
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($token) ?>" />
|
Loading…
Reference in New Issue