
Неразрушающая проверка состояния патча для SolarWinds ARM CVE-2026-28326, которая опрашивает gRPC-слушатель на TCP 55555 и сообщает VULNERABLE, PATCHED или INCONCLUSIVE.
Проверка состояния патча без аутентификации для CVE-2026-28326 — уязвимости удалённого
выполнения кода без аутентификации в SolarWinds Access Rights Manager (ARM), исправленной в
ARM 2026.2.1
17.09.2026. 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). Сообщено в SolarWinds Кай Хуангом из Armadin.
AV:A в этом векторе предполагает, что TCP 55555 доступен только из смежной сети — именно на
такой сценарий рассчитаны собственные средства межсетевого экранирования ARM; там, где порт
открыт более широко, оценка занижает уровень подверженности для этой среды.
gRPC-слушатель удалённого взаимодействия ARM на TCP 55555 аутентифицирует узлы с помощью
клиентского сертификата. Непропатченные сборки также принимают резервные учётные данные,
производные от жёстко закодированного ключа, одинакового для всех установок, что обходит эту
аутентификацию и открывает небезопасный путь десериализации. Результат — выполнение кода от
имени учётной записи службы ARM, которая по умолчанию работает как LocalSystem.
Скрипт сообщает, присутствует ли исправление на каждой цели. Он читает коды состояния,
возвращаемые перехватчиком аутентификации, и не использует ключ, не аутентифицируется и не
отправляет данные на путь десериализации. Результат, отличный от VULNERABLE, сам по себе не
означает, что цель пропатчена; см. Интерпретация результатов, отличных от уязвимых.
# 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+, только стандартная библиотека — без сторонних пакетов.
Запускайте инструмент против сервера ARM, а не против коллектора. Обе роли слушают на 55555, но
коллектор отклоняет внешние запросы одинаково до и после исправления, поэтому инструмент сообщает
о нём как о INCONCLUSIVE.
| Флаг | Описание |
|---|---|
TARGET | Одна или несколько целей HOST[:PORT]; порт по умолчанию — 55555 |
-f, --targets-file FILE | Читать цели из файла (по одной в строке; комментарии #) |
-p, --port PORT | Порт по умолчанию, если цель его не указывает (по умолчанию: 55555) |
--timeout SECS | Тайм-аут на одну проверку (по умолчанию: 12) |
--workers N | Число одновременных целей (по умолчанию: 16); вывод сохраняется в порядке ввода |
-b, --brief | Одна выровненная строка на цель, для сканирования множества хостов |
--json | Вывод структурированного JSON, включая обе проверки для каждой цели |
--no-color | Отключить цветной вывод (также учитывает NO_COLOR и отсутствие TTY) |
Уязвимый сервер (вывод по умолчанию; маркер [!] и VULNERABLE отображаются красным на 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)
Пропатченный сервер. Исправленная сборка возвращает grpc-message, который инструмент выводит
рядом с вердиктом:
$ ./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.
Слушатель, который невозможно классифицировать. Коллектор или сервер, отклоняющий внешние запросы, отвечает на обе проверки одинаково на любой сборке:
$ ./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
Несколько целей с --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 с --json. Включены результаты обеих проверок, чтобы вердикт можно было сверить с
необработанными ответами:
$ ./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": ""
}
]
}
]
Проверка предназначена для использования на производственных системах:
Remoting/Send. Обе проверки отправляют gRPC-сообщение
с телом нулевой длины, и перехватчик отклоняет вызов до того, как будет обработано какое-либо
тело сообщения.GetCertificate отправляет пустой код регистрации, который сервер
отклоняет до того, как он выполнит поиск или удалит какие-либо ожидающие коды, поэтому проверка
не расходует код регистрации для клиента, находящегося в процессе регистрации.| # | Вызов | Назначение |
|---|---|---|
| 1 | ConnectionAuthorization/GetCertificate | Подтверждает, что конечная точка — это ARM и что она находится в роли сервера |
| 2 | Remoting/Send | Проверка состояния патча |
На непропатченном сервере отклонённый анонимный запрос записывается в журнал на уровне debug
(Unable to validate remote request token) и информационной строкой с указанием узла. Пропатченные
сборки записывают отклонение с указанием узла и подсказкой о регистрации, процитированной в
grpc-message выше. Ни одна из сборок не записывает сессию.
Чтобы искать эксплуатацию, а не подверженность, просмотрите записи NetworkConnectionEntry в ARM.
Запись с IsAuthenticated=false и заполненным ClientCertThumbPrint согласуется с обходом
аутентификации и не встречается в нормальном трафике.