От uname до промпта на 919 тысяч символов: три месяца внутри двух ханипотов

от автора

26 мая посетитель SSH-сервера потратил полтора часа на то, чтобы обследовать каталог веб-приложения, найти конфигурацию Bitrix, извлечь из неё учётные данные базы, попытаться выгрузить таблицы и записать PHP-файл в web root. Он не знал, что файловая система, конфигурация и база были декорациями Cowrie.

Через два месяца другой клиент нашёл открытый Ollama-compatible API. Вместо обычной проверки /api/tags он отправил 27 374 запроса к /api/chat. Медианный промпт занимал 2716 символов, каждый десятый — больше 155 тысяч, самый большой — 919 294. Внутри были переводы, суммаризация, извлечение фактов, задачи для coding agents и целиком переданные системные инструкции. Модели за API не было вообще.

Это два края одного эксперимента. Я держал в интернете два среднеинтерактивных ханипота: традиционный сервер под легендой поставщика промышленного оборудования и отдельный AI gateway, похожий на забытый dev-стенд. За три месяца они показали не столько «кто атакует интернет», сколько две разные экономики автоматизации. Старый стек пытаются быстро инвентаризировать, закрепить и превратить в очередной узел. Открытый AI API пытаются использовать как готовый вычислительный бэкенд — иногда сразу с реальной рабочей нагрузкой.

В статье нет исходных IP-адресов, токенов, точных honeytoken-значений, URL загрузчиков и полных промптов. Они не нужны для воспроизведения выводов, но могут раскрыть стенды, чужие данные или действующую инфраструктуру.

Почему два стенда, а не ещё один Cowrie

Большинство коротких экспериментов с ханипотами отвечает на один из двух вопросов: сколько раз перебрали root и какие команды выполнили после входа. Это полезный начальный срез, но он плохо различает три сущности:

  1. транспортный шум — TCP-соединения, мусорные методы и протокольную путаницу;

  2. протокольную разведку — проверку SSH, MySQL, Ollama, OpenAI API или MCP;

  3. намерение — загрузку исполняемого файла, закрепление, кражу вычислений, поиск секретов или попытку вызвать инструмент.

Я хотел сравнить две легенды при одинаковом принципе безопасности: внешняя сторона должна выглядеть достаточно правдоподобно, но ни одна принятая команда не должна исполниться на реальном сервере.

Первый стенд имитировал инфраструктуру поставщика промышленного оборудования и SCADA-ПО:

  • SSH на базе Cowrie с поддельной Unix-файловой системой;

  • MySQL-like сенсор;

  • HTTP/HTTPS-фасад, похожий на корпоративный Bitrix-сайт;

  • согласованные между сервисами конфигурационные файлы и honeytoken-артефакты;

  • сохранение загрузок и ограниченный PCAP.

