April 25

Постмортем: компрометация VPS, контейнерный майнер и ложная уверенность в «знакомых IP»

На связи Verlliann.

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

Это детальный разбор инцидента на sirius-nql: от первого входа до форензики, гипотез, верификации и выводов. Текст ориентирован на публикацию в Telegraph, но его же можно использовать как внутренний IR-документ.


Executive Summary

Когда я впервые открыл метрики и процессы, это выглядело как типичный «шум на VPS». Но довольно быстро стало понятно: это не шум, а полноценный инцидент.

На сервере был подтвержден security-инцидент:

  • получен root-доступ по SSH (парольная аутентификация),
  • зафиксированы многочисленные успешные root-сессии,
  • внутри контейнера docs_llm_frontend запущен криптомайнер и watchdog,
  • найден валидный конфиг майнинга с пул-инфраструктурой,
  • обнаружены PakChoi-like артефакты закрепления внутри контейнера.

Критичный нюанс расследования: часть IP из логов относится к VPN/провайдерской инфраструктуре команды. Это не отменяет компрометацию, но меняет модель атрибуции: опираться нужно на действия и артефакты, а не только на IP.


Контекст и исходные условия

Сервис и среда

  • Хост: sirius-nql.play2go.cloud
  • ОС: Ubuntu 24.04 LTS
  • Контейнерный рантайм: Docker
  • Скомпрометированный контейнер: docs_llm_frontend

Операционный контекст команды

Часть действий, которые в логах могут выглядеть «подозрительно» (пакетные установки, подготовка runtime, изменения docker-окружения), выполнялась штатно в рамках нашего пайплайна AI-агента.
В расследовании эти действия вынесены в отдельную категорию как легитимные, чтобы не смешивать их с подтвержденной вредоносной активностью.

Затронутые компоненты (из инцидент-репорта)

  • Хостовая система Linux (root-доступ).
  • Контейнерный контур приложения (Next.js frontend + связанные сервисы).
  • Секреты, доступные root-пользователю в shell history.

Риск-конфигурация доступа

На хосте в момент инцидента:

  • PermitRootLogin yes
  • PasswordAuthentication yes

Это означает прямую поверхность атаки для root SSH brute-force / credential stuffing.


Scope расследования

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

В расследование вошли:

  1. Исторические логи из выгрузок (server_logs_export, nginx_logs, docker.log, all_raw, all_containers).
  2. Live-forensics на хосте:
    • процессы, дерево процессов, /proc, сеть,
    • docker inspect, docker diff, контейнерные артефакты,
    • копирование бинарников и конфигов, хэширование.
  3. Проверка гипотез о host-level persistence.
  4. Перепроверка атрибуции после уточнения trusted/VPN IP команды.

Главные находки

Ниже — то, что я смог подтвердить фактически, а не предположить.

1) Подтвержденная компрометация root-доступа

В логах зафиксированы успешные входы root по паролю.

Подтвержденные IP с успешными root-входами:

  • 45.83.140.194
  • 95.32.25.170
  • 144.31.203.175
  • 194.87.220.163 (массовая серия)

Агрегированно по Accepted password for root:

  • 194.87.220.163 — 78
  • 45.83.140.194 — 7
  • 144.31.203.175 — 2
  • 95.32.25.170 — 1

2) Подтвержден запуск вредоноса внутри контейнера

Активные процессы во время live-сбора:

  • майнер: /RKiQ2aLh -c /kBiR -B
  • watchdog: /rjtg0pT

Parent chain:

containerd-shim -> sh -c "npm install && npm run dev -- -H 0.0.0.0 -p 3000" -> {майнер, watchdog}

Наблюдаемая нагрузка (из инцидент-репорта)

  • CPU: до ~392% суммарно (многопоточный майнинг).
  • RAM: около ~2.4 GB RSS у процесса майнера.
  • Эффект: устойчивый рост load average до ручного вмешательства.

3) Подтвержден рабочий майнинг-конфиг

Файл /kBiR содержит:

  • RandomX/cpu-параметры,
  • пул-узлы:
    • 91.208.184.203:{80,443,53,123}
    • 45.196.97.119:{80,443,53,123}
    • 156.246.94.183:{80,443,53,123}
  • логин/пароль майнинга: imeatingpoop / imeatingpoop.

4) Подтвержденные IOC-хэши

  • aa0c6cdbeb3bc3ce07d5060958e1a38e54287bc89e25f6def98b620999ff4f7a — майнер
  • 7d6032764df8e706c458e663e5501ce627e4f2985c16ea61e299f2ac429cbcc9 — watchdog
  • eada4bb685c53f0a5b7aa0dad5aa4d31aebba94af8b84f40f2802e828a87f304 — конфиг

