
Cacti 1.2.22 — неаутентифицированная инъекция команд
| Поле | Детали |
|---|---|
| Продукт | Cacti |
| Затронутая версия | 1.2.22 |
| Уязвимость | Неаутентифицированная инъекция OS-команд |
| CVE | CVE-2022-46169 |
| CWE | CWE-77 — Инъекция команд |
| Серьёзность | Критическая |
| CVSS v3.1 | 9.8 |
| Вектор атаки | Сеть |
| Аутентификация | Не требуется |
| Взаимодействие с пользователем | Не требуется |
| Воздействие | Конфиденциальность / Целостность / Доступность |
| Исправленная версия | 1.2.23 |
| Уязвимый компонент | remote_agent.php |
| Дополнительный компонент | lib/functions.php |
| Основное уязвимое действие | polldata |
Опубликованный вектор CVSS:
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Официальный бюллетень оценивает её в 9.8 Критическая. ([GitHub][1])
На самом деле здесь две ошибки, объединённые в цепочку.
Этап 1 — Обход авторизации
remote_agent.php в Cacti принимает запросы без обычной аутентификации, но пытается определить, является ли запрашивающая сторона авторизованным поллером.
Поток авторизации фактически выглядит так:
HTTP-запрос
│
▼
remote_agent.php
│
▼
remote_client_authorized()
│
▼
get_client_addr()
│
▼
gethostbyaddr()
│
▼
сравнение имени хоста с таблицей poller
Проблема в get_client_addr().
В версии 1.2.22 она проверяет множество серверных переменных, производных от HTTP, включая заголовки forwarded-client-IP. Бюллетень объясняет, что управляемые атакующим значения HTTP_* могут влиять на IP, возвращаемый этой функцией. ([GitHub][1])
Это означает, что приложение можно обмануть, заставив его поверить:
Атакующий
↓
"мой IP — это сервер Cacti"
↓
gethostbyaddr()
↓
имя хоста сервера Cacti
↓
совпадает с таблицей poller
↓
АВТОРИЗОВАН
Таким образом, атакующему не нужна легитимная учётная запись Cacti.
После обхода проверки авторизации интересующая функциональность конечной точки — это действие polldata.
Соответствующий путь выполнения примерно таков:
remote_agent.php
│
▼
polldata
│
▼
poll_for_data()
│
├── host_id
├── local_data_ids
└── poller_id
│
▼
поиск poller_item
│
▼
POLLER_ACTION_SCRIPT_PHP
│
▼
proc_open()
│
▼
выполнение OS-команды
Важная ошибка — обработка poller_id.
Приложение получает его с помощью:
get_nfilter_request_var()
вместо того чтобы обеспечивать его целочисленность.
Это управляемое атакующим значение в конечном итоге становится частью команды, передаваемой в PHP-функцию proc_open(). Официальный бюллетень прямо называет это примитивом инъекции команд. ([GitHub][1])
Концептуально:
управляемый атакующим ввод
↓
poller_id
↓
конкатенация строк
↓
proc_open()
↓
команда операционной системы
Это критическая часть уязвимости.
Интересный момент для вашего анализа PoC в том, что ни одна из ошибок по отдельности не является всей историей.
Это цепочка уязвимостей:
┌──────────────────────────┐
│ Неаутентифицированный │
│ атакующий │
└────────────┬─────────────┘
│
▼
remote_agent.php
│
▼
Обход авторизации
через логику IP клиента
│
▼
polldata
│
▼
поиск poller_item
│
▼
POLLER_ACTION_SCRIPT_PHP
│
▼
ввод атакующего
→ poller_id
│
▼
proc_open()
│
▼
Выполнение команд
│
▼
RCE
Это очень важное различие, которое стоит сделать в вашем отчёте:
CVE-2022-46169 — это не просто "плохой параметр в remote_agent.php". Это объединённая в цепочку уязвимость обхода авторизации + инъекции команд.
Официальный бюллетень подтверждает, что уязвимое условие выполнения требует poller_item, действие которого — POLLER_ACTION_SCRIPT_PHP. ([GitHub][1])
Ваш PoC должен явно документировать это, поскольку это важная аналитическая деталь.
Целевой системе нужен подходящий poller_item, настроенный с:
POLLER_ACTION_SCRIPT_PHP
Бюллетень Cacti отмечает, что это распространено на производственных установках, поскольку предопределённые шаблоны, такие как Device - Uptime и Device - Polling Time, могут создавать такие записи. ([GitHub][1])
Поэтому не пишите:
"Каждая установка Cacti 1.2.22 автоматически эксплуатируема."
Более технически точное утверждение:
Cacti 1.2.22 уязвим, и успешное выполнение команд зависит от наличия соответствующей конфигурации
poller_item.
host_id и local_data_id важныpoll_for_data() не просто выполняет переданный poller_id.
Сначала она запрашивает poller_item, используя значения, соответствующие:
host_id
local_data_id
Затем она проверяет действие полученного элемента.
Уязвимое условие фактически таково:
host_id
+
local_data_id
↓
poller_item
↓
action == POLLER_ACTION_SCRIPT_PHP
↓
уязвимый путь выполнения
Оригинальный бюллетень отмечает, что эти идентификаторы можно обнаружить, поскольку соответствующие записи существуют в базе данных приложения, и что подходящие записи, скорее всего, существуют на производственных установках. ([GitHub][1])
Для публичного PoC я бы продемонстрировал это предварительное условие явно, а не скрывал его.
Для публикуемого материала я рекомендую сделать PoC, демонстрирующий выполнение команд без предоставления читателям готового weaponized reverse-shell пейлоада.
Например, структурируйте демонстрацию так:
1. Разверните Cacti 1.2.22 в изолированной ВМ
2. Настройте poller_item с POLLER_ACTION_SCRIPT_PHP
3. Подтвердите доступность remote_agent.php
4. Продемонстрируйте влияние на решение об авторизации
5. Достигните пути выполнения polldata
6. Используйте безвредный маркер выполнения команд
7. Зафиксируйте результирующие доказательства приложения/журналов
8. Обновитесь до 1.2.23
9. Повторите тест
10. Продемонстрируйте, что уязвимость больше не эксплуатируема
Это даёт легитимную демонстрацию уязвимости, не превращая отчёт в готовое интернет-оружие RCE.
Вы можете разделить уязвимый код на три области.
remote_agent.phpОтвечает за предоставление функциональности удалённого агента и диспетчеризацию запрошенного действия.
remote_agent.php
│
└── action = polldata
│
▼
poll_for_data()
lib/functions.phpСодержит get_client_addr().