
Comprobador no destructivo del estado de parcheo para SolarWinds ARM CVE-2026-28326 que sondea el listener gRPC en TCP 55555 para informar VULNERABLE, PATCHED o INCONCLUSIVE.
Una comprobación del estado de parcheo sin autenticación para CVE-2026-28326, la vulnerabilidad de
ejecución remota de código sin autenticación en SolarWinds Access Rights Manager (ARM), corregida en
ARM 2026.2.1
el 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 a SolarWinds por Kai Huang de Armadin.
El AV:A en ese vector asume que TCP 55555 es accesible solo desde una red adyacente, que es la
postura para la que están diseñadas las propias herramientas de firewall de ARM; donde el puerto se
ha abierto de forma más amplia, la puntuación subestima la exposición para ese entorno.
El listener de remoting gRPC de ARM en TCP 55555 autentica a los pares con un certificado de cliente.
Las compilaciones sin parchear también aceptan una credencial de respaldo derivada de una clave
hardcodeada que es idéntica en todas las instalaciones, lo que omite esa autenticación y expone una
ruta de deserialización insegura. El resultado es la ejecución de código como la cuenta de servicio
de ARM, que se ejecuta como LocalSystem de forma predeterminada.
El script informa si la corrección está presente en cada objetivo. Lee los códigos de estado
devueltos por el interceptor de autenticación y no utiliza la clave, no se autentica ni envía datos a
la ruta de deserialización. Un resultado distinto de VULNERABLE no indica por sí mismo que un
objetivo esté parcheado; consulte Interpretación de resultados no vulnerables.
# 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 biblioteca estándar — sin paquetes de terceros.
Ejecute la herramienta contra el servidor ARM en lugar de contra un colector. Ambos roles escuchan
en 55555, pero un colector rechaza las solicitudes externas de la misma manera antes y después de la
corrección, por lo que la herramienta lo reporta como INCONCLUSIVE.
| Flag | Descripción |
|---|---|
TARGET | Uno o más objetivos HOST[:PORT]; el puerto predeterminado es 55555 |
-f, --targets-file FILE | Lee los objetivos desde un archivo (uno por línea; comentarios con #) |
-p, --port PORT | Puerto predeterminado cuando un objetivo omite uno (predeterminado: 55555) |
--timeout SECS | Tiempo de espera por sonda (predeterminado: 12) |
--workers N | Objetivos concurrentes (predeterminado: 16); la salida mantiene el orden de entrada |
-b, --brief | Una sola línea alineada por objetivo, para escanear muchos hosts |
--json | Emite JSON estructurado, incluyendo ambas sondas por objetivo |
--no-color | Desactiva la salida con color (también respeta NO_COLOR y entornos no TTY) |
Servidor vulnerable (salida predeterminada; el marcador [!] y VULNERABLE se muestran en rojo en
una 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 parcheado. La compilación corregida devuelve un grpc-message, que la herramienta imprime
junto al veredicto:
$ ./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 no se puede clasificar. Un colector, o un servidor que rechaza solicitudes externas, responde a ambas sondas de la misma manera en cualquiera de las compilaciones:
$ ./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últiples objetivos 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
Salida JSON con --json. Se incluyen ambos resultados de sonda para que el veredicto pueda
contrastarse con las respuestas sin procesar:
$ ./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 comprobación está pensada para usarse contra sistemas de producción:
Remoting/Send. Ambas sondas envían un mensaje gRPC con un
cuerpo de longitud cero, y el interceptor rechaza la llamada antes de que se despache ningún cuerpo
de mensaje.GetCertificate envía un código de inscripción vacío, que el servidor rechaza
antes de buscar o eliminar cualquier código pendiente, por lo que la sonda no consume un código de
inscripción de un cliente que esté en pleno registro.| # | Llamada | Propósito |
|---|---|---|
| 1 | ConnectionAuthorization/GetCertificate | Confirma que el endpoint es ARM y está en el rol de servidor |
| 2 | Remoting/Send | La sonda del estado de parcheo |
En un servidor sin parchear, una solicitud anónima rechazada se registra a nivel de depuración
(Unable to validate remote request token) y una línea informativa que nombra al par. Las
compilaciones parcheadas registran un rechazo que nombra al par y la sugerencia de registro citada
en el grpc-message anterior. Ninguna de las compilaciones registra una sesión.
Para buscar explotación en lugar de exposición, revise los registros NetworkConnectionEntry de ARM.
Una entrada con IsAuthenticated=false y un ClientCertThumbPrint poblado es consistente con el
bypass de autenticación y no ocurre en el tráfico normal.