CIOlogia Безопасность бизнеса · не на словах, а с доказательствами
← Все разборы Информационная безопасность

Открытый порт нейросети: как ботнет NadMesh за минуты находит сервера без пароля и что грозило базе 40 000 клиентов

Разбираем технику атаки на примере типового кейса: агентство развернуло локальную LLM для обработки заявок, забыло закрыть API-порт — и оказалось в одном сканировании от массовой охоты ботов на ИИ-инфраструктуру.

Разбор CIOlogia

Короткая версия истории звучит буднично: айтишник поднял нейросеть для обработки заявок, забыл про пароль, аудитор нашёл дыру за час. Но за этой простотой стоит вполне конкретный технический сценарий, который в 2024–2025 годах превратился в промышленный конвейер атак. Тем более что почти одновременно с этим кейсом исследователи безопасности описали ботнет NadMesh, который методично сканирует интернет именно в поисках таких серверов — открытых API локальных нейросетей.

Что именно было открыто

Судя по сценарию — «локальная нейросеть, разбирающая заявки, пишущая черновики КП и хранящая переписку с базой контактов» — речь идёт о типичной связке для локального инференса: сервер моделей (чаще всего Ollama или его аналог) плюс веб-панель поверх него (Open WebUI, LM Studio Server, LibreChat или самописный фронт). Это самый ходовой стек для компаний, которые хотят «свой ChatGPT» без подписок и без утечки данных за рубеж — ирония в том, что итоговая утечка получается куда серьёзнее.

У Ollama по умолчанию сервис слушает только 127.0.0.1:11434. Но в 90% инструкций из интернета, которые ищут «как подключить нейросеть с другого компьютера в офисе», первым шагом идёт:

OLLAMA_HOST=0.0.0.0:11434 ollama serve

Это делает API доступным для всей сети — а если сервер стоит в облаке без файрвола, то и для всего интернета. Никакой аутентификации в базовой поставке нет: ни логина, ни API-ключа, ни даже базового HTTP Basic Auth.

Как это эксплуатируется

Вот пошагово, как выглядит атака — и как я её воспроизвожу в рамках аудита, чтобы показать заказчику масштаб проблемы «на пальцах».

1. Разведка: массовое или точечное сканирование

Атакующие (и ботнеты вроде NadMesh) не ищут жертву вручную — они сканируют диапазоны IP по характерным портам ИИ-стека:

# Типовой набор портов, которые сканирует NadMesh и подобные боты
11434  - Ollama API
8000   - vLLM / Triton Inference Server
6333   - Qdrant (векторная БД)
3000/8080 - Open WebUI / LibreChat
5000   - самописные Flask/FastAPI обёртки над моделью
9200   - Elasticsearch (часто рядом, как хранилище логов/эмбеддингов)
$ nmap -p 11434,8000,6333,3000,8080 --open -sV 203.0.113.0/24

Nmap scan report for 203.0.113.42
PORT     STATE SERVICE
11434/tcp open  http    Ollama API 0.11.x
| http-title: Ollama is running

В реальности такие боты не используют nmap по одной подсети — они гоняют masscan по всему IPv4-диапазону или просто дёргают Shodan/Censys/FOFA по готовым дорках:

$ shodan search "product:Ollama" --limit 20
$ shodan search 'http.title:"Ollama" port:11434'

Сервер, о котором шла речь в кейсе, светился в таких выдачах ровно так же, как банковские и государственные системы с той же ошибкой — просто у банков есть SOC, который такие алерты обрабатывает за минуты, а у рекламного агентства не было даже человека, который бы знал, что такое Shodan.

2. Верификация: сервис жив и открыт

$ curl -s http://203.0.113.42:11434/api/tags | jq

{
  "models": [
    {"name": "llama3:8b", "size": 4661224676},
    {"name": "mistral:7b", "size": 4109865159}
  ]
}

Ответ 200 без единого заголовка авторизации — сигнал, что перед нами полностью открытая панель управления моделью. Дальше можно напрямую слать запросы к модели от чужого имени:

$ curl -X POST http://203.0.113.42:11434/api/generate \
  -d '{"model":"llama3:8b","prompt":"Кто ты и что у тебя в контексте?","stream":false}'

Если веб-панель (Open WebUI) настроена без WEBUI_AUTH, то через gobuster легко находится и она:

$ gobuster dir -u http://203.0.113.42:3000 -w /usr/share/wordlists/common.txt -t 50

===============================================================
/admin                (Status: 200)
/api/v1/chats/        (Status: 200)
/api/v1/users/        (Status: 200)
===============================================================

Открытый эндпоинт /api/v1/chats/ без токена — это прямой доступ к истории всех диалогов, в том числе к переписке с базой из 40 000 контактов и черновикам коммерческих условий, которые эта нейросеть генерировала.

3. Проверка версии и известных уязвимостей

