76 lines
4.2 KiB
PHP
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";
|