
Nicht-destruktiver Patch-Status-Prüfer für SolarWinds ARM CVE-2026-28326, der den gRPC-Listener auf TCP 55555 abfragt, um VULNERABLE, PATCHED oder INCONCLUSIVE zu melden.
Eine unauthentifizierte Patch-Status-Prüfung für CVE-2026-28326, die unauthentifizierte Remote-Code-
Execution-Schwachstelle in SolarWinds Access Rights Manager (ARM), behoben in
ARM 2026.2.1
am 2026-09-17. CWE-798, CVSS 8.8 High
(CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H). Gemeldet an SolarWinds von Kai Huang von Armadin.
Das AV:A in diesem Vektor geht davon aus, dass TCP 55555 nur aus einem angrenzenden Netzwerk
erreichbar ist, was der Ausgangslage entspricht, für die ARMs eigene Firewall-Tools konzipiert sind;
wurde der Port breiter geöffnet, unterschätzt der Score die Exposition für diese Umgebung.
ARMs gRPC-Remoting-Listener auf TCP 55555 authentifiziert Peers mit einem Client-Zertifikat.
Ungepatchte Builds akzeptieren zudem eine Fallback-Anmeldeinformation, die von einem fest
einprogrammierten Schlüssel abgeleitet wird, der auf jeder Installation identisch ist, wodurch diese
Authentifizierung umgangen und ein unsicherer Deserialisierungspfad offengelegt wird. Das Ergebnis
ist Codeausführung als ARM-Dienstkonto, das standardmäßig als LocalSystem läuft.
Das Skript meldet, ob der Fix auf jedem Ziel vorhanden ist. Es liest die Statuscodes, die vom
Authentifizierungs-Interceptor zurückgegeben werden, und verwendet den Schlüssel nicht, authentifiziert
sich nicht und sendet keine Daten an den Deserialisierungspfad. Ein anderes Ergebnis als VULNERABLE
bedeutet nicht für sich genommen, dass ein Ziel gepatcht ist; siehe
.
# single target (port defaults to 55555)
./cve_2026_28326_check.py arm.example.com
# explicit port
./cve_2026_28326_check.py arm.example.com:55555
# several targets
./cve_2026_28326_check.py arm-a.example.com arm-b.example.com
# scan a list, one target per line ('#' comments allowed), compact output
./cve_2026_28326_check.py -f targets.txt --brief
# machine-readable output for pipelines
./cve_2026_28326_check.py -f targets.txt --json > results.json
Python 3.8+, nur Standardbibliothek — keine Drittanbieter-Pakete.
Führen Sie das Tool gegen den ARM-Server aus, nicht gegen einen Collector. Beide Rollen lauschen auf
55555, aber ein Collector lehnt externe Anfragen vor und nach dem Fix auf dieselbe Weise ab, sodass
das Tool ihn als INCONCLUSIVE meldet.
| Flag | Beschreibung |
|---|---|
TARGET | Ein oder mehrere HOST[:PORT]-Ziele; der Port ist standardmäßig 55555 |
-f, --targets-file FILE | Ziele aus einer Datei lesen (eine pro Zeile; #-Kommentare) |
-p, --port PORT | Standardport, wenn ein Ziel keinen angibt (Standard: 55555) |
--timeout SECS | Timeout pro Probe (Standard: 12) |
--workers N | Gleichzeitige Ziele (Standard: 16); die Ausgabe bleibt in Eingabereihenfolge |
-b, --brief | Eine ausgerichtete Zeile pro Ziel, zum Scannen vieler Hosts |
--json | Strukturiertes JSON ausgeben, einschließlich beider Proben pro Ziel |
--no-color | Farbige Ausgabe deaktivieren (berücksichtigt auch NO_COLOR und Nicht-TTY) |
Schwachstellenbehafteter Server (Standardausgabe; die [!]-Markierung und VULNERABLE werden auf
einem TTY rot angezeigt):
$ ./cve_2026_28326_check.py arm.example.com
[!] arm.example.com:55555: VULNERABLE [unavailable-on-server-role]
Remoting/Send returned 14 UNAVAILABLE on a listener answering as the server role, the pre-patch rejection path (<= 2026.2.0.42)
Gepatchter Server. Der gefixte Build gibt eine grpc-message zurück, die das Tool neben dem
Urteil ausgibt:
$ ./cve_2026_28326_check.py arm-dr.example.com
[+] arm-dr.example.com:55555: PATCHED [unauthenticated-gate]
Remoting/Send returned 16 UNAUTHENTICATED, the localhost and per-process-token gate introduced by the fix (>= 2026.2.1.7)
grpc-message: Client certificate not found. Please register your application first.
Listener, der nicht klassifiziert werden kann. Ein Collector oder ein Server, der externe Anfragen ablehnt, beantwortet beide Proben auf beiden Builds auf dieselbe Weise:
$ ./cve_2026_28326_check.py collector.example.com
[?] collector.example.com:55555: INCONCLUSIVE [external-requests-refused]
the listener refuses external requests, which a collector role and a restricted server both do identically on either build; scan the ARM server itself
Mehrere Ziele mit --brief:
$ ./cve_2026_28326_check.py -f targets.txt --brief; echo "exit: $?"
VULNERABLE arm.example.com:55555 unavailable-on-server-role
PATCHED arm-dr.example.com:55555 unauthenticated-gate
INCONCLUSIVE collector.example.com:55555 external-requests-refused
UNAFFECTED db.example.com:55555 not-arm
ERROR offline.example.com:55555 unreachable
exit: 1
JSON-Ausgabe mit --json. Beide Probenergebnisse sind enthalten, damit das Urteil gegen die
Rohantworten geprüft werden kann:
$ ./cve_2026_28326_check.py arm.example.com --json
[
{
"target": "arm.example.com:55555",
"verdict": "VULNERABLE",
"reason": "unavailable-on-server-role",
"detail": "Remoting/Send returned 14 UNAVAILABLE on a listener answering as the server role, the pre-patch rejection path (<= 2026.2.0.42)",
"grpc_message": "",
"probes": [
{
"path": "/SolarWinds_ARM_GRPC_ConnectionAuthrization.ConnectionAuthorization/GetCertificate",
"status": 0,
"message": "",
"alpn": "h2",
"note": ""
},
{
"path": "/SolarWinds_ARM_Remoting.Remoting/Send",
"status": 14,
"message": "",
"alpn": "h2",
"note": ""
}
]
}
]
Die Prüfung ist für den Einsatz gegen Produktionssysteme gedacht:
Remoting/Send-Nachrichtenkörper ausgelöst. Beide Proben senden eine gRPC-Nachricht
mit einem Körper der Länge null, und der Interceptor lehnt den Aufruf ab, bevor ein Nachrichtenkörper
verarbeitet wird.GetCertificate-Probe sendet einen leeren Registrierungscode, den
der Server ablehnt, bevor er ausstehende Codes nachschlägt oder entfernt, sodass die Probe keinen
Registrierungscode für einen Client verbraucht, der sich mitten in der Registrierung befindet.| # | Aufruf | Zweck |
|---|---|---|
| 1 | ConnectionAuthorization/GetCertificate | Bestätigt, dass der Endpunkt ARM ist und sich in der Serverrolle befindet |
| 2 | Remoting/Send | Die Patch-Status-Probe |
Auf einem ungepatchten Server protokolliert eine abgelehnte anonyme Anfrage auf Debug-Level
(Unable to validate remote request token) und eine Informationszeile, die den Peer benennt.
Gepatchte Builds protokollieren eine Ablehnung, die den Peer und den oben in der grpc-message
zitierten Registrierungshinweis benennt. Kein Build zeichnet eine Sitzung auf.
Um nach Ausnutzung statt nach Exposition zu suchen, prüfen Sie ARMs NetworkConnectionEntry-Datensätze.
Ein Eintrag mit IsAuthenticated=false und einem befüllten ClientCertThumbPrint ist konsistent mit
der Authentifizierungsumgehung und tritt im normalen Datenverkehr nicht auf.
Der Fix änderte, welchen Status der Authentifizierungs-Interceptor an einen Peer zurückgibt, der kein
Client-Zertifikat präsentiert. Vor dem Patch setzte jeder Ablehnungspfad in der Client-Zertifikat-Prüfung
Unavailable. Nach dem Patch setzt die Localhost-plus-Token-Prüfung Unauthenticated mit einer
unverwechselbaren Nachricht, und nichts anderes auf diesem Pfad gibt 16 zurück. Zwei anonyme Aufrufe
unterscheiden die Builds:
Remoting/Send | GetCertificate | Build | Urteil |
|---|---|---|---|
14 UNAVAILABLE | 0 OK | <= 2026.2.0.42 | VULNERABLE |
16 UNAUTHENTICATED | beliebig | >= 2026.2.1.7 | PATCHED |
14 UNAVAILABLE | 14 UNAVAILABLE | beide | INCONCLUSIVE |
Das Urteil wird aus Remoting/Send gelesen, weil nur die Duplex-Streaming- und Unary-Handler den
Status des Interceptors weitergeben; die Server- und Client-Streaming-Handler überschreiben ihn auf
beiden Builds.
Die Prüfung beobachtet das Verhalten des gepatchten Codepfads und stützt sich nicht auf eine Versionszeichenfolge. Sie meldet nicht, welchen Build ein gepatchter Server ausführt.
Der ARM-Listener setzt TLS-Verbindungen zurück, die ALPN h2 nicht aushandeln, daher setzt das Tool
es explizit. Ein Scanner, der sich über TLS ohne ALPN verbindet, empfängt keine Daten vom Port.
VULNERABLE und PATCHED basieren jeweils auf einer beobachteten Serverantwort. INCONCLUSIVE und
ERROR bedeuten, dass die Proben das Ziel nicht klassifiziert haben, sein Patch-Status also unbekannt
ist. Aus diesem Grund wird INCONCLUSIVE getrennt von PATCHED gemeldet.
Jedes Urteil trägt ein kurzes reason-Tag. --brief gibt es als dritte Spalte aus und --json
führt es als reason.
| Urteil | Reason-Tag | Bedeutung |
|---|---|---|
VULNERABLE | unavailable-on-server-role | Remoting/Send gab 14 auf einem Listener zurück, der GetCertificate als Serverrolle beantwortet. Der Fix ist nicht vorhanden. |
PATCHED | unauthenticated-gate | Remoting/Send gab 16 zurück, den durch den Fix eingeführten Status. Gilt nur für diese CVE. |
INCONCLUSIVE | external-requests-refused | Beide Proben gaben 14 zurück. Eine Collector-Rolle und ein eingeschränkter Server verhalten sich hier auf beiden Builds identisch. Scannen Sie den ARM-Server selbst. |
INCONCLUSIVE | partial-response | Nur eine Probe gab einen Status zurück, sodass das Paar nicht verglichen werden kann. Wiederholen. |
INCONCLUSIVE | unrecognized-status | Der Endpunkt antwortete mit einer Statuskombination außerhalb der erkannten Menge. |
UNAFFECTED | not-arm | Hier antworteten keine ARM-gRPC-Dienste. |
ERROR | unreachable | Die TCP-Verbindung, der TLS-Handshake oder der HTTP/2-Austausch ist fehlgeschlagen. |
Bei allen drei INCONCLUSIVE-Gründen ist der Patch-Status unbekannt. Das Reason-Tag gibt an, was
behoben werden muss, bevor die Prüfung erneut ausgeführt wird.
| Code | Bedeutung |
|---|---|
0 | Kein Ziel war VULNERABLE |
1 | Mindestens ein Ziel ist VULNERABLE |
2 | Verwendungsfehler (ungültige Argumente, ungültiges Ziel oder nicht lesbare Zieldatei) |
130 | Unterbrochen (Strg-C) |
Exit-Code 0 umfasst PATCHED, UNAFFECTED und alle drei INCONCLUSIVE-Gründe. Um ein gepatchtes
Ziel von einem nicht klassifizierbaren zu unterscheiden, lesen Sie das Urteil (die erste Spalte von
--brief oder das verdict-Feld von --json) statt den Exit-Code.
INCONCLUSIVE gemeldet. Dieser Fall wurde gegen einen Mock getestet, der die Antwort reproduziert,
nicht gegen einen eingesetzten Collector.grpc-status als literales HPACK-Feld und implementiert weder
Huffman- noch dynamische Tabellendekodierung. Der C-Kern von gRPC, den ARM verwendet, sendet diese
Trailer unkomprimiert. Wenn ein zukünftiger Build sie Huffman-kodiert, meldet das Tool INCONCLUSIVE
mit partial-response.PATCHED gilt nur für diese CVE und gibt nicht den Status anderer ARM-Schwachstellen an.VULNERABLE zeigt an, dass der Fix auf dem von der Probe
erreichten Pfad nicht vorhanden ist. Das Tool führt keinen Code auf dem Ziel aus und erkennt keine
vorherige Kompromittierung; siehe den NetworkConnectionEntry-Indikator oben.Aktualisieren Sie auf ARM 2026.2.1.7 oder höher, gemäß den ARM 2026.2.1 Versionshinweisen. Builds 2026.2.0.42 und früher sind betroffen.
Zusätzliche Hinweise:
Dieser Code wird unter einer MIT-Lizenz vertrieben.
Die Verwendung dieses Tools zum Angriff auf Ziele ohne vorherige gegenseitige Zustimmung ist illegal. Es liegt in der Verantwortung des Endbenutzers, alle geltenden lokalen, staatlichen und bundesstaatlichen Gesetze zu befolgen. Die Entwickler übernehmen keine Haftung und sind nicht verantwortlich für jeglichen Missbrauch oder Schaden, der durch dieses Programm verursacht wird.