Vérificateur non destructif de l'état des correctifs pour SolarWinds ARM CVE-2026-28326 qui sonde l'écouteur gRPC sur TCP 55555 pour signaler VULNERABLE, PATCHED ou INCONCLUSIVE.
Une vérification de l'état du correctif sans authentification pour CVE-2026-28326, la vulnérabilité
d'exécution de code à distance sans authentification dans SolarWinds Access Rights Manager (ARM), corrigée dans
ARM 2026.2.1
le 2026-09-17. CWE-798, CVSS 8.8 Élevé
(CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H). Signalée à SolarWinds par Kai Huang d'Armadin.
Le AV:A de ce vecteur suppose que le TCP 55555 n'est accessible que depuis un réseau adjacent, ce qui correspond
à la posture pour laquelle les outils de pare-feu propres à ARM sont conçus ; lorsque le port a été ouvert plus largement, le
score sous-estime l'exposition pour cet environnement.
L'écouteur de remoting gRPC d'ARM sur le TCP 55555 authentifie les pairs avec un certificat client. Les versions
non corrigées acceptent également un identifiant de secours dérivé d'une clé codée en dur identique sur chaque
installation, ce qui contourne cette authentification et expose un chemin de désérialisation non sécurisé. Le
résultat est une exécution de code sous le compte de service ARM, qui s'exécute en tant que LocalSystem par défaut.
Le script indique si le correctif est présent sur chaque cible. Il lit les codes de statut renvoyés par
l'intercepteur d'authentification et n'utilise pas la clé, ne s'authentifie pas et n'envoie pas de données vers le
chemin de désérialisation. Un résultat autre que VULNERABLE n'indique pas à lui seul qu'une cible est
corrigée ; voir Interprétation des résultats non vulnérables.
# 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+, bibliothèque standard uniquement — aucun paquet tiers.
Exécutez l'outil contre le serveur ARM plutôt que contre un collecteur. Les deux rôles écoutent sur le 55555, mais un
collecteur refuse les requêtes externes de la même manière avant et après le correctif, de sorte que l'outil le signale comme
INCONCLUSIVE.
| Flag | Description |
|---|---|
TARGET | Une ou plusieurs cibles HOST[:PORT] ; le port par défaut est 55555 |
-f, --targets-file FILE | Lit les cibles depuis un fichier (une par ligne ; commentaires #) |
-p, --port PORT | Port par défaut lorsqu'une cible n'en précise pas (défaut : 55555) |
--timeout SECS | Délai d'attente par sonde (défaut : 12) |
--workers N | Cibles simultanées (défaut : 16) ; la sortie reste dans l'ordre d'entrée |
-b, --brief | Une seule ligne alignée par cible, pour scanner de nombreux hôtes |
--json | Émet du JSON structuré, incluant les deux sondes par cible |
--no-color | Désactive la sortie colorée (respecte aussi NO_COLOR et les non-TTY) |
Serveur vulnérable (sortie par défaut ; le marqueur [!] et VULNERABLE sont affichés en rouge sur un TTY) :
$ ./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)
Serveur corrigé. La version corrigée renvoie un grpc-message, que l'outil affiche à côté du
verdict :
$ ./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.
Écouteur qui ne peut pas être classifié. Un collecteur, ou un serveur qui refuse les requêtes externes, répond aux deux sondes de la même manière sur l'une ou l'autre version :
$ ./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
Plusieurs cibles avec --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
Sortie JSON avec --json. Les résultats des deux sondes sont inclus afin que le verdict puisse être vérifié par rapport
aux réponses brutes :
$ ./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": ""
}
]
}
]
La vérification est destinée à être utilisée contre des systèmes de production :
Remoting/Send. Les deux sondes envoient un message gRPC avec un corps de longueur nulle,
et l'intercepteur rejette l'appel avant que tout corps de message ne soit distribué.GetCertificate envoie un code d'enrôlement vide, que le serveur rejette avant de rechercher
ou de supprimer tout code en attente, de sorte que la sonde ne consomme pas de code d'enrôlement pour un client en
cours d'enregistrement.| # | Appel | Objectif |
|---|---|---|
| 1 | ConnectionAuthorization/GetCertificate | Confirme que le point de terminaison est ARM et qu'il est dans le rôle serveur |
| 2 | Remoting/Send | La sonde d'état du correctif |
Sur un serveur non corrigé, une requête anonyme rejetée est journalisée au niveau debug
(Unable to validate remote request token) ainsi qu'une ligne informative nommant le pair. Les versions corrigées
journalisent un rejet nommant le pair et l'indice d'enregistrement cité dans le grpc-message ci-dessus.
Aucune des deux versions n'enregistre de session.
Pour rechercher une exploitation plutôt qu'une exposition, examinez les enregistrements NetworkConnectionEntry d'ARM. Une
entrée avec IsAuthenticated=false et un ClientCertThumbPrint renseigné est cohérente avec le
contournement d'authentification et ne se produit pas dans le trafic normal.