Короткий ответ

WordPress на host.example рассылает спам: закройте исходящий SMTP с этого хоста (25/465/587), снимите копию wp-content и БД, найдите свежий PHP/плагин/мусор в uploads, не удаляйте дерево до tar. Не «оптимизируйте» сайт плагином очистки в проде. Не разбирайте webshell запуском в браузере. Очередь mailq сохраните (заголовки), не обязательно каждое тело письма в тикет.

Связанные: дефейс, процесс, секреты.

Симптомы и как отличить

  • IP сайта в блоклистах, PTR host.example.
  • mailq тысячи писем с From вашего домена или случайных.
  • CPU php-fpm.
  • Плагин «hello-dolly» внезапно плюс неизвестный wp-xxx.php в uploads.
КартинаНе взлом WPКуда
Свой SMTP-маркетинг с того же IPрепутациявынести рассылку
Форма Contact и редкий легитимный mailмалоrate, не IR
Спам с поддельным From без очереди на хостечужой серверSPF, не PHP
Пользовательский Outlookящикне webroot

Возможные причины

  1. Webshell в uploads/themes.
  2. Нулевой/нулевых плагин, nulled theme.
  3. Украденная учётка wp-admin ivan.petrov.
  4. Компрометация хоста целиком (cron PHP вне WP).
  5. Плагин SMTP с украденными creds шлёт через SendGrid — тоже инцидент, другой канал.
  6. Редко: соседний vhost на том же PHP-FPM.

Диагностика

1. Стоп исходящей почты с хоста

sudo nft add rule inet filter output tcp dport { 25, 465, 587 } ip daddr != 127.0.0.1 drop
# или UFW egress, если так принято; главное — не disable фильтра
sudo mailq | head
sudo postqueue -p | head

Сохраните postqueue -p в /var/ir/wp-spam/.

Windows IIS+WP реже, но PHP может слать через pickup:

Get-NetTCPConnection -RemotePort 25,587 | Format-Table

2. Копия

sudo mkdir -p /var/ir/wp-spam
sudo tar -czf /var/ir/wp-spam/wp-content.tgz -C /var/www/html wp-content
sudo cp /var/www/html/wp-config.php /var/ir/wp-spam/wp-config.php
sudo find /var/www/html/wp-content -name '*.php' -mtime -30 -ls

3. Откуда mail()

sudo grep -R --include='*.php' -n 'mail(' /var/www/html/wp-content 2>/dev/null | head
sudo tail -n 200 /var/log/php*-fpm.log 2>/dev/null
sudo grep POST /var/log/nginx/access.log | grep -E 'wp-content/uploads|wp-login' | tail

Плагины:

wp plugin list --path=/var/www/html 2>/dev/null || ls /var/www/html/wp-content/plugins
wp user list --path=/var/www/html 2>/dev/null || true

wp CLI может отсутствовать — тогда только ls и БД.

4. Пользователи WP и cron

Новые администраторы, неизвестные wp_users. wp-cron.php шторм. Системный cron www-data.

sudo crontab -u www-data -l
sudo ls -la /var/www/html/wp-content/mu-plugins

Не выкладывайте SQL-эксплойт. Читайте таблицу штатным wp user list / phpMyAdmin с jump.

Решение

Сдерживание

  1. Egress SMTP drop.
  2. Maintenance / wp_die для анонимов при необходимости.
  3. Смена паролей WP-админов, ключей wp-config (AUTH_KEY и соли — генерация штатная), пароль БД.
  4. Выключить неизвестные плагины после копии: переименование каталога плагина.
sudo mv /var/www/html/wp-content/plugins/suspicious /var/ir/wp-spam/suspicious-plugin

Чистка

Удаление webshell из uploads — после hash. Сверяйте PHP внутри uploads (их там быть не должно). Ядро WP лучше переустановить из официального пакета той же версии, не «скачать с форума». Темы/плагины — из репозитория, не nulled.

Очередь:

sudo postsuper -d ALL
# только когда копия очереди есть и бизнес согласен потерять и легитимные письма в очереди

IIS+WP

Тот же смысл: стоп исходящего, копия C:\inetpub\wwwroot, поиск .php в uploads, смена секретов пула.

PHP в uploads и соседние vhost

Исполнение PHP в wp-content/uploads отключите на nginx/IIS после копии: location ~* /uploads/.*\.php { deny all; } — штатный приём, не эксплойт. Проверьте все vhost на том же php-fpm: спам мог идти с соседнего сайта, а вы чистите «главный» WordPress. Пользователь ivan.petrov как WP-админ: сессии wp_usermeta, неизвестные admin — удалить после экспорта списка. Egress на 10.0.10.55:587 если это ваш smarthost — исключите его в nft, иначе форма и спам делят один путь; после чистки вернёте только smarthost, не мир на 25.

Не ставьте «security plugin» в пике: лишний PHP с write. Ядро — официальный пакет той же версии. Nulled theme считайте бэкдором, даже если «купили на маркетплейсе». Очередь postsuper -d ALL только когда на хосте не должно быть легитимной почты. WS-042 веб-мастера не используйте как FTP-клиент к прод, пока инцидент открыт.

Как проверить, что проблема устранена

  • mailq пуст, новые письма не копятся.
  • Egress 25/587 с хоста закрыт кроме согласованного релея, если форма нужна.
  • Нет PHP в uploads.
  • Плагины известны, админы WP известны.
  • Репутация IP: запрос на delist после чистки, не до.
  • Соли и пароли сменены.

Если не помогло

  • Очередь растёт при drop 25 — шлют через внешний HTTP API плагина; режьте процесс/плагин и исходящий 443 к тому API временно.
  • Спам с другого vhost — смотрите все корни PHP.
  • После чистки снова PHP в uploads — запись webroot жива (права 777), исправьте владельца, не только файл.
  • PTR/IP общий на shared hosting — чистите свой vhost, delist может быть не ваш; тогда эскалация хостеру.

Профилактика

  • Запрет исходящего 25 с веб-серверов, только один smarthost с auth.
  • Запрет исполнения PHP в uploads (правила nginx/IIS).
  • Обновления WP, MFA админов, лишние плагины долой.
  • Не 777 на wp-content.
  • Мониторинг mailq и блоклисты.

FAQ

Можно ли просто сменить пароль admin и не трогать файлы?

Нет. Webshell не знает ваш новый пароль и продолжит mail().

Удалить весь wp-content/uploads?

Сначала копия. Потом можно чистить PHP из uploads, не обязательно все JPEG.

Нужно ли менять префикс таблиц wp_?

Не лечит. Секреты и файлы важнее косметики.

WordPress на Windows Server

Да, тот же IR. SMTP может идти через локальный IIS pickup — смотрите очередь и процессы php-cgi.

Форма «свяжитесь с нами» умрёт без 587

Временно да. Вернёте через smarthost после чистки, не через mail() в интернет.