Quelltext des aktuellen Standes
<?php
declare(strict_types=1);
/**
* Hält die vier Lücken fest, die das Sicherheits-Audit gefunden hat.
*
* Jeder Test prüft die Eigenschaft selbst, nicht ihre Schreibweise – ein
* Umbau darf den Test brechen, wenn er die Eigenschaft aufgibt.
*/
use OpenNIT\Assets\RemoteAccess;
use OpenNIT\Backup\S3Client;
use OpenNIT\Backup\SelfBackupManager;
return function (): void {
$wurzel = dirname(__DIR__);
// ================================================================
// Fernspeicher: nur Web-Protokolle
// ================================================================
test('Der Endpunkt des Fernspeichers muss über das Web gehen', function () {
assertTrue(SelfBackupManager::endpunktTauglich('https://s3.example.org'), 'https wird abgewiesen');
assertTrue(SelfBackupManager::endpunktTauglich('http://minio.intern:9000'), 'http wird abgewiesen');
assertTrue(SelfBackupManager::endpunktTauglich('HTTPS://S3.EXAMPLE.ORG'), 'Großschreibung wird abgewiesen');
// Diese Schemata würden die Übertragung auf das Dateisystem richten.
assertFalse(SelfBackupManager::endpunktTauglich('file:///etc'), 'file:// gilt als tauglich');
assertFalse(SelfBackupManager::endpunktTauglich('ftp://ablage.example.org'), 'ftp gilt als tauglich');
assertFalse(SelfBackupManager::endpunktTauglich('/var/www'), 'ein blosser Pfad gilt als tauglich');
assertFalse(SelfBackupManager::endpunktTauglich('https://'), 'Endpunkt ohne Rechnernamen gilt als tauglich');
assertFalse(SelfBackupManager::endpunktTauglich(''), 'leerer Endpunkt gilt als tauglich');
});
test('Ein Dateipfad als Endpunkt liest und schreibt nichts', function () {
$basis = sys_get_temp_dir() . '/onit-s3-test-' . bin2hex(random_bytes(4));
@mkdir($basis . '/eimer', 0700, true);
$opfer = $basis . '/eimer/unberuehrt.txt';
file_put_contents($opfer, 'URSPRUNG');
$nutzlast = $basis . '/nutzlast.bin';
file_put_contents($nutzlast, 'FREMDER-INHALT');
$client = new S3Client('file://' . $basis, 'us-east-1', 'eimer', 'k', 's', true);
// Lesen: der Inhalt darf nicht als Antwortrumpf zurückkommen.
$lesen = $client->listObjects('');
assertFalse($lesen['ok'], 'Ein Dateipfad gilt als erreichbarer Speicher');
assertFalse(str_contains($lesen['message'], 'URSPRUNG'), 'Der Dateiinhalt steht in der Meldung');
// Schreiben: die vorhandene Datei bleibt, wie sie war.
$client->putFile('unberuehrt.txt', $nutzlast);
assertSame('URSPRUNG', (string)file_get_contents($opfer), 'Die Datei wurde überschrieben');
@unlink($opfer);
@unlink($nutzlast);
@rmdir($basis . '/eimer');
@rmdir($basis);
});
test('Eine abgewiesene Adresse übernimmt auch sonst nichts', function () use ($wurzel) {
// Sonst stünde die Hälfte der Angaben schon in der Datenbank, während
// das Formular einen Fehlschlag meldet.
$quelle = (string)file_get_contents($wurzel . '/src/Controllers/Admin/SelfBackupAdminController.php');
$beginn = strpos($quelle, 'public function saveSettings(');
assertTrue($beginn !== false, 'saveSettings() fehlt');
$pruefung = strpos($quelle, 'endpunktTauglich($endpunkt)', $beginn);
$schreibt = strpos($quelle, 'speichereEinstellung(', $beginn);
assertTrue($pruefung !== false, 'Der Endpunkt wird beim Speichern nicht geprüft');
assertTrue($pruefung < $schreibt, 'Es wird geschrieben, bevor die Adresse geprüft ist');
});
test('curl bekommt die Protokollgrenze als zweite Schranke', function () use ($wurzel) {
$quelle = (string)file_get_contents($wurzel . '/src/Backup/S3Client.php');
assertTrue(str_contains($quelle, 'CURLOPT_PROTOCOLS'), 'Die Protokollgrenze fehlt');
assertTrue(str_contains($quelle, 'CURLOPT_REDIR_PROTOCOLS'), 'Weiterleitungen sind unbegrenzt');
});
// ================================================================
// Zurücksetzen: Zusage muss beim Server ankommen
// ================================================================
test('Das Zurücksetzen verlangt eine Zusage, die abgeschickt wird', function () use ($wurzel) {
$seite = (string)file_get_contents($wurzel . '/templates/assets/show.php');
// Ohne `name` erreicht das Kästchen den Server nie.
assertTrue(
(bool)preg_match('/id="inv-delete-wipe-ack"[^>]*name="intune_wipe_ack"/', $seite)
|| (bool)preg_match('/name="intune_wipe_ack"[^>]*id="inv-delete-wipe-ack"/', $seite),
'Das Bestätigungskästchen wird nicht mitgeschickt'
);
$steuerung = (string)file_get_contents($wurzel . '/src/Controllers/AssetController.php');
assertTrue(str_contains($steuerung, "intune_wipe_ack"), 'Der Server prüft die Zusage nicht');
assertTrue(
(bool)preg_match("/'wipe'.*intune_wipe_ack/s", $steuerung),
'Die Prüfung hängt nicht am Zurücksetzen'
);
});
// ================================================================
// Verknüpfungen: Standortgrenze der Geräte
// ================================================================
test('Die Verknüpfungssuche trägt die Standortgrenze mit', function () use ($wurzel) {
$manager = (string)file_get_contents($wurzel . '/src/Content/PageLinkManager.php');
// Alle drei Wege an ein Gerät nehmen die Grenze entgegen.
foreach (['getLinkedObjects', 'searchObjects', 'findObject'] as $weg) {
assertTrue(
(bool)preg_match('/function ' . $weg . '\((?:[^)]*)\$scopes/s', $manager),
'Der Weg ' . $weg . ' kennt keine Grenze'
);
}
assertTrue(str_contains($manager, 'scopeParts'), 'Die Grenze wird nicht eingesetzt');
$steuerung = (string)file_get_contents($wurzel . '/src/Controllers/PageController.php');
assertTrue(
str_contains($steuerung, "locationScopeSql('view_assets', 'o.location_id')"),
'Der Controller bildet die Standortgrenze nicht'
);
// Jeder Aufruf reicht sie auch durch.
assertSame(
3,
preg_match_all('/objectScopes\(\)/', $steuerung) - 1,
'Nicht jeder Aufruf reicht die Grenze durch'
);
});
// ================================================================
// Fernwartung: kein ausführbares Schema im Verweis
// ================================================================
test('Eine Verbindungs-Vorlage darf im Browser keinen Code ausführen', function () {
// Die mitgelieferten Vorgaben bleiben gültig.
assertTrue(RemoteAccess::vorlageTauglich('teamviewer10://control?device={id}'), 'TeamViewer wird abgewiesen');
assertTrue(RemoteAccess::vorlageTauglich('anydesk:{id}'), 'AnyDesk wird abgewiesen');
assertTrue(RemoteAccess::vorlageTauglich('https://fernwartung.example.org/{id}'), 'https wird abgewiesen');
// Diese führen im Browser aus, statt ein Programm anzusprechen.
assertFalse(RemoteAccess::vorlageTauglich('javascript:alert(1)'), 'javascript: gilt als tauglich');
assertFalse(RemoteAccess::vorlageTauglich('JaVaScRiPt:alert(1)'), 'Groß-/Kleinschreibung hebelt die Prüfung aus');
assertFalse(RemoteAccess::vorlageTauglich("java\tscript:alert(1)"), 'Ein Steuerzeichen hebelt die Prüfung aus');
assertFalse(RemoteAccess::vorlageTauglich(' javascript:alert(1)'), 'Führender Abstand hebelt die Prüfung aus');
assertFalse(RemoteAccess::vorlageTauglich('data:text/html,<script>alert(1)</script>'), 'data: gilt als tauglich');
assertFalse(RemoteAccess::vorlageTauglich('vbscript:msgbox(1)'), 'vbscript: gilt als tauglich');
assertFalse(RemoteAccess::vorlageTauglich('file:///etc/passwd'), 'file: gilt als tauglich');
assertFalse(RemoteAccess::vorlageTauglich('{id}'), 'Eine Vorlage ohne Schema gilt als tauglich');
});
test('Eine früher gespeicherte Vorlage wirkt nachträglich nicht', function () use ($wurzel) {
// Bestandsinstanzen können einen Wert von vor der Prüfung führen;
// er darf beim Ausgeben nicht mehr greifen.
$quelle = (string)file_get_contents($wurzel . '/src/Assets/RemoteAccess.php');
assertTrue(
(bool)preg_match('/vorlageTauglich\(\$eigene\)\s*\?\s*\$eigene\s*:\s*\'\'/', $quelle),
'Die Ausgabe prüft die hinterlegte Vorlage nicht'
);
$steuerung = (string)file_get_contents($wurzel . '/src/Controllers/AdminAssetSyncController.php');
assertTrue(
str_contains($steuerung, 'RemoteAccess::vorlageTauglich($wert)'),
'Beim Speichern wird nicht geprüft'
);
});
};