Files
deskhub/DeskHub-Webseite/tests/security_helpers.php
T
2026-10-16 19:12:24 +00:00

76 lines
4.2 KiB
PHP

<?php
declare(strict_types=1);
/**
* Prüft sicherheitsrelevante Hilfsfunktionen: Client-IP hinter Reverse-Proxys (kein Umgehen der Login-Sperre per
* gefälschtem X-Forwarded-For), HTTPS-Erkennung und das Zurücksetzen der Login-Zähler.
*/
putenv('DESKHUB_SQLITE_PATH=:memory:');
putenv('DESKHUB_TESTING=1');
putenv('DESKHUB_CONFIG_JSON=' . json_encode(['site' => ['trusted_proxies' => ['10.0.0.0/8', '192.168.1.5', 'fd00::/8']]]));
require_once dirname(__DIR__) . '/includes/bootstrap.php';
$failures = [];
$check = function (bool $ok, string $what) use (&$failures): void {
if (!$ok) $failures[] = $what;
};
// CIDR-Vergleich
$check(ip_in_list('10.1.2.3', ['10.0.0.0/8']), '10.1.2.3 in 10.0.0.0/8');
$check(!ip_in_list('11.1.2.3', ['10.0.0.0/8']), '11.1.2.3 nicht in 10.0.0.0/8');
$check(ip_in_list('192.168.1.5', ['192.168.1.5']), 'Einzeladresse');
$check(!ip_in_list('192.168.1.6', ['192.168.1.5']), 'andere Einzeladresse');
$check(ip_in_list('172.17.0.3', ['172.16.0.0/12']), '/12-Bereich');
$check(!ip_in_list('172.32.0.1', ['172.16.0.0/12']), 'außerhalb /12');
$check(ip_in_list('fd00::1234', ['fd00::/8']), 'IPv6 im Bereich');
$check(!ip_in_list('2001:db8::1', ['fd00::/8']), 'IPv6 außerhalb');
$check(!ip_in_list('10.0.0.1', ['fd00::/8']), 'IPv4 gegen IPv6-Eintrag');
$check(!ip_in_list('kaputt', ['10.0.0.0/8']), 'ungültige IP');
// Direkter Zugriff: X-Forwarded-For wird ignoriert (sonst könnte jeder die Sperre umgehen)
$_SERVER['REMOTE_ADDR'] = '203.0.113.9';
$_SERVER['HTTP_X_FORWARDED_FOR'] = '1.2.3.4';
$_SERVER['HTTP_X_FORWARDED_PROTO'] = 'https';
$check(client_ip() === '203.0.113.9', 'Ohne vertrauenswürdigen Proxy zählt REMOTE_ADDR: ' . client_ip());
$check(!request_is_https(), 'Ohne vertrauenswürdigen Proxy wird X-Forwarded-Proto ignoriert');
// Über vertrauenswürdigen Proxy
$_SERVER['REMOTE_ADDR'] = '10.0.0.7';
$_SERVER['HTTP_X_FORWARDED_FOR'] = '198.51.100.20';
$check(client_ip() === '198.51.100.20', 'Proxy: Client-IP aus X-Forwarded-For: ' . client_ip());
$check(request_is_https(), 'Proxy: X-Forwarded-Proto https wird erkannt');
// Der Angreifer hängt eine eigene Adresse vorne an: maßgeblich ist die letzte nicht vertrauenswürdige von rechts
$_SERVER['HTTP_X_FORWARDED_FOR'] = '1.1.1.1, 198.51.100.20, 10.0.0.3';
$check(client_ip() === '198.51.100.20', 'Gefälschte Adresse vorne wird ignoriert: ' . client_ip());
$_SERVER['HTTP_X_FORWARDED_FOR'] = 'kein-ip-wert';
$check(client_ip() === '10.0.0.7', 'Ungültiger Header fällt auf REMOTE_ADDR zurück');
// Login-Zähler: nach Erfolg zurücksetzbar
$_SERVER['REMOTE_ADDR'] = '203.0.113.9';
unset($_SERVER['HTTP_X_FORWARDED_FOR']);
for ($i = 0; $i < 5; $i++) $check(rate_limit_check('login_email', 'a@example.org', 5, 15), "Versuch {$i} erlaubt");
$check(!rate_limit_check('login_email', 'a@example.org', 5, 15), 'sechster Versuch gesperrt');
rate_limit_reset('login_email', 'a@example.org');
$check(rate_limit_check('login_email', 'a@example.org', 5, 15), 'nach dem Zurücksetzen wieder erlaubt');
$check(!rate_limit_check('login_email', 'b@example.org', 0, 15), 'anderer Schlüssel unabhängig');
// Abteilungsprüfung für Mitarbeiter (verhindert Zugriff auf Tickets/Anhänge fremder Abteilungen)
$pdo = get_db();
$pdo->exec("INSERT INTO departments (name, email, is_default) VALUES ('Zweite Abteilung', 'zweite@example.org', 0)");
$deptA = (int) $pdo->query("SELECT id FROM departments ORDER BY id LIMIT 1")->fetchColumn();
$deptB = (int) $pdo->query("SELECT id FROM departments ORDER BY id DESC LIMIT 1")->fetchColumn();
$check($deptA !== $deptB, "zwei Abteilungen angelegt");
$admin = ["id" => 1, "role" => "admin", "department_id" => null];
$agentA = ["id" => 2, "role" => "agent", "department_id" => $deptA];
$agentNone = ["id" => 3, "role" => "agent", "department_id" => null];
$check(staff_can_access_department($admin, $deptB), "Admin darf jede Abteilung");
$check(staff_can_access_department($agentA, $deptA), "Agent darf eigene Abteilung");
$check(!staff_can_access_department($agentA, $deptB), "Agent darf fremde Abteilung nicht");
$check(!staff_can_access_department($agentNone, $deptA), "Agent ohne Abteilung darf nichts");
if ($failures) {
fwrite(STDERR, "FEHLER:\n- " . implode("\n- ", $failures) . "\n");
exit(1);
}
echo "Security helpers checks passed.\n";