Improve security, validation, and isolation checks

Add multiple security and validation improvements across the app:

- Prevent session fixation: regenerate session ID on login and after successful 2FA; tighten session cookie params (Secure, HttpOnly, SameSite=Lax).
- Harden installer: add CSRF checks for install/update flows and use PDO::quote when injecting admin credentials into SQL migration to avoid injection; add csrf_field() to installer templates.
- Template hardening: add safe_url and safe_mailto Twig filters, escape tag names for JS, and add rel="noopener noreferrer" to external links to mitigate XSS/opener risks.
- Domain controller: validate referrer to avoid open redirects, enforce user isolation mode when finding/deleting/updating domains and when assigning notification groups (ensures users only affect their own resources).
- Notification groups: verify channel belongs to group before deleting or toggling to prevent unauthorized access.
- ErrorLog: whitelist allowed sort columns to avoid arbitrary column injection in ORDER BY.
- Routes: move the debug whois route to protected/admin area.

These changes collectively reduce attack surface (XSS, open redirect, session fixation, SQL injection) and enforce proper resource isolation and input validation.
This commit is contained in:
Hosteroid
2026-03-11 00:03:54 +02:00
parent 36abf58838
commit e3006738a9
19 changed files with 112 additions and 34 deletions

View File

@@ -315,6 +315,9 @@ class InstallerController extends Controller
return;
}
// CSRF Protection
$this->verifyCsrf('/install');
// Block re-installation if already installed
if ($this->isInstalled()) {
$_SESSION['error'] = 'System is already installed. Use the update function instead.';
@@ -364,11 +367,11 @@ class InstallerController extends Controller
$file = __DIR__ . '/../../database/migrations/000_initial_schema_v1.1.0.sql';
$sql = file_get_contents($file);
// Replace admin credentials
// Replace admin credentials (use PDO::quote to prevent SQL injection)
$passwordHash = password_hash($adminPassword, PASSWORD_BCRYPT);
$sql = str_replace('{{ADMIN_PASSWORD_HASH}}', $passwordHash, $sql);
$sql = str_replace('{{ADMIN_USERNAME}}', $adminUsername, $sql);
$sql = str_replace('{{ADMIN_EMAIL}}', $adminEmail, $sql);
$sql = str_replace("'{{ADMIN_PASSWORD_HASH}}'", $pdo->quote($passwordHash), $sql);
$sql = str_replace("'{{ADMIN_USERNAME}}'", $pdo->quote($adminUsername), $sql);
$sql = str_replace("'{{ADMIN_EMAIL}}'", $pdo->quote($adminEmail), $sql);
// Execute the entire consolidated schema at once
// This is safe because MySQL can handle multiple statements with CREATE TABLE IF NOT EXISTS
@@ -584,6 +587,9 @@ class InstallerController extends Controller
$this->redirect('/install/update');
return;
}
// CSRF Protection
$this->verifyCsrf('/install/update');
try {
$pdo = \Core\Database::getConnection();