Дополнительная находка: риск компрометации API-токенов

В shell history были обнаружены переменные вида ANTHROPIC_* и использование стороннего base URL (aiprimetech.io).
Это трактуется как потенциальная компрометация секретов и требует обязательной ротации/отзыва.

5) PakChoi-like stage внутри контейнера

В docs_llm_frontend обнаружено:

  • pakchoi:x:0:0::/home/pakchoi:/bin/bash
  • /etc/systemd/system/sys-health.service
  • /etc/systemd/system/sys-health.timer
  • refs на docker start amco_ee51c4e6
  • изменения в /etc/group, /etc/shadow, /home/pakchoi/.bashrc

6) Host-level persistence именно этим stage не подтверждена

На хосте не найдено:

  • пользователя pakchoi,
  • host unit-файлов sys-health.*,
  • контейнера amco_ee51c4e6.

Дополнительно:

  • в контейнере отсутствует /var/run/docker.sock,
  • PID 1 контейнера — sh, не systemd.

Практический смысл: контейнерный stage есть, но его host-персистентность в текущем виде, вероятно, не работала.


Подробный таймлайн (UTC)

Ниже — ключевые точки, которые я сверил по логам и live-артефактам.

Фаза A: первичный доступ и ранняя активность

  • 2026-04-25 04:00:15 — первый подтвержденный Accepted password for root (45.83.140.194).
  • 2026-04-25 04:02:21Accepted password for root (95.32.25.170).
  • 2026-04-25 04:03:59 — root выполняет apt update (подтверждено как легитимное действие команды).
  • 2026-04-25 04:04:14 — root выполняет apt install unzip (подтверждено как легитимное действие команды).
  • 2026-04-25 04:04:59 — root выполняет bash - (подтверждено как легитимное действие команды).
  • 2026-04-25 04:05:08 — root выполняет apt install -y nodejs (подтверждено как легитимное действие команды).
  • 2026-04-25 04:10:52 — создается группа docker (подтверждено как легитимное действие команды).

Комментарий: эти команды изначально рассматривались как потенциально подозрительные из-за близости по времени к компрометации, но после уточнения команды помечены как штатные админ-действия, связанные с пайплайном AI-агента, и исключены из IOC-логики.

Фаза B: дополнительные успешные root-сессии

  • 2026-04-25 05:11:04 — успешный вход (45.83.140.194).
  • 2026-04-25 05:13:05 — успешный вход (45.83.140.194).
  • 2026-04-25 05:19:29 — успешный вход (144.31.203.175).
  • 2026-04-25 07:00:01 — успешный вход (45.83.140.194).
  • 2026-04-25 10:10:30 — успешный вход (45.83.140.194).

Фаза C: автоматизированная волна root-логинов

  • 2026-04-25 10:47:51 — старт серии root-сессий с 194.87.220.163 (помечено как активность пайплайна AI-агента команды).
  • 2026-04-25 10:47:51–11:43:51 — частые короткие сессии (accept/disconnect паттерн) (помечено как активность пайплайна AI-агента команды).
  • Всего в этой волне — 78 успешных Accepted password for root (помечено как активность пайплайна AI-агента команды).

Комментарий: с учетом уточнений команды этот блок в таймлайне отмечен как автоматизированная активность вашего AI-пайплайна и не используется как самостоятельный индикатор злонамеренной сессии без дополнительных артефактов.

Фаза D: контейнерная активность

  • 2026-04-25 11:23:02 — creation docs_llm_frontend.
  • 2026-04-25 11:23:14 — container start.
  • 2026-04-25 11:23:20–11:23:23npm install + запуск next dev в логах контейнера.

Комментарий: это штатный рантайм контейнера, но позже он становится хостом для вредоносных процессов.

Фаза E: запуск майнера

  • 2026-04-25 14:49:59 — старт watchdog /rjtg0pT.
  • 2026-04-25 14:50:08 — старт майнера /RKiQ2aLh -c /kBiR -B.
  • Далее — стабильная высокая CPU-нагрузка и пул-коннекты.

Ключевой момент, который я отдельно перепроверял: запуск вредоноса происходит заметно позже npm install и после окна компрометации root-активности.


Проверка версий и оценка уверенности

Гипотеза 1: «Это не инцидент, просто шум»

  • Вердикт: ложно.
  • Почему: есть живой майнер, watchdog, валидный конфиг, пул-инфраструктура, IOC.

