Fix live exploitierte RouterOS-CLI-Injection + Parser-Datenverlust
Deep-Dive-Gegencheck mit echtem Exploit-Nachweis gegen den Testrouter: - RouterOSCommand.cliLine quotete Werte nur bei Leerzeichen und escapte eingebettete Anführungszeichen nie. Ein Kommentar wie test" ; :log warning "X schloss das Quoting vorzeitig und ließ RouterOS den Rest als zweiten Befehl ausführen. Live exploitiert (injizierter script,warning-Log-Eintrag) und live als behoben bestätigt. Betraf jede Schreiboperation über SSH - auf dem aktuellen Testrouter ist www-ssl deaktiviert, REST also unerreichbar, der Bug war aktiv. - RouterOSCliParser.keyValues nahm an, print terse quote mehrwortige Werte - live an zwei Menüs widerlegt (RouterOS 7.24.4 quotet dort nichts). Trunkierte jeden mehrwortigen Wert beim ersten Leerzeichen. Fix: Token-Scan statt Regex. - SSHTransport.fetchFieldValues defensiv gegen dieselbe Injection-Klasse gehärtet (aktuell nur hartkodiert aufgerufen, aber generische API). 2 neue Regressionstests, alle 101 Unit-Tests grün. Details in bugs.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -134,10 +134,22 @@ struct RouterOSCommand: Equatable, Identifiable {
|
||||
.joined(separator: " ")
|
||||
}
|
||||
|
||||
/// Always quotes, with `\` and `"` backslash-escaped inside — the previous version only
|
||||
/// quoted when the value contained a space and never escaped embedded quotes at all, which
|
||||
/// let a value like `test" ; :log warning "INJECTED` (a completely plausible free-text
|
||||
/// comment/SSID/hostname) close the quoted argument early and inject a second, independent
|
||||
/// RouterOS command after the `;` — RouterOS' console uses `;` as a statement separator, same
|
||||
/// as the injection risk `SSHTransport.runDiagnosticCommand`'s doc comment already flags for
|
||||
/// its own caller-sanitized input. Live-confirmed exploitable and live-confirmed fixed
|
||||
/// (2026-09-17, bugs.md): unescaped, this executed an injected `:log warning` as a second
|
||||
/// command; escaping `\`/`"` (verified live to be RouterOS' own escape syntax — `\"` and `\\`
|
||||
/// both round-tripped correctly through `print detail`) closes it. Quoting unconditionally
|
||||
/// (not just "if it contains a space") also verified live to be always accepted, including for
|
||||
/// plain single-word values and `yes`/`no` booleans — no reason left to special-case those.
|
||||
private static func quoteIfNeeded(_ value: String) -> String {
|
||||
// An empty value must render as an explicit `""`, not a bare `key=` with nothing after
|
||||
// the `=` — that's how RouterOS' CLI represents "clear this field" versus a syntax error.
|
||||
if value.isEmpty { return "\"\"" }
|
||||
return value.contains(" ") ? "\"\(value)\"" : value
|
||||
let escaped = value
|
||||
.replacingOccurrences(of: "\\", with: "\\\\")
|
||||
.replacingOccurrences(of: "\"", with: "\\\"")
|
||||
return "\"\(escaped)\""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,20 +98,57 @@ enum RouterOSCliParser {
|
||||
value == "true" || value == "yes"
|
||||
}
|
||||
|
||||
/// RouterOS' `print terse` does *not* quote multi-word values at all — live-confirmed
|
||||
/// (2026-09-17, RouterOS 7.24.4, two independent menus: `/ip firewall address-list` and
|
||||
/// `/interface ethernet`): a comment set to `"hello world"` comes back completely unquoted as
|
||||
/// literal `comment=hello world name=ether2 ...`, indistinguishable from a second field by
|
||||
/// punctuation alone. The previous regex-based parser (`("[^"]*"|\S+)`) assumed quoting
|
||||
/// sometimes happened and matched only a bare `\S+` otherwise, so it silently truncated every
|
||||
/// such value at the first space and dropped the remaining words entirely — this app's own
|
||||
/// `RouterOSCliParserTests.testParseInterfacesMatchesLiveHexOutput` already contained an
|
||||
/// exact real-device dump with this exact shape (`last-link-up-time=2026-09-15 20:19:19`) and
|
||||
/// nobody noticed, because that particular field happens not to be read by any curated
|
||||
/// schema — the bug is real and was simply invisible until a multi-word field someone
|
||||
/// actually reads (e.g. any comment shown via the Expert tool's generic "Weitere Parameter")
|
||||
/// got truncated.
|
||||
///
|
||||
/// Fix: token-scan instead of regex-match. A whitespace-separated word only starts a *new*
|
||||
/// field if it itself looks like `key=...` (letters/digits/dots/hyphens then `=`); anything
|
||||
/// else is appended to the value of whichever field started most recently. This correctly
|
||||
/// handles the common case (free text with spaces) at the cost of one known, irreducible edge
|
||||
/// case given RouterOS' ambiguous unquoted format: a value whose text itself contains a
|
||||
/// `word=` substring (e.g. a comment literally reading "config=broken") gets misread as a new
|
||||
/// field starting mid-value — rare, and no worse than the previous parser's 100% failure rate
|
||||
/// on every multi-word value. A defensive quote-strip is kept in case some other menu/RouterOS
|
||||
/// version *does* quote (unverified either way beyond the two menus tested live).
|
||||
private static func keyValues(from text: String) -> [String: String] {
|
||||
var result: [String: String] = [:]
|
||||
let pattern = #"([.a-zA-Z0-9-]+)=("[^"]*"|\S+)"#
|
||||
guard let regex = try? NSRegularExpression(pattern: pattern) else { return result }
|
||||
let nsText = text as NSString
|
||||
let matches = regex.matches(in: text, range: NSRange(location: 0, length: nsText.length))
|
||||
for match in matches {
|
||||
let key = nsText.substring(with: match.range(at: 1))
|
||||
var value = nsText.substring(with: match.range(at: 2))
|
||||
let keyPattern = try! NSRegularExpression(pattern: #"^[.a-zA-Z0-9-]+="#)
|
||||
|
||||
var currentKey: String?
|
||||
var currentValueWords: [String] = []
|
||||
func commitCurrentField() {
|
||||
guard let key = currentKey else { return }
|
||||
var value = currentValueWords.joined(separator: " ")
|
||||
if value.hasPrefix("\""), value.hasSuffix("\""), value.count >= 2 {
|
||||
value = String(value.dropFirst().dropLast())
|
||||
}
|
||||
result[key] = value
|
||||
}
|
||||
|
||||
for word in text.split(separator: " ", omittingEmptySubsequences: true) {
|
||||
let token = String(word)
|
||||
let range = NSRange(token.startIndex..<token.endIndex, in: token)
|
||||
if keyPattern.firstMatch(in: token, range: range) != nil {
|
||||
commitCurrentField()
|
||||
let parts = token.split(separator: "=", maxSplits: 1, omittingEmptySubsequences: false)
|
||||
currentKey = String(parts[0])
|
||||
currentValueWords = parts.count > 1 ? [String(parts[1])] : [""]
|
||||
} else if currentKey != nil {
|
||||
currentValueWords.append(token)
|
||||
}
|
||||
}
|
||||
commitCurrentField()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,8 +173,17 @@ final class SSHTransport: RouterOSTransport {
|
||||
/// wrong (a real make-static conversion — confirmed via Winbox — wasn't found by it). `:put`
|
||||
/// inside `:foreach` prints one value per line, so this splits on newlines, not ";" (the
|
||||
/// semicolon-joined shape only applies to a single `:put [<menuPath> find ...]` list).
|
||||
/// `whereValue` is quoted/escaped the same way as `RouterOSCommand`'s CLI rendering (see its
|
||||
/// `quoteIfNeeded` doc comment for the live-confirmed injection this prevents) — both current
|
||||
/// callers only ever pass the hardcoded literal `"no"`, but this is a generic, reusable
|
||||
/// `RouterOSTransport` method, so a future caller passing user/device-controlled text (e.g. a
|
||||
/// hostname) must not silently reopen the same command-injection class this app has already
|
||||
/// had to fix once.
|
||||
func fetchFieldValues(menuPath: String, restPath: String, whereField: String, whereValue: String, returnField: String) async throws -> Set<String> {
|
||||
let script = ":foreach i in=[\(menuPath) find \(whereField)=\(whereValue)] do={:put [\(menuPath) get $i \(returnField)]}"
|
||||
let escapedValue = whereValue
|
||||
.replacingOccurrences(of: "\\", with: "\\\\")
|
||||
.replacingOccurrences(of: "\"", with: "\\\"")
|
||||
let script = ":foreach i in=[\(menuPath) find \(whereField)=\"\(escapedValue)\"] do={:put [\(menuPath) get $i \(returnField)]}"
|
||||
let output = try await run(script)
|
||||
let values = output
|
||||
.split(whereSeparator: \.isNewline)
|
||||
|
||||
@@ -85,4 +85,22 @@ final class RouterOSCliParserTests: XCTestCase {
|
||||
XCTAssertEqual(interfaces.map(\.name), ["ether1", "ether2", "bridge", "lo"])
|
||||
XCTAssertEqual(interfaces.map(\.type), ["ether", "ether", "bridge", "loopback"])
|
||||
}
|
||||
|
||||
/// Regression test for bugs.md (2026-09-17, "erneuter Gegencheck"-Durchgang): RouterOS'
|
||||
/// `print terse` does not quote multi-word values at all (live-confirmed, RouterOS 7.24.4,
|
||||
/// two menus) — a comment "multi word test value" comes back as literal unquoted
|
||||
/// `comment=multi word test value name=ether2 ...`. The old regex parser silently truncated
|
||||
/// this to just "multi" and dropped "word test value" entirely; `keyValues` now token-scans
|
||||
/// instead, only starting a new field on a token that itself looks like `key=...`.
|
||||
func testParseGenericItemsPreservesUnquotedMultiWordValue() {
|
||||
let raw = "1 RS comment=multi word test value name=ether2 default-name=ether2 mtu=1500"
|
||||
|
||||
let items = RouterOSCliParser.parseGenericItems(raw)
|
||||
|
||||
XCTAssertEqual(items.count, 1)
|
||||
XCTAssertEqual(items[0].fields["comment"], "multi word test value")
|
||||
XCTAssertEqual(items[0].fields["name"], "ether2")
|
||||
XCTAssertEqual(items[0].fields["default-name"], "ether2")
|
||||
XCTAssertEqual(items[0].fields["mtu"], "1500")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ final class RouterOSCommandBuilderTests: XCTestCase {
|
||||
summary: "test"
|
||||
)
|
||||
|
||||
XCTAssertEqual(command.cliLine, "/ip route set [find .id=*1] comment=\"\"")
|
||||
XCTAssertEqual(command.cliLine, "/ip route set [find .id=\"*1\"] comment=\"\"")
|
||||
}
|
||||
|
||||
func testCliLineRendersSortedQuotedArgumentsForAdd() {
|
||||
@@ -96,7 +96,7 @@ final class RouterOSCommandBuilderTests: XCTestCase {
|
||||
summary: "test"
|
||||
)
|
||||
|
||||
XCTAssertEqual(command.cliLine, "/interface pppoe-client add password=\"a secret\" user=user@isp")
|
||||
XCTAssertEqual(command.cliLine, "/interface pppoe-client add password=\"a secret\" user=\"user@isp\"")
|
||||
}
|
||||
|
||||
func testCliLineRendersFindLookupForSet() {
|
||||
@@ -109,7 +109,7 @@ final class RouterOSCommandBuilderTests: XCTestCase {
|
||||
summary: "test"
|
||||
)
|
||||
|
||||
XCTAssertEqual(command.cliLine, "/interface wireless set [find name=wlan1] ssid=Home")
|
||||
XCTAssertEqual(command.cliLine, "/interface wireless set [find name=\"wlan1\"] ssid=\"Home\"")
|
||||
}
|
||||
|
||||
func testCliLineRendersFindLookupForAction() {
|
||||
@@ -122,6 +122,25 @@ final class RouterOSCommandBuilderTests: XCTestCase {
|
||||
summary: "test"
|
||||
)
|
||||
|
||||
XCTAssertEqual(command.cliLine, "/ip dhcp-server lease make-static [find .id=*7]")
|
||||
XCTAssertEqual(command.cliLine, "/ip dhcp-server lease make-static [find .id=\"*7\"]")
|
||||
}
|
||||
|
||||
/// Regression test for bugs.md #1 (2026-09-17, "erneuter Gegencheck"-Durchgang): a free-text
|
||||
/// value containing an embedded `"` followed by `;` used to close the CLI argument's quoting
|
||||
/// early and let RouterOS' console treat the rest as a second, independent command —
|
||||
/// live-confirmed exploitable (`:log warning "..."` executed as its own command via a comment
|
||||
/// field) and live-confirmed fixed by escaping `\`/`"` and always quoting.
|
||||
func testCliLineEscapesEmbeddedQuotesPreventingCommandInjection() {
|
||||
let command = RouterOSCommand.add(
|
||||
menuPath: "/ip firewall address-list",
|
||||
restPath: "ip/firewall/address-list",
|
||||
arguments: ["comment": "test\" ; :log warning \"INJECTED"],
|
||||
summary: "test"
|
||||
)
|
||||
|
||||
XCTAssertEqual(
|
||||
command.cliLine,
|
||||
"/ip firewall address-list add comment=\"test\\\" ; :log warning \\\"INJECTED\""
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,6 +101,97 @@ Schreibvorgangs aussetzt.
|
||||
|
||||
---
|
||||
|
||||
## 2026-09-17 (Nachtrag: "erneuter Gegencheck mit echtem Deep Dive")
|
||||
|
||||
Zweite, tiefere Testrunde auf expliziten Nutzerwunsch. Diesmal inkl. echter Exploit-Verifikation
|
||||
gegen den Router (nicht nur Code-Lesen) — beide folgenden Funde live nachgewiesen und live
|
||||
gegenverifiziert, dass der Fix greift, mit anschließendem Aufräumen der Testartefakte.
|
||||
|
||||
### 5. RouterOS-CLI-Injection über beliebige Textfelder (SSH-Transportpfad)
|
||||
**Status:** fixed (live exploitiert UND live als behoben bestätigt, siehe unten)
|
||||
**Confidence:** sehr hoch — kein Verdacht, sondern reproduzierter Exploit gegen den echten Router
|
||||
|
||||
`RouterOSCommand.cliLine` (`RouterOSCommand.swift`) baute CLI-Zeilen für den SSH-Transport per
|
||||
String-Interpolation. Die alte `quoteIfNeeded(_:)` quotete einen Wert nur, wenn er ein Leerzeichen
|
||||
enthielt, und escapte darin enthaltene `"`-Zeichen nie. RouterOS' Konsole behandelt `;` als
|
||||
Befehlstrenner (dieselbe Gefahr, die `SSHTransport.runDiagnosticCommand`s Doku-Kommentar für
|
||||
eigene, aufruferseitig sanitisierte Eingaben bereits benennt) — ein Wert wie
|
||||
`test" ; :log warning "INJECTED` (ein völlig plausibler freier Kommentar/SSID/Hostname) schloss
|
||||
das Anführungszeichen vorzeitig und ließ den Rest als zweiten, unabhängigen RouterOS-Befehl laufen.
|
||||
|
||||
**Exploit live bestätigt** (Testrouter, aufgeräumt danach): Befehl
|
||||
`/ip firewall address-list add list=injection-test address=10.10.10.10 comment="test" ; :log warning "INJECTED-VIA-COMMENT-FIELD"`
|
||||
über SSH ausgeführt → Log zeigt `script,warning INJECTED-VIA-COMMENT-FIELD` als eigenständig
|
||||
ausgeführten zweiten Befehl, ausgelöst rein durch ein Kommentarfeld.
|
||||
|
||||
Betroffen: jeder Text, der über den SSH-Transport in ein `RouterOSCommand` läuft — praktisch jedes
|
||||
Feld im Setup-Wizard (SSID, Kommentare, DNS-Server, …) und jedes Freitextfeld im Experte-Tab
|
||||
("Weitere Parameter"). Der REST-Pfad ist NICHT betroffen (JSON-Encoding, kein String-Interpolieren
|
||||
— siehe `RestTransport.apply`). Auf dem aktuellen hAP-lite-Testrouter ist `www-ssl` deaktiviert
|
||||
(`/ip service print` bestätigt: Zeile 9, `X` = disabled) — REST ist also gar nicht erreichbar, jede
|
||||
Schreiboperation läuft aktuell über SSH. Der Bug war damit live aktiv, nicht nur theoretisch.
|
||||
|
||||
**Fix:** `quoteIfNeeded` quotet jetzt immer und escaped `\` → `\\`, `"` → `\"` innerhalb der
|
||||
Anführungszeichen — live verifiziert, dass das RouterOS' eigene Escape-Syntax ist (`\"`/`\\`
|
||||
rundeten korrekt durch `print detail`). Unconditionelles Quoting live als unproblematisch bestätigt
|
||||
(auch für einwortige Werte und `yes`/`no`-Booleans anstandslos akzeptiert). Gleiche Lücke defensiv
|
||||
auch in `SSHTransport.fetchFieldValues` geschlossen (aktuell nur mit hartkodiertem `"no"`
|
||||
aufgerufen, aber generische `RouterOSTransport`-Methode). Exploit-PoC danach erneut gegen den
|
||||
Router gefahren — kein injizierter Log-Eintrag mehr, Kommentar korrekt als reiner Text gespeichert.
|
||||
Neuer Regressionstest `testCliLineEscapesEmbeddedQuotesPreventingCommandInjection`. Build + alle
|
||||
101 Unit-Tests grün.
|
||||
|
||||
### 6. Genereller RouterOS-Antwort-Parser trunkiert mehrwortige Werte
|
||||
**Status:** fixed (Ursache live nachgewiesen, Fix per Unit-Test abgesichert)
|
||||
**Confidence:** sehr hoch — live gegen zwei unabhängige Menüs nachgewiesen
|
||||
|
||||
`RouterOSCliParser.keyValues(from:)` nahm an, `print terse` quote mehrwortige Werte in
|
||||
Anführungszeichen (Regex `("[^"]*"|\S+)`). Live-Test (zwei unabhängige Menüs, `/ip firewall
|
||||
address-list` und `/interface ethernet`, RouterOS 7.24.4) zeigt: **RouterOS quotet dort gar
|
||||
nichts** — ein Kommentar `"multi word test value"` kommt als literales, unquotiertes
|
||||
`comment=multi word test value name=ether2 ...` zurück. Der alte Regex-Parser matchte dafür nur
|
||||
`\S+` und schnitt den Wert beim ersten Leerzeichen ab — "word test value" ging komplett verloren,
|
||||
ohne Fehler, ohne Warnung.
|
||||
|
||||
Betrifft praktisch jedes mehrwortige Freitextfeld, das über den generischen Parser gelesen wird —
|
||||
vor allem das Experte-Tab-"Weitere Parameter (frei)"-Grid, das laut eigenem Doku-Kommentar "fast
|
||||
alle" Menüs generisch parst. Der Bug war bereits indirekt im eigenen Testcode sichtbar: der
|
||||
existierende Live-Dump-Test `testParseInterfacesMatchesLiveHexOutput` enthält exakt dieses Muster
|
||||
(`last-link-up-time=2026-09-15 20:19:19`, ein echter Gerätedump) — nur fiel es nie auf, weil dieses
|
||||
Feld von keinem kuratierten Schema gelesen wird.
|
||||
|
||||
**Fix:** `keyValues` von Regex-Matching auf Token-Scanning umgestellt — ein Leerzeichen-getrenntes
|
||||
Wort startet nur dann ein neues Feld, wenn es selbst wie `key=...` aussieht; alles andere wird an
|
||||
den Wert des zuletzt begonnenen Feldes angehängt. Bekannter Restfall (inhärent durch RouterOS'
|
||||
mehrdeutiges unquotiertes Format, nicht clientseitig lösbar): ein Wert, der selbst ein
|
||||
`wort=`-Muster enthält (z.B. ein Kommentar "config=broken"), wird fälschlich als neues Feld
|
||||
gelesen — deutlich seltener als die vorherige 100%-Fehlerquote bei jedem mehrwortigen Wert. Neuer
|
||||
Regressionstest `testParseGenericItemsPreservesUnquotedMultiWordValue`, abgeleitet vom echten
|
||||
Live-Dump. Build + alle 101 Unit-Tests grün.
|
||||
|
||||
### 7. Beobachtung (kein Fix): Isolation wirkt nicht rückwirkend auf bereits bestehende Verbindungen
|
||||
**Status:** offen (bewusst nicht automatisch gefixt — Risikoabwägung, siehe unten)
|
||||
**Confidence:** hoch (Regel-Reihenfolge im Code nachvollzogen), nicht live reproduziert
|
||||
|
||||
`FirewallConfig.buildCommands()` (`FirewallConfig.swift`) setzt die Regel "forward
|
||||
established,related → accept" auf Position 4, die Isolations-Drop-Regeln erst ab Position
|
||||
`filterCommands.count` (7+). Für eine neue Verbindung zwischen zwei isolierten Netzen ist das
|
||||
korrekt (erstes Paket hat `connection-state=new`, trifft also nicht Regel 4, sondern die
|
||||
Isolations-Regel weiter hinten). Für eine zum Zeitpunkt des Anwendens bereits **bestehende**
|
||||
(im Conntrack getrackte) Verbindung zwischen zwei gerade erst als isoliert markierten Netzen
|
||||
greift dagegen weiterhin Regel 4 zuerst — sie bleibt offen, bis sie von selbst endet. Das ist
|
||||
Standardverhalten jeder stateful/conntrack-basierten Firewall (RouterOS, iptables, pf, …), kein
|
||||
App-spezifischer Fehler, und der bisher einzige Live-Test dieser Funktion (M8, 2026-09-15) betraf
|
||||
einen frisch aufgeteilten Port ohne bestehende Verbindung — dieser Randfall wurde nie geprüft.
|
||||
|
||||
Nicht automatisch gefixt: ein Fix würde bedeuten, beim Aktivieren der Isolation gezielt
|
||||
`/ip firewall connection remove` für die betroffenen Netzpaare auszulösen — ein zusätzlicher,
|
||||
potenziell überraschender Seiteneffekt (kappt aktive Verbindungen, die der Nutzer evtl. bewusst
|
||||
offen hat), der über die reine Bugfix-Aufgabe dieses Durchgangs hinausgeht und eine bewusste
|
||||
Produktentscheidung ist, keine reine Korrektur. Nur als Beobachtung vermerkt.
|
||||
|
||||
---
|
||||
|
||||
## Noch nicht geprüft / außerhalb dieses Durchgangs
|
||||
|
||||
- UI-Interaktion selbst (Klickpfade, Darstellung) — nicht automatisierbar, siehe Hinweis oben.
|
||||
|
||||
@@ -250,3 +250,28 @@ dokumentiert und direkt gefixt:
|
||||
Fix: neuer `beginWrite()`/`endWrite()`-Zähler, Herzschlag pausiert währenddessen.
|
||||
|
||||
Build grün, alle 99 Unit-Tests grün. Details je Fund in `bugs.md`.
|
||||
|
||||
### 12. Erneuter Deep-Dive-Gegencheck mit echtem Exploit-Nachweis (bugs.md #5-#7, 2026-09-17)
|
||||
**Status:** fixed (2 kritische Live-Exploits nachgewiesen+gefixt+gegenverifiziert, 1 Beobachtung offen dokumentiert)
|
||||
|
||||
Auf Nutzerwunsch ("erneuter Gegencheck mit echtem Deep Dive, alles testen") tiefer weitergesucht,
|
||||
diesmal mit echten Exploit-Versuchen gegen den Router statt nur Code-Lesen:
|
||||
|
||||
- **#5 RouterOS-CLI-Injection** (schwerwiegendster Fund der ganzen Session): `RouterOSCommand.cliLine`
|
||||
quotete Werte nur bei Leerzeichen und escapte nie eingebettete `"`. Ein Kommentar wie
|
||||
`test" ; :log warning "X` schloss das Quoting vorzeitig und ließ RouterOS den Rest als zweiten,
|
||||
unabhängigen Befehl ausführen — live exploitiert (Log zeigt injizierten `script,warning`-Eintrag)
|
||||
und live als behoben bestätigt (kein injizierter Eintrag mehr, Kommentar korrekt gespeichert).
|
||||
Betraf jeden Text im Setup-Wizard/Experte-Tab, sobald über SSH geschrieben wird — und auf dem
|
||||
aktuellen Testrouter ist `www-ssl` deaktiviert, REST also gar nicht erreichbar: jede
|
||||
Schreiboperation lief bereits über SSH, der Bug war aktiv, nicht nur theoretisch.
|
||||
- **#6 Generischer Parser trunkiert mehrwortige Werte**: `print terse` quotet in RouterOS 7.24.4
|
||||
gar keine mehrwortigen Werte (live an zwei Menüs bestätigt) — der alte Regex-Parser schnitt
|
||||
jeden solchen Wert beim ersten Leerzeichen ab, unbemerkt seit Session-Beginn (war bereits im
|
||||
eigenen Live-Dump-Testcode sichtbar, fiel aber nie auf). Fix: Token-Scan statt Regex.
|
||||
- **#7 Beobachtung, kein Fix**: Netzwerk-Isolation wirkt nicht rückwirkend auf bereits bestehende
|
||||
(conntrack-getrackte) Verbindungen — Standardverhalten jeder stateful Firewall, nie getestet,
|
||||
bewusst nicht automatisch "gefixt" (Conntrack-Flush wäre eine Produktentscheidung, kein reiner
|
||||
Bugfix).
|
||||
|
||||
Build grün, alle 101 Unit-Tests grün (2 neue Regressionstests). Details in `bugs.md`.
|
||||
|
||||
Reference in New Issue
Block a user