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

85 lines
4.8 KiB
PHP

<?php
declare(strict_types=1);
/**
* Prüft die Sicherheitsprüfung (includes/security_check.php): erkennt sie erreichbare interne Dateien und ausführbare
* Uploads, und meldet sie nichts, wenn alles gesperrt ist? Der Webserver wird durch eine Abruf-Funktion simuliert.
*/
putenv('DESKHUB_SQLITE_PATH=:memory:');
require_once dirname(__DIR__) . '/includes/bootstrap.php';
$failures = [];
$check = function (bool $ok, string $what) use (&$failures): void {
if (!$ok) $failures[] = $what;
};
$levels = fn(array $rows) => array_column($rows, 'level');
// Gut konfigurierter Server: alles Interne 403, Unbekanntes 404
$secure = fn(string $url) => str_contains($url, 'deskhub-pruefung') ? [404, ''] : [403, ''];
$r = security_check_exposure('https://support.example.org', $secure);
$check($levels($r) === ['ok'], 'Gesperrter Server: nur "ok" erwartet, erhalten ' . implode(',', $levels($r)));
// Server ohne Sperrregeln (z. B. nginx ohne Konfiguration): Konfiguration und Datenbank liefern 200
$open = fn(string $url) => str_contains($url, 'deskhub-pruefung') ? [404, ''] : [200, '<?php return [];'];
$r = security_check_exposure('https://support.example.org', $open);
$check($levels($r) === ['error'], 'Offener Server muss einen Fehler melden');
$check(str_contains($r[0]['detail'], '/config/config.php') && str_contains($r[0]['detail'], '/database/deskhub.sqlite') && str_contains($r[0]['detail'], '/backups/'), 'Fehlertext nennt die erreichbaren Adressen: ' . $r[0]['detail']);
// Nur eine Adresse offen
$partial = fn(string $url) => str_contains($url, '/backups/') ? [200, 'Index of'] : (str_contains($url, 'deskhub-pruefung') ? [404, ''] : [403, '']);
$r = security_check_exposure('https://support.example.org/', $partial);
$check($levels($r) === ['error'] && str_contains($r[0]['detail'], '/backups/') && !str_contains($r[0]['detail'], 'config.php'), 'Genau die offene Adresse wird gemeldet');
// Server antwortet auf alles mit 200 (Catch-all): nicht aussagekräftig statt falschem Alarm
$catchAll = fn(string $url) => [200, '<html>Startseite</html>'];
$r = security_check_exposure('https://support.example.org', $catchAll);
$check($levels($r) === ['info'], 'Catch-all-Server: nur Hinweis "nicht prüfbar"');
// Nicht erreichbar
$down = fn(string $url) => [0, ''];
$r = security_check_exposure('https://support.example.org', $down);
$check($levels($r) === ['info'], 'Nicht erreichbar: nur Hinweis');
// Upload-Ausführung: simulierter Server führt die Datei aus (liefert den Marker) bzw. nicht
$dir = sys_get_temp_dir() . '/deskhub_sc_' . bin2hex(random_bytes(4));
mkdir($dir);
$executing = function (string $url) use ($dir): array {
$f = $dir . '/' . basename($url);
if (!is_file($f)) return [404, ''];
preg_match('/echo "([a-f0-9]+)"/', (string) file_get_contents($f), $m);
return [200, $m[1] ?? ''];
};
$notExecuting = function (string $url) use ($dir): array {
$f = $dir . '/' . basename($url);
return is_file($f) ? [200, (string) file_get_contents($f)] : [404, '']; // liefert den Quelltext aus, führt ihn nicht aus
};
$denied = fn(string $url) => [403, ''];
$r = security_check_upload_execution('https://support.example.org', $dir, $executing);
$check($r['level'] === 'error', 'Ausführbare Uploads müssen als Fehler gemeldet werden');
$r = security_check_upload_execution('https://support.example.org', $dir, $notExecuting);
$check($r['level'] === 'ok', 'Nicht ausgeführte Uploads: ok (Quelltext ist kein Marker)');
$r = security_check_upload_execution('https://support.example.org', $dir, $denied);
$check($r['level'] === 'ok', 'Gesperrte Uploads: ok');
$check(glob($dir . '/*') === [], 'Die Testdatei wird wieder gelöscht');
$r = security_check_upload_execution('https://support.example.org', $dir . '/gibt-es-nicht', $denied);
$check($r['level'] === 'info', 'Nicht beschreibbarer Ordner: Hinweis');
rmdir($dir);
// Einstellungsprüfungen laufen ohne Fehler und liefern Einträge mit gültigen Stufen
$pdo = get_db();
$rows = security_check_settings($pdo);
$check(count($rows) >= 3, 'Einstellungsprüfung liefert Ergebnisse');
foreach ($rows as $row) $check(in_array($row['level'], ['ok', 'warn', 'error', 'info'], true) && $row['title'] !== '', 'Gültiger Eintrag: ' . json_encode($row));
$all = security_check_all($pdo, false);
$check($all !== [] && $all[0]['level'] !== 'ok' || count(array_unique(array_column($all, 'level'))) === 1, 'Fehler stehen vor "ok" (Sortierung)');
$sorted = array_column($all, 'level');
$rank = ['error' => 0, 'warn' => 1, 'info' => 2, 'ok' => 3];
$check($sorted === array_values(array_map(fn($l) => $l, $sorted)) && array_map(fn($l) => $rank[$l], $sorted) === (function ($a) { sort($a); return $a; })(array_map(fn($l) => $rank[$l], $sorted)), 'Ergebnisse sind nach Dringlichkeit sortiert');
if ($failures) {
fwrite(STDERR, "FEHLER:\n- " . implode("\n- ", $failures) . "\n");
exit(1);
}
echo "Security check checks passed.\n";