Второй выглядел как забытый AI gateway среды разработки:

  • Ollama-compatible /api/*;

  • OpenAI-compatible /v1/*;

  • минимальный MCP через HTTP/SSE;

  • status/docs-поверхность с признаками внутреннего сервиса;

  • нормализованные JSONL-события, полные тела запросов и PCAP.

За AI gateway не стояло реального inference. Он не загружал модели, не вызывал MCP-инструменты, не выполнял shell-команды и не ходил во внешние API. Приложение слушало loopback, наружу его публиковал nginx. Административный SSH обоих серверов был отделён от публичной поверхности, парольный вход не использовался, firewall работал по allowlist.

Cowrie в режиме эмулированной оболочки решает похожую задачу для SSH: хранит состояние поддельной файловой системы, регистрирует логины и команды, а загруженные через wget, curl, SCP или SFTP файлы складывает для анализа. Это штатная модель работы проекта, а не самописный перехватчик команд — она описана в документации Cowrie.

                        Интернет                           │             ┌─────────────┴─────────────┐             │                           │     SCADA/Bitrix lure             AI gateway lure     ─────────────────             ───────────────     nginx 80/443                  nginx 80/443/11434     Cowrie SSH                    loopback app     MySQL-like sensor             Ollama/OpenAI/MCP     fake filesystem               fake model catalog     payload storage               raw JSON bodies             │                           │             └──── JSONL + access log ───┘                           │                         PCAP                           │                  off-host архив анализа

Эта схема дала возможность сравнивать не порты, а пройденные стадии взаимодействия.

Что именно попало в выборку

Основной период наблюдения — с середины мая по 17 августа 2026 года. Окна данных различаются: Cowrie и PCAP традиционного стенда начинаются 17 мая, PCAP AI-стенда — 18 мая, его нормализованные app/nginx logs — 31 мая. HTTP-журналы Bitrix-фасада сохранились только с 24 июня. Поэтому суммы ниже нельзя использовать для прямого сравнения «какой сервер атаковали чаще».

Источник

Объём

Cowrie events

2 512 805

Cowrie SSH-сессии

312 932

Введённые SSH-команды

397 205

MySQL sensor events

952 358

HTTP requests на SCADA/Bitrix за доступное окно

82 069

AI app events

53 868

HTTP requests на AI gateway

115 303

Сохранённые AI raw bodies

34 020

Непустые уникальные SSH payloads

183

PCAP двух стендов

14,49 ГБ

Агрегация выполнялась потоково по всем доступным ротациям. Для Cowrie удалялись точные дубликаты событий. IP использовались только для локальной группировки сессий и кампаний, а в публикационный набор не вошли. Учётные данные считались по хешам и множествам: статья не содержит даже «самые популярные пароли», потому что для анализа поведения они почти ничего не добавляют.

Есть ещё одна принципиальная оговорка: событие не равно атаке. Один SSH-сеанс создаёт события соединения, версии клиента, обмена ключами, логина, ввода команды, скачивания и закрытия. Один MCP-клиент создаёт initialize, notifications/initialized и tools/list. Считать каждую строку отдельной атакой — удобный способ получить большое число и потерять смысл.

312 тысяч SSH-сессий: огромный объём, малая глубина

Cowrie принял 312 932 сессии от 7895 уникальных источников. Политика приманки разрешала многие комбинации логина и пароля, поэтому 292 768 login.success означают только переход в эмулированную оболочку. Реальный системный SSH они не затрагивали.

Распределение команд по сессиям лучше любых топов показывает характер трафика:

Команд в сессии

Сессий

0

47 059

1

245 386

2–5

5271

6–20

15 123

21–100

93

Медианная сессия длилась 0,65 секунды. На p95 приходилось 25,6 секунды, на p99 — 120 секунд. Почти четыре пятых подключений после входа выполняли ровно одну команду.

Чаще всего это была проверка ядра и архитектуры через варианты uname, иногда — короткий маркер, позволяющий убедиться, что оболочка отвечает. Дальнейший сценарий зависел от результата:

login  └─ shell отвечает?       ├─ нет  → закрыть соединение       └─ да           ├─ определить ОС/архитектуру           ├─ выбрать wget/curl/tftp           ├─ скачать подходящий payload           ├─ подготовить права и запуск           └─ добавить cron/systemd/SSH persistence

Эвристическая классификация 397 205 команд дала 230 359 событий разведки хоста, 33 884 IoT/botnet-like событий, 24 633 попытки очистки или вмешательства в журналы, 24 516 операций подготовки прав и запуска, 7018 команд-загрузчиков и 5180 действий вокруг SSH persistence. Категории пересекаются: одна строка может скачать файл, изменить права и запустить его.

Так выглядит оптимизация массового заражения. Бот не исследует каждый сервер — он задаёт минимальный набор вопросов, достаточный для выбора следующего шага. Большое число коротких сессий здесь важнее редких длинных диалогов: оно показывает, насколько дешёвой стала проверка уже подобранной пары логин/пароль.

Полтора часа внутри поддельного Bitrix

На фоне автоматизации выделилась цепочка 26 мая. Один источник возвращался с 18:16 до 19:47 UTC серией коротких SSH-сессий. Их нельзя механически склеить только по времени, поэтому я сопоставлял источник, команды, рабочие каталоги и переходы между артефактами.

Последовательность получилась такой:

  1. Проверка пользователя, shell и попытки повысить привилегии через sudo и su.

  2. Обход /var, /home и каталогов веб-приложения.

  3. Чтение Bitrix-конфигурации, клиентского MySQL-конфига, истории shell, SSH- и почтовых настроек.

  4. Подстановка найденных ложных учётных данных в команды PHP/MySQL.

  5. Попытка перечислить таблицу пользователей и построить дамп базы.

  6. Попытки создать или скачать PHP-файл в web root.

  7. Проверка слушающих сокетов и /proc/net/tcp.

Санитизированная логика команд выглядела примерно так:

find <web-root> -maxdepth 3 -type f \( -name '*.php' -o -name '.my.cnf' \)sed -n '1,220p' <bitrix-config>mysql -h 127.0.0.1 -u <fake-user> -p'<fake-password>' <fake-db>mysqldump -h 127.0.0.1 -u <fake-user> -p'<fake-password>' <fake-db>ss -lntpcat /proc/net/tcp

Декорация сработала потому, что отдельные файлы складывались в связную историю. В конфигурации веб-приложения была база, база выглядела пригодной для запроса, каталог напоминал реальный web root, а соседние файлы подтверждали легенду. Одиночный «секретный файл» без окружения, вероятно, дал бы одно чтение. Связный набор артефактов позволил увидеть переход от discovery к намерению получить данные и затем к попытке web persistence.

При этом красивого финала SSH → MySQL → дамп в данных нет. Шесть сессий от двух источников обращались к honeytoken-путям, но источник ручной цепочки не повторил найденную пару на публичном MySQL-сенсоре. Он использовал её только в эмулированной SSH-среде. Совпадение между SSH-доступом к honeytoken и внешним MySQL относится к моему smoke test.

Это отрицательный результат, который стоит сохранить. Если корреляция не подтверждает переход между протоколами, статья не должна дорисовывать его ради сюжета.

Что пытались доставить на сервер

Cowrie зарегистрировал 9049 успешных событий скачивания и 4853 неуспешных. В каталоге оказалось 5187 файловых записей, но 5004 из них были пустыми временными объектами. После удаления пустых и дедупликации по SHA-256 осталось 183 непустых файла общим объёмом около 590 МБ.

Тип

Файлов

ELF

85

Shell scripts

49

Текстовые файлы

42

HTML

6

PHP

1

Архитектуры ELF распределились так: x86-64 — 53, ARM — 10, x86 — 8, AArch64 — 7, RISC-V — 4, MIPS — 3.

Такой набор совместим с массовыми загрузчиками, которые после uname подбирают бинарник под VPS, маршрутизатор, камеру или другое Linux-устройство. Но архитектура файла не определяет семейство. Я не запускал образцы и не делал атрибуцию по одному строковому совпадению.

Статический поиск строк дал признаки загрузчиков в 109 объектах, операций с SSH в 41, systemd persistence в 39, майнинга в 30, Mirai/Mozi-like строк в 17, убийства процессов в 12 и cron persistence в 8. Эти множества пересекаются. Формулировка «30 майнеров» была бы неверной: корректно говорить о 30 объектах со строками, связанными с майнингом.

Отдельный плюс хранения по SHA-256 проявился при повторных загрузках. Тысячи событий скачивания превратились в сотни объектов, которые можно кластеризовать независимо от случайного имени файла и URL доставки. Для следующего этапа анализа нужны readelf, строки, импорты, fuzzy hashing и проверка top hashes по нескольким threat-intelligence источникам — всё без запуска на рабочей машине.

MySQL: сто тысяч паролей в одной кампании

MySQL-like сенсор собрал 952 358 событий: 476 372 соединения и 475 982 попытки входа от 1828 источников. Успешного входа не зафиксировано.

Почти весь объём создали две кампании:

  • первая — 399 871 неуспешный вход по четырём именам пользователей и ровно 100 000 уникальных паролей;

  • вторая — 50 954 входа по трём именам и 27 777 уникальным паролям.

Первая кампания дала 84% всех MySQL login events. Это хороший пример того, почему число событий нельзя переводить в число атакующих. Один процесс с большим словарём способен полностью изменить график за три месяца.

Здесь же обнаружилась ошибка наблюдаемости: сенсор не записывал timestamp внутрь события. Дату удаётся приблизительно восстановить по имени ротации, но один файл с 277 808 событиями нельзя надёжно распределить по времени. Аналитика кампаний сохранилась, а точная временная шкала — нет. Формат события нужно проектировать до публикации сервиса, а не после первой большой кампании.

Веб-сканеры не читают легенду до первого запроса

За доступное окно Bitrix-фасад получил 82 069 разбираемых запросов. Из них 50 254 закончились 404, 20 318 — 200, 4935 — 403, 3377 — 400.

Эвристики нашли 26 074 запроса с encoded-dot/traversal-like путями, 13 385 попыток поиска секретов, 3753 RCE/IoT-паттерна, 3504 запроса к admin/login-поверхностям, 2997 WordPress-запросов и 1662 Bitrix-specific пути. На сервере с промышленной легендой нашлось даже 340 обращений к MCP/AI-путям.

Зеркальный эффект был на AI gateway. Его nginx увидел 115 303 запроса от 6060 источников:

Класс пути

Запросов

Прочий HTTP

45 541

Ollama

36 721

Поиск секретов

12 600

RCE/IoT

7061

OpenAI API

4415

Encoded/traversal

3421

WordPress

2767

Admin/docs

2154

MCP

623

WordPress ищут на AI gateway, MCP — на Bitrix. До получения первого содержательного ответа многие сканеры не знают тип цели и обходят универсальный словарь путей. Легенда начинает влиять на поведение только после того, как клиент распознает знакомый протокол.

Из этого следует методический приём: nginx access log нельзя заменять логом приложения. Nginx показывает всю воронку, включая невалидный HTTP и пути, которые приложение отвергло до нормализации. App log отвечает на вопрос, какой сценарий распознан. PCAP нужен для проверки того, что реально пришло по сети, особенно при protocol confusion и ошибках парсера.

Как AI gateway превратился в бесплатный бэкенд

На уровне приложения AI-стенд собрал 53 868 событий от 3943 источников:

Протокол

Событий

Ollama

36 694

Обычный HTTP

13 935

OpenAI-compatible

2633

MCP

606

Начальная разведка была ожидаемой. Для Ollama клиент запрашивает каталог через GET /api/tags, затем пробует POST /api/generate или POST /api/chat. Эти маршруты соответствуют официальному Ollama API. OpenAI-compatible клиенты начинают с GET /v1/models и проверяют chat completions; список моделей описан в OpenAI API reference.

За весь период маршруты распределились так:

Маршрут

App events

/api/chat

31 423

/

13 826

/api/tags

2556

/api/version

2312

/v1/chat/completions

1456

/v1/models

1034

/mcp

518

/api/generate

348

/v1/embeddings

142

/sse

88

В конце июля один источник изменил всю картину. За неполные две недели он создал 27 379 app events через Python/3.12 aiohttp/3.14.3. Пять запросов пришлись на /api/tags, остальные 27 374 — на /api/chat. Заголовка Authorization не было. В запросах фигурировала модель deepseek-v4-flash:latest.

Распределение длины промптов:

Перцентиль

Символов

min

1

p50

2716

p90

155 214

p95

237 902

p99

373 599

max

919 294

В 27 374 запросах было 14 808 уникальных хешей промптов. Это не проверка работоспособности и не однообразный тест. Повторяющиеся санитизированные префиксы показывали задачи перевода, суммаризации, извлечения фактов для памяти, анализа кода и работу агентных оболочек. Встречались системные инструкции, стилизованные под Claude Code, Codex CLI, OpenCode и Hermes. По одному содержимому нельзя утверждать, что запросы отправили именно эти продукты: клиент мог скопировать шаблоны или проксировать чужой трафик.

Наиболее правдоподобная интерпретация — адрес приняли за рабочий Ollama-бэкенд и включили в пакетный или прокси-конвейер. Стенд не отвечал настоящими результатами, но клиент продолжал присылать работу. Возможны повторные постановки задач, слабая проверка качества ответа или асинхронная очередь, которая не останавливала отправителя при бесполезном обработчике. Точная причина из серверных логов не восстанавливается.

Второй источник 26–27 июля отправил ещё 1320 запросов, 1045 из которых имели уникальные хеши промптов. Средняя длина была около 50 тысяч символов, максимум — 861 334. Пересечение хешей с крупнейшей кампанией составило всего шесть элементов, то есть 0,6% меньшего набора. Объединять эти источники в одного оператора без сетевых и инфраструктурных доказательств нельзя.

Этот слой создаёт новую проблему хранения. Загруженный через SSH файл обычно является исполняемым объектом без законного ожидания приватности со стороны атакующего. В открытый AI API могут случайно проксировать пользовательские документы, код, переписку и секреты третьих лиц. Полные тела нужны для исследования, но публичный отчёт должен работать с хешами, длинами, классами и короткими вручную проверенными выдержками.

MCP: клиенты дошли до списка инструментов и остановились

MCP дал только 606 событий приложения, однако они отличаются от обычного запроса модели. В наблюдавшейся версии протокольный диалог выглядел так:

клиент                         сервер  │ POST initialize              │  ├─────────────────────────────>│  │ возможности + версия         │  │<─────────────────────────────┤  │ notifications/initialized    │  ├─────────────────────────────>│  │ tools/list                   │  ├─────────────────────────────>│  │ ложный каталог инструментов  │  │<─────────────────────────────┤

Все 497 исходных тел POST-запросов удалось связать с нормализованными событиями:

  • initialize — 214;

  • notifications/initialized — 142;

  • tools/list — 141;

  • tools/call — 0.

Методы соответствуют схеме MCP 2025-06-18: initialize согласовывал версию и возможности, а tools/list запрашивал доступный каталог инструментов. Это видно в архивной версии спецификации.

Нулевая строка tools/call важнее общего числа MCP-запросов. Клиенты распознали адрес, прошли начальное согласование и получили список, но не попытались выполнить инструмент. Я могу утверждать факт инвентаризации, но не попытку эксплуатации инструментов.

Есть и временная деталь. 28 июля 2026 года новая редакция MCP отказалась от initialize/initialized и сессионной модели в пользу независимых запросов и server/discover. Изменение описано в блоге проекта MCP. Наблюдавшийся трафик использовал прежнее согласование даже после публикации новой редакции. Для детектирования это означает, что сигнатуры нельзя мгновенно переписывать только под последний вариант спецификации: клиенты и сканеры продолжают говорить на старых версиях.

Две воронки автоматизации

Если свести наблюдения к стадиям, различие двух поверхностей становится заметнее абсолютных чисел.

Стадия

SCADA/Bitrix

AI gateway

Обнаружение

SSH banner, HTTP paths, MySQL handshake

HTTP paths, /api/version, /api/tags, /v1/models

Проверка доступа

Подбор SSH/MySQL credentials

Обычно запрос без Authorization, иногда Bearer

Инвентаризация

uname, BusyBox, каталоги, процессы, сокеты

Каталог моделей, совместимость API, возможности MCP

Полезная работа

Команды shell, скачивание ELF/scripts

Chat, generate, embeddings, большие batch prompts

Попытка расширить влияние

SSH key, cron/systemd, PHP в web root

Поиск MCP tools и соседних admin/docs endpoints

Наблюдаемый предел

Эмулированная файловая система

Нет inference, egress и исполнения tools

Старый бот минимизирует стоимость одного хоста. Он за доли секунды проверяет окружение и выбирает бинарник. AI-клиент, напротив, может прислать сотни килобайт контекста до того, как убедится в качестве ответа. В первом случае ценность цели — shell и persistence. Во втором — совместимый API и предполагаемый вычислительный ресурс.

Обе воронки начинаются одинаково: с дешёвой массовой разведки. Отличие появляется после правдоподобного ответа. Поэтому глубина ханипота влияет не столько на количество соединений, сколько на качество получаемой телеметрии.

Что сломалось в измерениях

Три месяца дали несколько ошибок, которые проще исправить в следующем стенде, чем объяснять в каждом отчёте.

1. Ротация не гарантирует нужный retention

PCAP запускался с ограничением количества файлов, но имена включали timestamp. В такой комбинации ожидаемое недельное кольцо не получилось: на диске сохранилось почти всё окно наблюдения — 2117 файлов и 10,42 ГБ на первом стенде, 2044 файла и 4,07 ГБ на втором.

Это удача для исследования и плохая эксплуатационная практика. Retention надо проверять фактическим возрастом самого старого файла и занимаемым объёмом, а не наличием параметра в unit-файле.

2. Repository config и live config расходятся

В репозитории AI-стенда logrotate задавал 14 ротаций, на сервере — 90. Большое окно данных появилось благодаря live-настройке, но воспроизвести сервер из репозитория без сверки уже нельзя. Для долгого эксперимента конфигурационный дрейф — часть качества данных.

3. Один источник логов не покрывает всю воронку

App parser пропустил часть невалидного HTTP и протокольной путаницы, которые видны nginx. Nginx не знает смысл нормализованного response class и не связывает запрос с raw-body hash. Оба слоя не заменяют PCAP при разборе странного транспорта.

Минимальная связка для следующего стенда:

network flow / PCAP        ↓ request_id или временная корреляцияreverse proxy access log        ↓ request_idprotocol-normalized event        ↓ body hashencrypted raw-object storage

4. Полный raw capture создаёт собственный риск

AI-стенд накопил 2,39 ГБ raw bodies. В них могут быть не данные атакующего, а данные пользователей плохо настроенного прокси. Для публичной аналитики достаточно производных: SHA-256, длина, тип задачи, модель, маршрут, наличие auth и короткая санитизированная выборка. Полные тела должны иметь отдельные доступ, retention и процедуру удаления.

5. Эвристика не равна атрибуции

Строка xmrig, архитектура MIPS или шаблон Mirai-like повышают приоритет образца, но не называют семейство. User-Agent Codex не доказывает использование Codex. GeoIP указывает на точку выхода, а не на оператора. Honeytoken подтверждает переход только при совпадении контекста, времени и источника, а не потому, что красиво продолжает историю.

Что стоит защищать на реальных серверах

Наблюдения дают несколько конкретных требований, одинаково применимых к приманкам и боевым системам.

Для SSH и традиционного веб-стека:

  • отделять административный доступ от публичной поверхности и отключать парольную аутентификацию;

  • отслеживать замену authorized_keys, новые cron/systemd units и запись исполняемых файлов в web root;

  • считать цепочки login → uname → downloader → chmod → exec, а не отдельные команды;

  • не хранить секреты в доступных из web root конфигурациях и shell history;

  • карантинировать скачанные объекты по хешу без исполнения на аналитической машине.

Для self-hosted AI:

  • не публиковать Ollama, OpenAI-compatible и MCP endpoints напрямую; ставить auth и rate limits перед ними;

  • ограничивать не только число запросов, но и размер контекста, токены, параллелизм и суточный compute budget;

  • разделять inference и инструменты по зонам доверия;

  • запрещать tool execution и исходящую сеть по умолчанию, отдельно закрывать metadata, loopback, RFC1918 и внутренние DNS-зоны;

  • журналировать route, model, auth presence, response class, tool name, request ID и raw-body hash;

  • детектировать одновременно новые и старые версии MCP, пока клиентская экосистема мигрирует;

  • вводить отдельную политику приватности и retention для промптов.

Для следующей версии ханипотов я бы добавил общий идентификатор кампании, единую схему времени, контролируемый off-host export и автоматические проверки фактического retention. А вот увеличивать interaction до реального shell или настоящих MCP tools без отдельной изоляции и egress policy не стал бы: прирост реалистичности не компенсирует риск превратить исследовательский сервер в рабочую инфраструктуру чужой кампании.

Вместо вывода

За три месяца традиционный стенд собрал миллионы событий, но большая часть его посетителей укладывалась в одну команду и доли секунды. AI gateway получил на два порядка меньше app events, зато один клиент принёс десятки тысяч настоящих задач и промпты почти на миллион символов. Самая содержательная человеческая SSH-цепочка и самая крупная AI-кампания проявились по одной причине: приманка отвечала достаточно связно, чтобы клиент перешёл от проверки порта к своей цели.

Ханипот полезен не количеством пойманных IP. Его качество измеряется тем, насколько надёжно он отделяет соединение от сессии, сессию от кампании, автоматическую проверку от намерения — и насколько честно исследователь сохраняет отрицательные результаты, когда эффектная цепочка не подтверждается данными.

Источники и связанные материалы

ссылка на оригинал статьи https://habr.com/ru/articles/1071286/