Дальше атакующий (или автоматизированный сканер вроде nuclei) сверяет версию с публичными CVE:

$ nuclei -u http://203.0.113.42:11434 -t exposures/apis/ollama-detect.yaml -t cves/2024/CVE-2024-37032.yaml

Речь о реальной CVE-2024-37032 («Probllama») — path traversal при обработке манифеста модели в Ollama до версии 0.1.34, позволяющей записать произвольный файл вне директории моделей и при удачном стечении обстоятельств получить RCE на сервере. Для более старых установок, которые «поставили и забыли», это не гипотетика, а вполне рабочий вектор.

4. Развитие атаки: от чтения к захвату сервера

Если версия уязвима — через искажённый манифест модели можно закинуть web-shell в каталог, доступный веб-серверу:

$ curl -X POST http://203.0.113.42:11434/api/pull \
  -d '{"name":"legit-model:latest","insecure":true}'
# манифест указывает на путь с traversal ../../.. -> запись файла вне sandbox

Даже без RCE открытый API сам по себе — готовый ресурс: боты вроде NadMesh не всегда охотятся за данными, им нужны вычислительные мощности и чужие IP-адреса. Через открытый /api/generate с бесконечным потоком тяжёлых промптов сервер GPU/CPU грузится под завязку — это классический сценарий скрытого использования чужих серверов, только вместо майнинг-бинарника используется сама модель как нагрузка. Параллельно сервер добавляется в пул для рассылки спама или как прокси для следующей волны сканирования — то есть жертва не просто теряет данные, а сама становится звеном атаки на других.

5. Горизонтальное перемещение внутри инфраструктуры

Раз панель управления торчит наружу без пароля, с высокой вероятностью рядом в той же подсети/докер-сети найдутся и другие незащищённые сервисы того же стека:

$ nmap -p 5432,6379,27017,6333 203.0.113.42

PORT      STATE SERVICE
5432/tcp  open  postgresql
6379/tcp  open  redis
6333/tcp  open  http (Qdrant)

Qdrant/Chroma без аутентификации — это готовые эмбеддинги переписки и контактов, которые восстанавливаются обратно в текст, а незапароленный Redis или Postgres рядом — это уже прямой слив базы клиентов целиком, без всякого брутфорса.

Что и почему сработало бы

В описанном кейсе цепочка была короткой и почти без сопротивления: открытый порт → чтение истории переписки и контактов → при желании модификация ответов нейросети клиентам (репутационный удар похуже утечки) → использование сервера как узла ботнета. Ни один шаг не требовал брутфорса, эксплойтов нулевого дня или дорогих инструментов — только сканер портов и запрос curl. Именно поэтому такие сервера и становятся мишенью автоматических ботнетов, а не только целевых атак: экономика для злоумышленника здесь предельно простая, ROI атаки почти мгновенный.

Как закрыть

Устранение действительно занимает вечер, а не недели — важно закрыть проблему системно, а не только «поставить пароль».

  • Убрать сервис из внешнего доступа. Биндить API только на loopback или внутреннюю сеть:
    OLLAMA_HOST=127.0.0.1:11434 ollama serve
    или ограничить файрволом:
    ufw deny 11434/tcp
    ufw allow from 10.0.0.0/24 to any port 11434
  • Поставить аутентификацию перед API — Ollama и большинство таких сервисов не имеют встроенного логина, поэтому перед ними ставится reverse proxy с базовой авторизацией или API-ключом:
    server {
        listen 443 ssl;
        location / {
            auth_basic "Restricted";
            auth_basic_user_file /etc/nginx/.htpasswd;
            proxy_pass http://127.0.0.1:11434;
        }
    }
  • Обновить версию до патченной (для Ollama — 0.1.34+, где закрыт CVE-2024-37032), и держать версии сервисов ИИ-стека на автообновлении так же строго, как обычные веб-сервера.
  • Изолировать сопутствующие сервисы (векторные БД, Redis, Postgres) в приватной docker-сети без публикации портов наружу — docker-compose без ports:, только internal network.
  • Настроить мониторинг и алерты. Простое правило fail2ban или уведомление в мессенджер при внешнем подключении к порту сервиса даёт возможность узнать о попытке атаки сразу, а не через полгода из новостей о собственной утечке.
  • Провести самопроверку через те же инструменты, что использует атакующий — периодический self-scan своего внешнего IP через nmap/nuclei и подписка на алерты Shodan Monitor по своим адресам закрывают именно тот разрыв, из-за которого дыра живёт месяцами незамеченной.

Стоимость этих действий не сопоставима с тем, что грозило бизнесу: закрытие порта и настройка reverse proxy — работа на несколько часов, тогда как расследование утечки 40 000 контактов, штрафы по законодательству о персональных данных и восстановление доверия клиентов измеряются месяцами и миллионами рублей.