
Verificador não destrutivo de estado de patch para SolarWinds ARM CVE-2026-28326 que sonda o listener gRPC na porta TCP 55555 para reportar VULNERABLE, PATCHED ou INCONCLUSIVE.
Uma verificação de estado de patch não autenticada para CVE-2026-28326, a vulnerabilidade de execução
remota de código não autenticada no SolarWinds Access Rights Manager (ARM), corrigida no
ARM 2026.2.1
em 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). Reportado à SolarWinds por Kai Huang da Armadin.
O AV:A nesse vetor assume que a TCP 55555 é acessível apenas a partir de uma rede adjacente, que é a
postura para a qual as próprias ferramentas de firewall do ARM foram construídas; onde a porta foi aberta de forma mais ampla, a
pontuação subestima a exposição para esse ambiente.
O listener de remoting gRPC do ARM na TCP 55555 autentica pares com um certificado de cliente. Builds
não corrigidos também aceitam uma credencial de fallback derivada de uma chave hardcoded que é idêntica em todas
as instalações, o que contorna essa autenticação e expõe um caminho de desserialização inseguro. O
resultado é execução de código como a conta de serviço do ARM, que é executada como LocalSystem por padrão.
O script informa se a correção está presente em cada alvo. Ele lê os códigos de status retornados pelo
interceptor de autenticação e não usa a chave, não autentica nem envia dados para o
caminho de desserialização. Um resultado diferente de VULNERABLE não indica por si só que um alvo está
corrigido; consulte Interpretando resultados não vulneráveis.
# 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+, apenas biblioteca padrão — sem pacotes de terceiros.
Execute a ferramenta contra o servidor ARM em vez de um coletor. Ambos os papéis escutam na 55555, mas um
coletor recusa requisições externas da mesma forma antes e depois da correção, então a ferramenta o reporta como
INCONCLUSIVE.
| Flag | Descrição |
|---|---|
TARGET | Um ou mais alvos HOST[:PORT]; a porta padrão é 55555 |
-f, --targets-file FILE | Lê alvos de um arquivo (um por linha; comentários com #) |
-p, --port PORT | Porta padrão quando um alvo omite uma (padrão: 55555) |
--timeout SECS | Timeout por sonda (padrão: 12) |
--workers N | Alvos concorrentes (padrão: 16); a saída permanece na ordem de entrada |
-b, --brief | Uma linha alinhada por alvo, para escanear muitos hosts |
--json | Emite JSON estruturado, incluindo ambas as sondas por alvo |
--no-color | Desativa saída colorida (também respeita NO_COLOR e não-TTY) |
Servidor vulnerável (saída padrão; o marcador [!] e VULNERABLE são exibidos em vermelho em um 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)
Servidor corrigido. O build corrigido retorna um grpc-message, que a ferramenta imprime junto com o
veredito:
$ ./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 que não pode ser classificado. Um coletor, ou um servidor que recusa requisições externas, responde ambas as sondas da mesma forma em qualquer 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
Múltiplos alvos com --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
Saída JSON com --json. Ambos os resultados das sondas são incluídos para que o veredito possa ser verificado contra
as respostas brutas:
$ ./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": ""
}
]
}
]
A verificação destina-se ao uso contra sistemas de produção:
Remoting/Send. Ambas as sondas enviam uma mensagem gRPC com corpo de comprimento zero,
e o interceptor rejeita a chamada antes que qualquer corpo de mensagem seja despachado.GetCertificate envia um código de inscrição vazio, que o servidor rejeita antes de procurar
ou remover quaisquer códigos pendentes, então a sonda não consome um código de inscrição para um cliente que está
em meio ao registro.| # | Chamada | Propósito |
|---|---|---|
| 1 | ConnectionAuthorization/GetCertificate | Confirma que o endpoint é ARM e está no papel de servidor |
| 2 | Remoting/Send | A sonda de estado de patch |
Em um servidor não corrigido, uma requisição anônima rejeitada é registrada em nível de debug
(Unable to validate remote request token) e uma linha informativa nomeando o par. Builds corrigidos
registram uma rejeição nomeando o par e a dica de registro citada no grpc-message acima.
Nenhum dos builds registra uma sessão.
Para procurar exploração em vez de exposição, revise os registros NetworkConnectionEntry do ARM. Uma
entrada com IsAuthenticated=false e um ClientCertThumbPrint preenchido é consistente com a
contorno de autenticação e não ocorre em tráfego normal.