Гипотеза 2: «Это npm supply-chain в момент install»

  • Вердикт: не основная версия.
  • Почему: таймлайн не сходится (install в 11:23, вредонос стартует 14:50).

Гипотеза 3: «PakChoi-like stage присутствует»

  • Вердикт: подтверждено внутри контейнера.
  • Почему: pakchoi, sys-health.*, amco_* refs, системные правки в container FS.

Гипотеза 4: «Host-level persistence через эти файлы активна»

  • Вердикт: не подтверждено.
  • Почему: нет host артефактов, нет docker.sock в контейнере, PID1 не systemd.

Гипотеза 5: «По IP можно точно назвать атакующего»

  • Вердикт: некорректно (после уточнения trusted/VPN IP).
  • Почему: IP может быть shared egress, VPN, proxy или компрометированный endpoint.

Trusted/VPN IP и как это правильно трактовать

Команда подтвердила, что часть IP в логах — потенциально легитимные адреса их инфраструктуры.

Это меняет не факт инцидента, а модель атрибуции:

  • Нельзя: «IP = злоумышленник».
  • Нужно: «Есть подтвержденные вредоносные действия, источник сессий требует отдельной identity/endpoint-проверки».

То есть фокус переносится с «кто по IP» на «кто по учетке/устройству/сессии».

Для меня это был важный разворот в расследовании: сначала казалось, что IP даст однозначный ответ, но в реальности именно поведение и артефакты дали устойчивую картину.


Root Cause Analysis

Техническая первопричина

Открытый root SSH по паролю на интернет-периметре.

Усиливающие факторы

  • нет строгого источникового контроля SSH,
  • недостаточная brute-force защита,
  • недостаточно раннее обнаружение аномалий,
  • присутствие чувствительных данных в shell history.

Impact

Подтверждено

  • компрометация root-доступа,
  • выполнение несанкционированных действий,
  • запуск майнинга и расход вычислительных ресурсов,
  • потенциальная компрометация секретов, доступных root-пользователю.

Не подтверждено в текущем scope

  • устойчивое host-level закрепление через найденный контейнерный stage,
  • точная личная атрибуция субъекта по одному IP.
  • подтвержденная массовая эксфильтрация данных (в текущей выборке нет явных индикаторов).

Что отдельно проверяли и не нашли

  • host-level authorized_keys-бэкдор для root;
  • явную persistence через host cron в собранных срезах;
  • новых локальных пользователей на хосте, связанных с контейнерным pakchoi stage.

Важно: «не найдено в текущем scope» не равно «гарантированно отсутствует».


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

  • сняты live-артефакты до агрессивной зачистки,
  • бинарники/конфиги выгружены и хэшированы,
  • выполнена декомпозиция «контейнер vs хост»,
  • гипотезы разделены по степени доказанности.

План ремедиации

Немедленно (P0)

  1. Выключить root password SSH:
    • PermitRootLogin no
    • PasswordAuthentication no
  2. Перевести доступ на SSH-ключи + allowlist.
  3. Ротировать все секреты/токены.
  4. Удалить скомпрометированный контейнер и пересобрать из trusted source.
  5. Блокировать egress к IOC-пулам.

Краткосрочно (P1)

  1. Проверить endpoints участников команды на stealer/credential theft.
  2. Провести аудит VPN/SSH сессий и ротацию учетных данных.
  3. Включить fail2ban/аналог и защиту от brute-force.
  4. Поднять алерты на root-login spikes, high CPU, suspicious egress.

Среднесрочно (P2)

  1. Убрать shared-root практики.
  2. Ввести персонализированный админ-доступ с audit trail.
  3. Исключить dev-runtime режимы в production.
  4. Подготовить золотой образ и регламент быстрого rebuild.

Главные уроки

  1. «Знакомый IP» — это контекст, не гарантия легитимности.
  2. Компрометацию нужно доказывать поведением и артефактами.
  3. Root SSH по паролю в интернет — это не «если», а «когда».
  4. Контейнерная изоляция может ограничить ущерб, но не отменяет инцидент.

Финальный вывод

Инцидент подтвержден: вредоносная майнинг-нагрузка была развернута и запускалась на скомпрометированной инфраструктуре.

Уточнение по trusted/VPN IP делает расследование сложнее в части атрибуции, но не меняет технический факт компрометации.

Корректная итоговая формулировка:

  • компрометация подтверждена по действиям и артефактам;
  • атрибуция по субъекту требует дополнительной identity/endpoint проверки.

Благодарность

Отдельное спасибо за помощь в разборе: https://t.me/Anon1379