round58: Router-IP vom vollstaendigen Portscan im Sammel-Scan ausnehmen (FritzOS-Angriffserkennung reagierte auf vollen masscan-Scan gegen sich selbst)

This commit is contained in:
2026-07-26 13:26:38 +02:00
parent 0a1f3143d7
commit 403a052a0f

View File

@@ -50,6 +50,28 @@ function formatToolsUsed(toolsUsed: {
return `${masscanPart}, ${httpxPart}`;
}
/**
* Löst FRITZBOX_HOST (Umgebungsvariable, siehe /api/scan/fritzbox weiter
* unten) zur IP-Adresse des Routers selbst auf - wird genutzt, um genau
* dieses eine Gerät vom vollständigen masscan-Portscan im Sammel-Scan
* auszunehmen (siehe Kommentar dort). Ist FRITZBOX_HOST bereits eine
* IP-Adresse, wird sie direkt übernommen, sonst per DNS aufgelöst. Liefert
* null, wenn nicht konfiguriert oder nicht auflösbar - dann greift einfach
* keine Ausnahme, wie vor round58.
*/
async function resolveRouterIp(): Promise<string | null> {
const host = process.env.FRITZBOX_HOST;
if (!host) return null;
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return host;
try {
const { lookup } = await import("node:dns/promises");
const { address } = await lookup(host);
return address;
} catch {
return null;
}
}
/**
* Scannt EIN Gerät (Ports, Titel/Favicon, Softwareerkennung) und schreibt die
* Ergebnisse in die Datenbank. Ausgelagert aus der Route, damit sowohl der
@@ -249,7 +271,35 @@ export async function scanRoutes(app: FastifyInstance): Promise<void> {
// keine masscan-/httpx-Aufrufe mehr, nur noch die (leichtgewichtigen,
// reinen HTTP-)Detailabfragen pro Port (Titel, Favicon,
// Softwareerkennung).
const deviceIps = devices.map((d) => d.ip);
// NEU (round58): Die FritzBox (der Router selbst, NICHT ein Repeater
// o. ä.) hat eine eingebaute Angriffs-/Portscan-Erkennung
// (Sicherheitseinstellungen -> Angriffserkennung). Ein voller
// masscan-Lauf über 1-65535 Ports gegen die eigene Router-IP sieht
// dafür genau wie ein klassischer Portscan-Angriff aus - die
// FritzBox blockt/ignoriert die Scan-Quelle daraufhin offenbar
// kurzzeitig, sodass selbst der direkt danach laufende normale
// 80/443-Retry (siehe scanDeviceServices) noch nichts findet (siehe
// Bugreport: "0 Dienst(e) gefunden (Ports: keine)" bei der FritzBox
// selbst, während ein FRITZ!Repeater im selben Lauf ganz normal alle
// Dienste liefert - Repeater haben typischerweise nicht dieselbe
// Absicherung wie der Router, der ja die Sicherheits-Gateway-Rolle
// fürs ganze Netz hat). Die Router-IP wird daher gezielt aus dem
// masscan-Batch ausgenommen - für sie greift stattdessen weiter
// unten ganz normal die schonende "übliche Portliste"-Prüfung
// (kein vollständiger Portscan, kein masscan), ihre echten Dienste
// kennen wir ohnehin schon zuverlässig über den eigenen
// TR-064-Scanner (siehe scanner/fritzbox.ts).
const routerIp = await resolveRouterIp();
const deviceIps = devices.map((d) => d.ip).filter((ip) => ip !== routerIp);
if (routerIp && devices.some((d) => d.ip === routerIp)) {
logRepo.logScan({
type: "device",
targetId: null,
level: "info",
message: `Sammel-Scan: Router-IP ${routerIp} vom vollständigen Portscan ausgenommen (FritzOS-Angriffserkennung reagiert empfindlich auf volle Portscans gegen sich selbst) - wird stattdessen mit der üblichen Portliste geprüft.`,
});
}
// Sichtbarer Fortschritt VOR dem eigentlichen Geräte-Scan: ohne das
// stand die Anzeige die ganze Vorabscan-Zeit über bei "1 von 120" und
@@ -391,20 +441,22 @@ export async function scanRoutes(app: FastifyInstance): Promise<void> {
return;
}
try {
// WICHTIG (Bugfix round56): masscanBatch.get(device.ip) liefert
// "undefined" in ZWEI völlig unterschiedlichen Fällen: (a) der
// gesamte masscan-Batch ist fehlgeschlagen (masscanBatch ===
// null), ODER (b) der Batch war erfolgreich, aber DIESES
// Gerät hat schlicht KEINE offenen Ports - ein bei Handys/
// Smart-Geräten völlig normales, gültiges Ergebnis. Der
// bisherige Code hat beide Fälle identisch behandelt und
// damit für JEDES Gerät mit null gefundenen Ports fälschlich
// einen kompletten, langsamen 65535-Port-TCP-Fallback-Scan
// ausgelöst - genau das, was masscan eigentlich vermeiden
// sollte. Ein leeres Array ist in JS truthy (nur undefined/
// null/0/""/NaN/false sind falsy), daher reicht es, hier statt
// "undefined" bewusst ein leeres Array zu liefern, wenn der
// Batch als Ganzes erfolgreich war.
// Router-IP: siehe Kommentar oben bei resolveRouterIp() -
// bewusst NICHT preFetched/fullScan nutzen, sondern die
// schonende Standard-Portliste (kein voller 1-65535-Scan
// gegen den Router selbst, keine Angriffserkennung
// getriggert).
const isRouter = routerIp !== null && device.ip === routerIp;
if (isRouter) {
const result = await performDeviceScan(device, false);
created += result.created;
updated += result.updated;
allStale.push(...result.staleServices);
allNameChanges.push(...result.nameChanges);
allNew.push(...result.newServices);
return;
}
const masscanPorts = masscanBatch ? masscanBatch.get(device.ip) ?? [] : undefined;
const preFetched: PreFetchedScanData = {
masscanAttempted: true,