Короткий ответ
Считайте это инцидентом, не «почистить антивирусом на хостинге». Для site.example на 10.0.20.40 и /var/www/site.example: (1) изолируйте исходящий трафик и при необходимости входящий, (2) снимите копию файлов+БД, (3) ищите индикаторы (PHP в uploads, eval(base64, неизвестные mu-plugins, cron, пользователи), (4) чистите или поднимайте из чистого релиза + контент. Не rm -rf /var/www/site.example до копии и не включайте display_errors «посмотреть payload» в проде.
Смежный вход через плагин: взлом через плагин. Дальше — защита админки.
Не публикуйте payload шелла и не включайте display_errors «посмотреть вирус» — это утечка путей атакующему. Сначала tar webroot и дамп БД вне /var/www.
Симптомы и как отличить
- Поисковики: «сайт взломан», редирект только для User-Agent Google.
- Письма с
www-dataна 25/tcp. index.phpотличается от дистрибутива той же версии.- 500 из обфускации: 500.
Не путать с легитимным кэшем/CDN, который отдаёт старый HTML.
Возможные причины
- Уязвимый плагин/тема (отдельная статья).
- Слабый
wp-loginбез rate limit. www-dataпишет вwp-contentшире, чем нужно, плюс RCE.- Украденный FTP/SFTP/deploy ключ.
- Старый PHP-эксплойт в забытом
/backup.zipв webroot. - Соседний vhost на том же UID.
Мы не разбираем эксплуатацию. Фиксируем последствия.
Диагностика
1. Изоляция (минимально разрушительная)
Согласуйте с бизнесом. Пример nft: запрет исходящего с UID www-data на 25, 587, и подозрительные 80 к чужим IP — осторожно, сломаете легитимные API. Часто достаточно: снять сайт в return 503 на nginx кроме вашего IP 10.0.20.0/24, сохранить access.log.
Не ufw disable. Не открывайте всё «чтобы сканер зашёл».
2. Копия до любых удалений
sudo tar -C /var/www -czf /root/incident-site.example-$(date +%F).tar.gz site.example
sudo -u www-data wp --path=/var/www/site.example db export /root/incident-site.example-$(date +%F).sql
sudo cp -a /etc/nginx /root/incident-nginx-$(date +%F)Храните копию вне webroot и вне публичного S3 без ACL.
3. Индикаторы файлов
sudo find /var/www/site.example -name '*.php' -path '*/uploads/*'
sudo grep -R --include='*.php' -lE 'eval\s*\(|assert\s*\(|gzinflate\s*\(|base64_decode\s*\(' /var/www/site.example/wp-content | head
ls -la /var/www/site.example/wp-content/mu-plugins
crontab -u www-data -l 2>/dev/null
sudo crontab -l
ls /etc/cron.d/Смотрите wp-config.php на лишние include, изменённые ключи — не печатайте секреты в тикет.
4. Пользователи и опции
sudo -u www-data wp --path=/var/www/site.example user list --role=administrator
sudo -u www-data wp --path=/var/www/site.example option get siteurlНеизвестные admin — индикатор. Не удаляйте, пока не экспортировали список в заявку.
5. Сеть процесса
sudo ss -tp | grep php-fpmИсходящие к странным IP во время хита — полезно для IOC, не для «убить PID и забыть».
Решение
Сценарий A. Точечные IOC, ядро целое
Удалите конкретные вредоносные файлы после копии. Переустановите ядро:
sudo -u www-data wp --path=/var/www/site.example core download --force --skip-contentПлагины/темы — из чистых zip тех же версий, затем обновления. Ротация: пароли БД, FTP, AUTH_KEY в wp-config.php (новые соли), application passwords, nonce cookie — пользователи перелогинятся.
Сценарий B. Неясно, насколько глубоко
Поднимите новый каталог из чистого WP + дамп БД, пропущенный через поиск IOC (wp_options siteurl, неизвестные cron, widget с JS). Старый каталог оставьте read-only для экспертизы.
Не копируйте wp-content/uploads оптом, если там PHP: копируйте по расширениям изображений, PHP не переносите.
Сценарий C. Спам-бот в cron
Удалите crontab www-data, проверьте WP-Cron хуки (wp cron event list). Закройте xmlrpc.php если не нужен.
Сценарий D. Доступ через веб-шелл
Смените все секреты, SSH-ключи деплоя, пароль панели. Смотрите access.log на POST к странным URI до очистки — иначе потеряете вход.
Не публикуйте найденные шеллы как «PoC».
Сценарий E. PHP в uploads и nginx
Пока чистите, закройте исполнение:
location ~* ^/wp-content/uploads/.*\.(php|phtml|phar)$ {
deny all;
}Это не удаляет шелл, но режет вход. Затем удалите файлы по IOC из копии-исследования. Проверьте wp-content/upgrade, wp-content/mu-plugins, wp-content/languages на лишние php, .user.ini / auto_prepend_file в .htaccess.
Ротация секретов обязательна даже при «нашли один шелл»: он мог прочитать wp-config.php. Смените пароль БД, соли, админов, ключи деплоя, пароли SMTP. Не оставляйте WP_DEBUG_DISPLAY true.
Если сайт на общем хостинге с соседями под тем же UID — изоляции нет, рассматривайте переезд на отдельного пользователя/open_basedir/новый VPS после чистки.
Как проверить, что проблема устранена
- Поиск PHP в uploads: пусто.
- Админы только известные.
curl --resolveглавная без редиректа на чужой домен, с UA Googlebot отдельно проверьте.- Исходящий спам прекратился (
mailq, логи). - Файлы ядра совпадают с дистрибутивом (
wp core verify-checksums).
sudo -u www-data wp --path=/var/www/site.example core verify-checksums
sudo -u www-data wp --path=/var/www/site.example plugin verify-checksums --allПлагины без checksum в каталоге WP — ручная сверка.
Если не помогло
- Реинфекция через сутки: вход жив (плагин, ключ, соседний сайт).
- Редирект на CDN/WAF уровне — не в файлах.
- База с триггерами MySQL — редкость, но смотрите
SHOW TRIGGERS. - Нужен перенос на чистый сервер: миграция.
Профилактика
- Обновления, минимум плагинов, 2FA, brute force.
- Запрет исполнения PHP в uploads (nginx
location ^~ /wp-content/uploads/ { ... !php }). - Файлы не 777, деплой не от
www-dataс записью в ядро. - Бэкап вне сервера, immutable если есть.
- Запрет PHP в
uploadsдержите постоянно, не только в инцидент. После чистки проверьте, чтоauto_prepend_fileв.user.iniснова пуст.
FAQ
Достаточно ли плагина «сканер»?
Как индикатор — да. Как единственная чистка — нет. Сканер не видит все обфускации.
Можно ли chmod 000 на uploads?
Сломаете медиа. Запретите PHP в location, не всю запись.
Нужно ли менять префикс таблиц?
Слабая мера. Секреты и вход важнее.
Хостер предлагает «маливар ремов» без копии
Снимите свою копию сначала. Их чистка без артефактов бесполезна для разбора.
index.html в корне редиректит, index.php чистый
Смотрите index.html и autoindex/index директиву nginx (index index.html index.php — html победит).