Короткий ответ
Если разбор показал, что вход был через плагин на /var/www/site.example: уберите плагин с диска (не только Deactivate), проверьте ядро и uploads, смените все секреты, которые мог прочитать PHP (БД, соли wp-config.php, SMTP, API, SSH деплоя). Хост site.example, 10.0.20.40. Полный malware-runbook: заражение. Здесь — фокус на плагине как векторе, без разбора эксплойта.
Не называйте CVE, если не подтвердили версию пакетом/readme. Не оставляйте plugin-name.disabled внутри plugins/ если PHP всё ещё может include через другой файл.
Deactivate в админке недостаточен: PHP плагина вызывают прямым URL. Каталог должен исчезнуть с диска после tar-копии. Секреты БД и соли wp-config.php после RCE считайте скомпрометированными — ротируйте до возврата сайта в интернет. Прямой вызов wp-content/plugins/.../file.php в access.log после удаления должен давать 404, не 200.
Симптомы и как отличить
- В логах POST на
wp-admin/admin-ajax.php?action=...или прямой PHP плагина до появления шелла. wp plugin list— плагин не из wordpress.org / nulled.- Тема цела,
mu-pluginsпуст, а в каталоге одного плагина лишние.php.
Отличие от brute login: в auth.log WP / audit мало failed login, зато странные hit в plugin URL. Brute: rate limit.
Возможные причины
- Плагин не обновлялся годы.
- Nulled/«взломанная» премиум-копия с бэкдором из коробки.
- Upload-форма плагина без авторизации.
- Демо-данные плагина с PHP в uploads.
- Цепочка: слабый admin + установка произвольного плагина.
Диагностика
Копия сначала — как в соседней статье, тот же tar/sql.
sudo -u www-data wp --path=/var/www/site.example plugin list --fields=name,status,version
ls -la /var/www/site.example/wp-content/plugins
sudo find /var/www/site.example/wp-content/plugins -name '*.php' -mtime -7Access log:
sudo awk '$7 ~ /wp-content\/plugins/ {print}' /var/log/nginx/access.log | awk '{print $1,$7}' | sort | uniq -c | sort -nr | headreadme.txt плагина vs заявленная версия. Сверяйте установленную версию, не анонс вендора.
Проверьте wp-config.php на auto_prepend и неизвестные константы. Не вставляйте содержимое ключей в чат/тикет — только факт «ротировано».
Решение
Сценарий A. Плагин не нужен бизнесу
sudo tar -C /var/www/site.example/wp-content -czf /root/plugins-before-rm-$(date +%F).tar.gz plugins
sudo -u www-data wp --path=/var/www/site.example plugin deactivate offending-plugin
sudo rm -rf /var/www/site.example/wp-content/plugins/offending-pluginПроверьте, не осталось ли offending-plugin.php в корне plugins/ (drop-in).
Сценарий B. Плагин нужен
Снимите с диска, поставьте чистый zip той же или новой версии с официального канала, не из «папки с бэкапа заражённого сервера». Прогоните checksum, если WP.org:
sudo -u www-data wp --path=/var/www/site.example plugin install offending-plugin --force
sudo -u www-data wp --path=/var/www/site.example plugin verify-checksums offending-pluginИмя подставьте реальное. Премиум без checksum — сверка с вендорским архивом по SHA256.
Сценарий C. Ротация секретов (обязательна после RCE)
- Пароль MySQL пользователя сайта, обновить
wp-config.php. - Новые
AUTH_KEY/SECURE_AUTH_KEY/ salts — генерация с wordpress.org salt API с машины админа, вставка в конфиг. - Пароли всех администраторов WP, сброс сессий (
wp user session destroy). - FTP/SFTP, ключи CI, токены WooCommerce/платежей, SMTP.
- Ключи в wp-options (
wp option listищите*_key,*secret*— осторожно с выводом).
После смены пароля БД — проверьте, что FPM читает новый конфиг (reload не обязателен для PHP, каждый worker читает файл запроса, но opcache мог закэшировать редко — reload FPM безвреден).
sudo systemctl reload php8.3-fpmСценарий D. Запрет исполнения в uploads
location ~* ^/wp-content/uploads/.*\.php$ {
deny all;
}nginx -t && reload. Apache: php_admin_flag engine off в Directory uploads или Require all denied для php.
Сценарий E. Nulled-плагин
Считайте бэкдор встроенным. Удалите, купите лицензию или замените функциональность. Ротация как после RCE.
Сценарий F. Следы в базе после удаления плагина
Плагин мог записать в wp_options объект с PHP-сериализацией или JS-редиректом. После снятия каталога:
sudo -u www-data wp --path=/var/www/site.example db query "SELECT option_name FROM wp_options WHERE option_value LIKE '%eval(%' OR option_value LIKE '%base64%' LIMIT 20;"Префикс таблиц подставьте свой. Ложные срабатывания на нормальный base64-контент возможны — смотрите глазами, не DELETE пачкой. Проверьте wp_posts на редирект-вставки в post_content (часто footer скрипт). wp post list не заменит grep по дампу.
Файл wp-content/object-cache.php и advanced-cache.php живут вне списка plugin list. Сверьте с дистрибутивом drop-in Redis/Memcached. Лишний include — удалить после tar.
Ротация: даже «только плагин» = RCE = чтение wp-config.php. Пароль БД, соли, токены Woo, SSH. Иначе реинфекция через день тем же входом в админку.
Как проверить, что проблема устранена
sudo -u www-data wp --path=/var/www/site.example plugin list
sudo -u www-data wp --path=/var/www/site.example core verify-checksums
sudo find /var/www/site.example/wp-content/uploads -name '*.php'
sudo -u www-data wp --path=/var/www/site.example user list --role=administratorНет уязвимого каталога, нет PHP в uploads, админы известны, логин с новым паролем работает, старый — нет. Мониторьте access.log сутки на старые URI плагина (должны быть 404).
Если не помогло
- Реинфекция: второй плагин или тема, или запись в БД (
evalв option). Вернитесь к полной чистке. - Drop-in
object-cache.php/advanced-cache.phpзаражён — это не «плагин в списке». - Компрометация хоста вне WP (другой vhost) — секреты всё равно ротировать.
Профилактика
- Минимум плагинов, автообновление minor, staging.
- Запрет установки плагинов из админки в проде (
DISALLOW_FILE_MODS) при деплое из git. - 2FA администраторов.
- Заголовки не заменят патч плагина.
- Инвентарь плагинов в git/wiki: имя, версия, зачем нужен. Nulled-копии запрещены политикой. После любого RCE считайте
wp-config.phpпрочитанным — ротация не опциональна. На прод не возвращайте, покаfind uploads -name '*.php'пуст.
FAQ
Deactivate достаточно?
Нет. Файл вызывают напрямую по URL.
Можно ли просто понизить версию?
Нет смысла, если дыра в этой версии. Нужен патч или удаление.
DISALLOW_FILE_MODS после инцидента
Да, чтобы не поставили второй шелл через админку, пока ключи живые.
Нужно ли менять префикс wp_?
Не вместо ротации. Можете заодно, это не серебро.
Плагин «security» не спас
Он сам может быть вектором, если старый. Смотрите его версию так же.