Как искать скрытый майнер на работающем сайте
Высокая нагрузка на сайт сама по себе не доказывает, что там майнер. Я проверяю, какой процесс расходует CPU, с чем он соединяется и какие файлы менялись, затем сохраняю журналы и копию перед очисткой. Так можно начать разбор без немедленной остановки сайта и не спутать атаку с обычной нагрузкой.
1. Что в нагрузке действительно подозрительно
Начинаю с top или htop: смотрю на процессы, пользователя, длительность работы и изменение нагрузки во времени. Ищу связь с веб-запросами: если пик появляется вместе с посещаемостью, причина может быть в обычной работе CMS, импорте, генерации страниц или неудачном запросе к базе. Если нагрузка держится и при затишье, это повод расследовать, но не доказательство майнера. У PHP-процесса не всегда видно имя конкретного файла, а имя php-fpm само по себе ничего не говорит о его добросовестности.
В условном разборе «Лагуна-М» я сперва сохраняю время наблюдения, PID, пользователя и снимок нагрузки, а затем сопоставляю их с журналом веб-сервера и планировщика. Это простое действие помогает отличить подозрительный скрипт от штатной фоновой задачи. Если вы передали сайт на аудит безопасности, попросите зафиксировать исходные признаки до изменений. Иначе расследование быстро превращается в «вчера что-то было, сегодня уже не найти».
Для просмотра командной строки процесса годится ps -p PID -o pid,user,etime,args. В PID подставляют номер процесса, а не буквальный текст PID. Вывод полезен, но не гарантирует путь к PHP-скрипту: у долгоживущего PHP-FPM обработчика командная строка может показать только управляющий процесс. Путь уточняют по журналам веб-сервера, окружению процесса и файловым событиям, если такие данные собираются.
Не отправляю kill -9 наугад. Сигнал немедленно завершает процесс и может оборвать запрос, потерять полезные данные для анализа или затронуть легитимную задачу. Сначала фиксирую доступные сведения и выясняю владельца процесса; при подтверждённой угрозе выбираю согласованное с владельцем сайта сдерживание. Если сайт обслуживает критичную операцию, заранее оцениваю влияние любого действия на пользователей. Быстрый удар по PID эффектен, но расследование от этого не становится точнее.
- Сохранить время, PID, пользователя и снимок нагрузки.
- Сопоставить CPU с трафиком и плановыми задачами.
- Посмотреть аргументы процесса через `ps -p PID -o pid,user,etime,args`.
- Не считать один высокий показатель подтверждением заражения.
2. Как проверить исходящие соединения
На Linux список сокетов можно посмотреть командой ss -tunap. Параметр -p показывает связанные с сокетом процессы, если у пользователя есть необходимые права. Вывод сохраняю вместе со временем проверки. Смотрю на повторяющиеся исходящие соединения и сопоставляю их с PID, DNS-именами, конфигурацией приложения, журналами и известными интеграциями. Соединение на 80 или 443 порт не делает адрес подозрительным автоматически: там работают API, CDN, обновления и внешние сервисы.
У майнера может быть сетевое взаимодействие с пулом, но конкретный порт не является надёжным маркером. Протоколы и адреса меняются, а легитимный сайт общается с множеством внешних узлов. Поэтому в черновой статье я убрал непроверенный диапазон IP: без журналов и подтверждённой репутации адреса это не факт о заражении, а случайное обвинение. Белый список полезен лишь тогда, когда его поддерживают и понимают, какие зависимости сайта он описывает.
Если в ss виден PID, свяжите его с процессом и проверьте открытые файлы. Например, lsof -nP -p PID -i показывает сетевые файлы процесса; PID снова заменяется числом. Команды могут требовать прав администратора, а отсутствие результата не доказывает отсутствие активности: соединение могло завершиться до проверки. Для краткоживущих задач нужны журналы или инструмент наблюдения, который собирает данные постоянно.
Не блокирую адрес по принципу «не узнаю — значит вредоносный». Сначала выясняю, кому он принадлежит и какой компонент его вызвал; затем при подтверждённом риске согласую правило с владельцем сайта и проверяю, что не ломаются внешние функции. Так меньше шансов выключить собственную оплату или загрузку изображений, объявив это победой над атакой.
- Сохранить `ss -tunap` с отметкой времени.
- Связать сокет с PID и проверить процесс.
- Сверить адрес с зависимостями сайта и журналами.
- Не блокировать IP только из-за незнакомого имени.
3. Какие файлы и признаки искать
Дальше сравниваю дерево файлов с чистой резервной копией или официальным дистрибутивом CMS и плагинов. Время изменения помогает сузить поиск, но не датирует заражение: вредоносный файл могли положить давно, а время на сервере — изменить. Пример команды для недавних PHP-файлов: find /var/www -type f -name '*.php' -mtime -7 -size +20k -print. Перед запуском замените /var/www на реальный корень сайта. Здесь -mtime -7 отбирает файлы, изменённые менее семи суток назад, а порог размера — только удобный фильтр, не признак вредоносности.
В условном примере «Лагуна-М» сравнение файлов с чистой копией и журналом изменений даёт больше пользы, чем догадка по имени .cache. Папка /tmp не является автоматически частью сайта, а /var/www/html/.cache — лишь пример возможного пути, не универсальное место заражения. Я проверяю права записи веб-пользователя, каталог загрузок, задания cron и новые учётные записи CMS. Для восстановления полезно проверить целостность резервной копии: слепое возвращение заражённой копии возвращает и проблему.
Поиск строк eval, base64_decode или shell_exec можно использовать как грубый фильтр, но эти функции встречаются и в легитимном коде, а обфускация обходится без них. Для рекурсивного поиска в PHP-файлах команда выглядит так: find /var/www -type f -name '*.php' -exec grep -nHE 'eval|base64_decode|shell_exec' {} +. Команда читает содержимое файлов и печатает совпадения; она не устанавливает вредоносность и не заменяет сравнение с доверенной версией.
Любую находку проверяю по контексту, контрольной сумме или известной чистой копии. Если файл отличается, сохраняю копию для анализа и записываю путь, владельца и время. Не удаляю подозрительное до фиксации: вместе с ним можно уничтожить подсказку о способе проникновения. И наоборот, красивое имя плагина не даёт файлу индульгенцию. Проверять нужно код, источник изменения и то, может ли атакующий записать его снова.
- Сравнить файлы с чистой копией той же версии.
- Просмотреть новые PHP-файлы и задания cron.
- Использовать поиск строк только как фильтр.
- Сохранить подозрительные файлы и метаданные до очистки.
4. Что дают сканеры и чего от них не ждать
Внешний сканер видит то, что доступно снаружи: страницы, ответы сервера и известные сигнатуры. Сканер на самом сайте может видеть больше файлов, но зависит от прав, полноты правил и актуальности базы. Нулевой результат ни у одного способа не доказывает чистоту. Руководство WordPress рекомендует сочетать внешнюю и прикладную проверку; тот же практический принцип годится для других CMS, хотя конкретные плагины WordPress к ним не относятся.
Я не загружаю архив сайта в случайную форму проверки. В нём могут оказаться конфигурация, резервные копии, пользовательские данные и секреты. Сначала изучаю условия сервиса и его режим обработки данных; при сомнениях использую локальный инструмент или согласованную с владельцем сайта площадку. Сканирование публичной страницы и передача полного архива — разные действия с разным объёмом раскрываемой информации.
В этом разборе сканер — дополнительный источник гипотез. Его находку сопоставляю с файлом, процессом и временем изменения; ложное срабатывание проверяю по чистому дистрибутиву. Если сигнатура отсутствует, продолжаю проверять поведение и журналы. Утверждение «есть в базе угроз — значит точно майнер» слишком сильное: результат зависит от точности детектирования и контекста файла.
База знаний подтверждает полезный порядок: искать вредоносный код не одним способом, просматривать изменения файлов, проверять чистую копию и обновлять компоненты после устранения причины. Это конкретнее, чем запускать сканер по расписанию и считать задачу закрытой. У сканера нет доступа к вашему контексту — он не знает, зачем именно сайт запускает конкретный код.
- Внешний сканер проверяет доступную снаружи поверхность.
- Локальная проверка изучает файлы в пределах прав.
- Сопоставлять результат с кодом и журналами.
- Не передавать архив с секретами без оценки сервиса.
5. Очистка, восстановление и защита от повтора
Перед изменениями сохраняю резервную копию файлов и базы, журналы и список подозрительных объектов. Затем выясняю, как появился посторонний код: уязвимый компонент, утёкшая учётная запись, небезопасные права или другой путь. Удалить файл и оставить доступ атакующему — косметический ремонт. Рекомендации CISA по реагированию отдельно подчёркивают сохранение данных расследования и проверку резервных копий; при серьёзном инциденте не стоит начинать с перезапуска, который сотрёт часть летучих данных.
Если очистка требует замены файлов приложения или рестарта, планирую короткое окно и проверяю точные имена служб на конкретном сервере. systemctl restart nginx работает только если сервис действительно называется nginx; на другой установке может быть Apache, контейнер или иной способ запуска. Команда перезапуска не удаляет заражение и не закрывает уязвимость. Если сайт можно восстановить из проверенной чистой копии, сравниваю даты, содержимое и базу, затем обновляю компоненты до поддерживаемых версий.
После очистки обновляю CMS, темы, расширения и системные компоненты из доверенного источника, удаляю неиспользуемые модули, меняю скомпрометированные пароли и ключи, проверяю административные учётные записи. Отдельно ограничиваю запись в каталоги приложения и проверяю планировщик заданий. База знаний рекомендует обновление и повторную проверку после удаления кода; без устранения первопричины тот же сценарий может повториться. Бэкап рядом с заражённым сайтом — полезная копия до первого сбоя, но не проверенный план восстановления.
Иногда обсуждают PHP disable_functions. Это настройка php.ini, список имён разделяется запятыми, но документация PHP прямо предупреждает: директива не является достаточной мерой безопасности. Отключение exec, shell_exec, system или proc_open способно сломать приложение, а сама настройка не устраняет дыру в CMS. Я включаю её только после проверки совместимости и рассматриваю как дополнительное ограничение, а не как лечение заражения.
После восстановления проверяю страницы, формы, авторизацию и интеграции, затем наблюдаю за процессами, соединениями и изменениями файлов. Фиксирую время контрольной проверки и критерии, по которым можно снова поднять инцидент. В условной истории «Лагуна-М» финал — не строка «вирусов не найдено», а подтверждённая чистая копия, закрытый путь проникновения и повторный осмотр. Если данные показывают новую запись файлов, расследование продолжается.
- Сохранить журналы и проверенную копию до правок.
- Устранить путь проникновения, а не только файл.
- Обновить компоненты и сменить скомпрометированные секреты.
- Проверить сайт и продолжить наблюдение после очистки.
Частые вопросы
Высокий CPU означает, что на сайте майнер?
Нет. Это сигнал к проверке. Сопоставьте процесс с трафиком, журналами, задачами CMS и сетевыми соединениями.
Можно ли остановить подозрительный процесс командой kill -9?
Не до фиксации данных и оценки влияния. Сначала сохраните PID, время, пользователя, аргументы и доступные журналы.
Что показывает команда ss -tunap?
Сокеты и, при достаточных правах, связанные процессы. Вывод отражает состояние на момент проверки и сам по себе не доказывает заражение.
Помогает ли поиск eval и base64_decode?
Он находит часть подозрительных совпадений, но эти функции встречаются в обычном коде и могут отсутствовать в вредоносном. Сравните файл с чистой версией.
Нужно ли загружать сайт в онлайн-антивирус?
Сканер может дать дополнительную зацепку. Не передавайте архив с конфигурацией и секретами сервису, не проверив его условия обработки данных.
Источники
- PHP Manual: disable_functions — Проверены место настройки, формат списка и ограничение: директива не считается достаточной мерой безопасности. https://www.php.net/manual/en/ini.core.php#ini.disable-functions
- Linux man-pages: ss(8) — Проверено назначение ss и параметра -p для отображения процессов, использующих сокеты. https://man7.org/linux/man-pages/man8/ss.8.html
- WordPress.org: FAQ My site was hacked — Проверены рекомендации фиксировать признаки, сочетать внешние и прикладные сканеры, проверять копию и обновлять сайт после очистки. https://wordpress.org/documentation/article/faq-my-site-was-hacked/
- CISA: Cybersecurity Incident and Vulnerability Response Playbooks — Проверена последовательность сдерживания, сохранения свидетельств и перехода к устранению и восстановлению. https://www.cisa.gov/sites/default/files/publications/Cybersecurity_Incident_Vulnerability_Response_Playbooks_508C.pdf
- GNU Findutils Manual — Проверено назначение временного фильтра `-mtime` и диапазона `-7`. https://www.gnu.org/software/findutils/manual/html_node/find_html/Age-Ranges.html
- GNU Grep Manual — Проверено рекурсивное чтение каталогов с `-r` и вывод строк с `-n`. https://www.gnu.org/software/grep/manual/grep.html
