22 сентября 2026 года исследователи ThreatDown описали ботнет CARBONATO. Он заходит на серверы через открытый Docker API без пароля, поднимает на хосте ИИ-агента и первым делом ищет ключи к языковым моделям. SSH-ключи и токены доступа идут уже потом. Если у вас на VPS крутятся парсеры в контейнерах, а в .env лежат ключи OpenRouter или OpenAI и логины прокси, это ваш профиль риска. Ниже разбор того, как устроена атака, и чек-лист на 15 минут, который закрывает этот вход.
Что нашли: 4,3 ГБ образов и агент по имени GH0ST
Всё началось с незакрытого Docker-реестра самих операторов. По данным ThreatDown, в нём было 59 репозиториев, 234 тега образов и 4,3 ГБ данных. Архив охватывает период с октября 2024 по август 2026 года, то есть ботнет работал почти два года, прежде чем его описали. В репозиториях нашлись майнер XMRig и образы с названиями вида fsociety/agent.
Цепочка заражения по отчёту выглядит так:
- Сканер ищет хосты, где Docker API открыт на TCP 2375 без аутентификации.
- Через этот API червь запускает привилегированный контейнер с примонтированной файловой системой хоста. С этого момента он фактически root на машине.
- Поднимается обратный SSH-туннель к инфраструктуре операторов, ставится SSH-сервер с их ключом.
- Закрепление идёт через cron, таймеры systemd, rc.local и OpenRC, причём файлы помечаются неизменяемыми. Сторожевые процессы заново скачивают образы, если что-то удалить.
- Контейнер маскируется под
systemd-resolved, а процесс под поток ядра[kworker/u2:0]. - Каждые пять минут скрипты сканируют соседние подсети /24 и Docker-мосты в поисках следующего открытого порта 2375.
Распространение полностью автоматическое и от ИИ не зависит. ИИ здесь отвечает за то, что происходит внутри уже захваченного сервера.
Зачем ботнету ИИ-агент и почему ему нужны именно ключи LLM
На хост ставится открытый фреймворк Hermes Agent с персоной GH0ST (её инструкции лежат в файле SOUL.md). Оператор пишет задачу в Telegram, агент пересылает её вместе с инструкциями на LLM-шлюз операции. Модель разбирает задачу, пишет команды для терминала, читает вывод и решает, что делать дальше. Результаты уходят обратно в Telegram.
В инструкциях агента приоритеты расписаны прямо. ThreatDown цитирует: «AI API keys are the absolute priority. Exfiltrate first». В списке 14 провайдеров моделей, в том числе OpenAI, Anthropic, Google, Groq, Mistral и OpenRouter. SSH-учётки, токены доступа и данные баз идут следующими пунктами.
Логика простая: украденный ключ LLM сразу превращается в бесплатные вычисления или в товар на перепродажу, а платит за них владелец ключа. В отличие от майнинга, такую кражу не видно по нагрузке на процессор. Заметите её только по счёту от провайдера модели.
Почему это касается тех, кто парсит
Типичный стек парсинга в 2026 году выглядит так: VPS, несколько контейнеров (краулер, очередь, база, headless-браузер), LLM для разбора страниц и пул прокси. Все секреты лежат в одном .env или в переменных окружения контейнеров. Для агента, который «читает всё подряд» на root-доступе, это готовая добыча одним файлом:
- ключи LLM: ради них CARBONATO и создавался;
- логины и пароли прокси: отчёт их отдельно не выделяет, но агент с root-доступом собирает любые учётки и токены, которые видит, а прокси-креды обычно лежат рядом;
- ключи облака и доступы к базам с результатами парсинга;
- сам сервер: XMRig в том же архиве означает, что CPU вашего краулера уйдёт на майнинг, и задачи начнут отваливаться по таймаутам.
Отдельная проблема с репутацией. Сервер, с которого червь сканирует чужие подсети, быстро попадает в абьюз-листы, и хостер может заблокировать его по жалобе. Для парсинга это двойной удар: IP сервера помечен, а украденные прокси-креды уже расходуют ваш трафик чужими запросами.
Как порт 2375 оказывается открытым, хотя вы его не открывали
По умолчанию Docker слушает локальный UNIX-сокет, а не сеть. Порт 2375 появляется, когда кто-то сознательно добавил -H tcp://0.0.0.0:2375 в конфиг демона. Обычно это делают, чтобы подключить удалённую IDE, CI или панель управления контейнерами, и потом об этом забывают. Документация Docker прямо предупреждает, что доступ к демону равен root-доступу к машине, и советует беречь ключи от него как пароль root. Шифрованный вариант с TLS работает на порту 2376, а 2375 означает открытый текст без проверки клиента.
Вторая ловушка бьёт тех, кто уверен, что «у меня же ufw». В документации Docker сказано, что трафик опубликованных портов контейнеров перенаправляется в таблице nat до цепочек INPUT и OUTPUT, на которые опирается ufw. На практике правила ufw для таких портов просто не срабатывают. Если вы запустили Redis, панель очереди или прокси-менеджер с -p 6379:6379, порт торчит в интернет, что бы ни показывал ufw status.
Чек-лист на 15 минут для сервера с парсерами
1. Проверьте, что Docker API не слушает сеть
- Посмотрите слушающие порты:
ss -tlnp | grep -E '2375|2376|dockerd'. Если в выводе есть0.0.0.0:2375или:::2375, закрывайте немедленно. - Проверьте, откуда пришёл флаг:
/etc/docker/daemon.json(ключ"hosts") и юнитsystemctl cat docker(строкаExecStartс-H tcp://). - Уберите TCP-слушатель и перезапустите демон. Для удалённого управления используйте SSH-контекст:
docker context create remote --docker host=ssh://user@server. Порт наружу при этом не нужен вообще. - Если TCP всё же необходим (CI, оркестратор), то только 2376 с взаимной TLS-аутентификацией и белым списком IP источника.
2. Проверьте, что торчит наружу из контейнеров
- Выполните
docker ps --format '{{.Names}} {{.Ports}}'. Всё, что начинается с0.0.0.0:, доступно из интернета в обход ufw. - Служебные сервисы (Redis, Postgres, Mongo, панели очередей, Selenium Grid, API прокси-менеджера) публикуйте только на локальный адрес:
-p 127.0.0.1:6379:6379. Внешний доступ делайте через SSH-туннель. - Если без внешнего порта не обойтись, фильтруйте в цепочке
DOCKER-USER: её Docker не перезаписывает, и именно она применяется к трафику контейнеров. - Не держите на сервере открытый прокси без авторизации (Squid на 3128, SOCKS на 1080 «для своих»). Сканеры регулярно находят такие порты, и через ваш IP идёт чужой трафик с чужими жалобами.
3. Разберитесь с секретами
- Разделите ключи: отдельный ключ LLM на каждый сервер или проект, с лимитом расходов у провайдера модели. Украденный ключ с лимитом $20 — неприятность, без лимита — дыра в бюджете.
- Ключи прокси тоже разводите по задачам: отдельный логин (суб-аккаунт) на каждый парсер. Тогда утечку видно по расходу конкретного логина, и отозвать можно только его, не останавливая остальную работу.
- Не пробрасывайте в контейнер весь
.envчерезenv_file, если сервису нужны два ключа из двадцати. - Где можно, привяжите доступ к IP сервера: белый список у прокси-провайдера или ограничение ключа API по адресу.
Подробнее о том, где и как хранить логины прокси в скриптах и контейнерах, мы писали в разборе безопасного хранения учётных данных прокси.
4. Уберите лишние привилегии
- Не запускайте контейнеры с
--privilegedи не монтируйте/или/var/run/docker.sockвнутрь без крайней нужды. Сокет внутри контейнера — это тот же root на хосте. - Для headless-браузеров обычно хватает
--shm-sizeи seccomp-профиля. Привилегированный режим «чтобы Chrome завёлся» — плохой компромисс.
Как понять, что вас уже заразили
ThreatDown и разборы его отчёта приводят такие признаки компрометации:
- файл
SOUL.mdсо словом GH0ST (например,/root/.hermes/SOUL.md); - переменная окружения или строка в
.env:CARBONATO_API_KEY; - файлы
/usr/local/bin/.docker-network-monitorи подозрительный/usr/sbin/systemd-logind; - контейнер с именем
systemd-resolved(настоящий systemd-resolved — служба хоста, а не контейнер); - неожиданный исходящий трафик в Telegram API и обратные SSH-туннели в сторону AS262145;
- соединения с адресами 45.79.183.61, 213.136.79.115, 190.211.124.187;
- неизменяемые файлы в cron и systemd:
lsattr /etc/cron.d/* /etc/systemd/system/*покажет флагi.
Если хоть один признак совпал, чистить сервер вручную бессмысленно: закрепление многослойное, и сторож вернёт импланты. Правильный порядок такой:
- С другой машины отзовите все ключи, которые были на сервере: LLM, облако, базы, прокси.
- Проверьте расход по каждому ключу за последние недели у провайдеров. Статистика по логину прокси сразу покажет чужой трафик.
- Поднимите новый сервер из чистого образа, выпустите новые ключи и только потом переносите данные, без бинарников и cron-файлов со старой машины.
Где здесь прокси и что они не решают
Прокси не защищают сервер от CARBONATO. Червь приходит не через ваши исходящие запросы, а через входящий порт. Зато правильная схема работы с прокси снижает ущерб. Отдельные логины на задачи, лимиты трафика и привязка по IP превращают утечку из «весь баланс ушёл» в «отозвал один логин».
Есть и обратная сторона, которую ботнеты показывают регулярно: чужие захваченные устройства сами становятся «резидентными прокси» сомнительных сетей. Поэтому для парсинга стоит брать трафик у провайдера с понятным происхождением пула. Для большинства задач сбора данных подойдут резидентные прокси с оплатой за гигабайт, где расход по каждому прокси виден в кабинете. Для сервисных задач без жёсткой антибот-защиты хватит более дешёвых прокси датацентров.
Вывод
CARBONATO не использует ни уязвимостей нулевого дня, ни хитрых эксплойтов. Он входит через дверь, которую владельцы серверов открыли сами: TCP 2375 без пароля. Новое в нём другое: внутри работает ИИ-агент, которому поручено первым делом забрать ключи к моделям. Для тех, кто собирает данные на своих VPS, вывод практический. Закройте Docker API, опубликуйте служебные порты на 127.0.0.1, разведите ключи LLM и прокси по задачам с лимитами расходов. Это 15 минут работы, и после них ваш сервер для таких ботнетов становится неинтересной целью.
