
Verificatore non distruttivo dello stato delle patch per SolarWinds ARM CVE-2026-28326 che sonda il listener gRPC su TCP 55555 per segnalare VULNERABLE, PATCHED o INCONCLUSIVE.
Un controllo dello stato della patch non autenticato per CVE-2026-28326, la vulnerabilità di
esecuzione di codice in remoto non autenticata in SolarWinds Access Rights Manager (ARM), corretta in
ARM 2026.2.1
il 2026-09-17. CWE-798, CVSS 8.8 Alto
(CVSS:3.1/AV:A/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H). Segnalata a SolarWinds da Kai Huang di Armadin.
L'AV:A in quel vettore presuppone che TCP 55555 sia raggiungibile solo da una rete adiacente, che è la
postura per cui sono costruiti gli stessi strumenti firewall di ARM; dove la porta è stata aperta in modo più ampio, il
punteggio sottostima l'esposizione per quell'ambiente.
Il listener di remoting gRPC di ARM su TCP 55555 autentica i peer con un certificato client. Le build
non aggiornate accettano anche una credenziale di fallback derivata da una chiave hardcoded identica su ogni
installazione, che aggira tale autenticazione ed espone un percorso di deserializzazione non sicuro. Il
risultato è l'esecuzione di codice come account del servizio ARM, che per impostazione predefinita viene eseguito come LocalSystem.
Lo script segnala se la correzione è presente su ciascun target. Legge i codici di stato restituiti
dall'intercettore di autenticazione e non utilizza la chiave, non si autentica e non invia dati al
percorso di deserializzazione. Un risultato diverso da VULNERABLE non indica di per sé che un target sia
corretto; vedere Interpretazione dei risultati non vulnerabili.
# 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+, solo libreria standard — nessun pacchetto di terze parti.
Eseguire lo strumento contro il server ARM anziché contro un collector. Entrambi i ruoli ascoltano sulla 55555, ma un
collector rifiuta le richieste esterne allo stesso modo prima e dopo la correzione, quindi lo strumento lo segnala come
INCONCLUSIVE.
| Flag | Descrizione |
|---|---|
TARGET | Uno o più target HOST[:PORT]; la porta predefinita è 55555 |
-f, --targets-file FILE | Legge i target da un file (uno per riga; commenti con #) |
-p, --port PORT | Porta predefinita quando un target ne omette una (predefinita: 55555) |
--timeout SECS | Timeout per singola sonda (predefinito: 12) |
--workers N | Target concorrenti (predefinito: 16); l'output mantiene l'ordine di input |
-b, --brief | Una singola riga allineata per target, per scansionare molti host |
--json | Emette JSON strutturato, includendo entrambe le sonde per target |
--no-color | Disabilita l'output colorato (rispetta anche NO_COLOR e i non-TTY) |
Server vulnerabile (output predefinito; il marcatore [!] e VULNERABLE sono mostrati in rosso su 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)
Server corretto. La build corretta restituisce un grpc-message, che lo strumento stampa insieme al
verdetto:
$ ./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 che non può essere classificato. Un collector, o un server che rifiuta le richieste esterne, risponde a entrambe le sonde allo stesso modo su entrambe le build:
$ ./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
Target multipli con --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
Output JSON con --json. Sono inclusi i risultati di entrambe le sonde, così il verdetto può essere verificato rispetto
alle risposte grezze:
$ ./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": ""
}
]
}
]
Il controllo è pensato per l'uso su sistemi di produzione:
Remoting/Send. Entrambe le sonde inviano un messaggio gRPC con corpo di lunghezza zero,
e l'intercettore rifiuta la chiamata prima che qualsiasi corpo del messaggio venga inoltrato.GetCertificate invia un codice di registrazione vuoto, che il server rifiuta prima di cercare
o rimuovere qualsiasi codice in sospeso, quindi la sonda non consuma un codice di registrazione per un client che è
in fase di registrazione.| # | Chiamata | Scopo |
|---|---|---|
| 1 | ConnectionAuthorization/GetCertificate | Conferma che l'endpoint è ARM e si trova nel ruolo server |
| 2 | Remoting/Send | La sonda sullo stato della patch |
Su un server non aggiornato una richiesta anonima rifiutata viene registrata a livello debug
(Unable to validate remote request token) e una riga informativa che nomina il peer. Le build corrette
registrano un rifiuto che nomina il peer e il suggerimento di registrazione citato nel grpc-message sopra.
Nessuna delle due build registra una sessione.
Per cercare lo sfruttamento anziché l'esposizione, esaminare i record NetworkConnectionEntry di ARM. Una
voce con IsAuthenticated=false e un ClientCertThumbPrint popolato è coerente con il
bypass dell'autenticazione e non si verifica nel traffico normale.