From 403a052a0f1b1d6f6201ea76a797760f1c441c7f Mon Sep 17 00:00:00 2001 From: Dicken Date: Sun, 26 Jul 2026 13:26:38 +0200 Subject: [PATCH] round58: Router-IP vom vollstaendigen Portscan im Sammel-Scan ausnehmen (FritzOS-Angriffserkennung reagierte auf vollen masscan-Scan gegen sich selbst) --- apps/backend/src/routes/scan.ts | 82 +++++++++++++++++++++++++++------ 1 file changed, 67 insertions(+), 15 deletions(-) diff --git a/apps/backend/src/routes/scan.ts b/apps/backend/src/routes/scan.ts index 31bc410..f4b862a 100644 --- a/apps/backend/src/routes/scan.ts +++ b/apps/backend/src/routes/scan.ts @@ -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 { + 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 { // 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 { 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,