Безопасность сайта

Как очистить WordPress от вирусов вручную, если плагин не справляется

Ручная чистка WordPress после взлома: где смотреть, что искать, как сравнивать с эталоном и не убить рабочий контент.

обновлено 11 минут чтения 0 Дмитрий Акиндинов
Содержание
  1. Почему плагины не справляются
  2. Шаг 0: изолировать сайт и снять грязный бэкап
  3. Шаг 1: сравнить файлы с эталоном
  4. Шаг 2: обыскать типичные точки скрытия
  5. Шаг 3: какие сигнатуры искать
  6. Шаг 4: чистка базы данных
  7. Шаг 5: восстановление и проверка
  8. Шаг 6: закрыть точку входа
  9. Что я делаю на типовом разборе
  10. Когда вызвать подрядчика
  11. Частые вопросы

Ручная чистка WordPress занимает 4-5 часов на средний случай и идёт по шагам: изолировать сайт, снять грязную копию, сравнить файлы с эталоном ядра, обыскать типовые места, вычистить базу, восстановить и закрыть точку входа. Плагины-сканеры видят известные сигнатуры, а свежую закладку пропускают.

Wordfence показал десять заражённых файлов, а после автоматической чистки сайт сначала упал, а через два дня снова стал перенаправлять на казино. Если такая ситуация знакома, то добро пожаловать в реальность: автоматические сканеры WordPress справляются примерно в половине случаев. Дальше нужны руки, голова и SSH. В статье, как разобрать заражённый WordPress самому, что искать, чем сравнивать и как убедиться, что точка входа закрыта, а не временно прикрыта.

Логотип WordPress с пинцетом, вытаскивающим вредоносный код

Почему плагины не справляются

Плагины-сканеры (Wordfence, MalCare, Sucuri, iThemes Security) работают по сигнатурам. У каждого своя база, у некоторых ещё и эвристика. Они отлично ловят типовые шеллы и стандартные инжекты пятилетней давности.

Но реальный взлом 2026 года часто выглядит не как шелл, а как:

  • Корректный с точки зрения PHP код, который кладётся в существующий плагин и активируется только через cron при определённых условиях.
  • Запись в базе данных в формате сериализованного массива, которая разворачивается на лету и инжектится в footer темы.
  • Файл .ico или .jpg в uploads/, в EXIF которого зашит payload, а в рабочем плагине добавлена строчка include 'uploads/2024/01/icon.ico';.

Сигнатурный сканер такие штуки пропускает. Эвристика иногда ловит, но даёт массу ложных срабатываний, и пользователь устаёт от красных предупреждений на каждый легитимный плагин.

Поэтому стратегия «поставлю Wordfence, он сам всё почистит» работает на лёгких случаях. Если сайт уже неделю редиректит, а плагин ничего не находит — значит инфекция сложнее, чем умеет автомат.

Шаг 0: изолировать сайт и снять грязный бэкап

До того, как что-то трогать, делаем две вещи. Они нужны, чтобы потом не пожалеть.

Закрыть сайт от посетителей

Можно через .htaccess (блок по IP всех, кроме своего):

Order Deny,Allow
Deny from all
Allow from 1.2.3.4

Или через одну строку в wp-config.php, выводящую заглушку. Цель — остановить заражение посетителей и отключить инфектор от сети, чтобы он перестал общаться со своим C2.

Снять полный бэкап грязного состояния

Это контринтуитивно, но обязательно. Вы будете править файлы, удалять, восстанавливать. Что-то пойдёт не так. Без снимка «до» вы не сможете откатиться.

Команда на сервере:

cd /home/user/example.ru/
tar czf ~/dirty-backup-$(date +%F).tar.gz public_html/
mysqldump -u DBUSER -p DBNAME | gzip > ~/dirty-db-$(date +%F).sql.gz

Архив храните вне webroot, чтобы его нельзя было скачать через браузер. И не путайте его с чистым бэкапом для восстановления, подпишите имя dirty-.

Шаг 1: сравнить файлы с эталоном

WordPress прекрасен тем, что у каждого его файла есть официальная контрольная сумма. Это даёт быстрый ответ на вопрос «что в core поменяно».

Через WP-CLI

Если на сервере установлен WP-CLI (на нормальных хостингах есть):

wp core verify-checksums
wp plugin verify-checksums --all

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

Без WP-CLI

Если WP-CLI нет, скачиваете на свою машину чистый WordPress той же версии, что у вас на сайте, и плагины тех же версий. Затем diff -r:

diff -r wp-clean/ wp-site/wordpress/ > diff-core.txt
diff -r plugin-clean/contact-form-7/ wp-site/wp-content/plugins/contact-form-7/ > diff-cf7.txt

Версию ядра и плагинов смотрите в админке или в файле wp-includes/version.php и */readme.txt плагинов.

Что не сравнивается

Темы. У них нет официальной checksum, кроме официальных тем twenty*. Свою кастомную тему придётся проверять вручную или сравнивать с git-репозиторием, если он у вас есть. Если репы нет — это отдельный урок: после чистки заведите git хотя бы для wp-content/themes/.

Шаг 2: обыскать типичные точки скрытия

У вредоносного кода в WordPress есть несколько любимых мест. Прохожусь по ним по порядку.

wp-config.php

Обычно туда дописывают include грязного файла либо подменяют ключи аутентификации. Открываете и сверяете построчно с тем, что там должно быть. Никаких посторонних define, никаких if(isset($_REQUEST...)), никаких eval.

.htaccess в корне и во вложенных папках

Корневой .htaccess обычно содержит блок # BEGIN WordPress ... # END WordPress. Всё, что не в этих блоках, надо проверять. Особенно строки с RewriteCond %{HTTP_USER_AGENT} и RewriteRule на сторонний домен.

Не забывайте про .htaccess во вложенных каталогах. Команда:

find . -name ".htaccess" -ls

Иногда злоумышленник кладёт .htaccess в uploads/2024/, который разрешает выполнение PHP в этой папке.

wp-content/themes/<active>/

Самые частые жертвы: — functions.php — в неё дописывают функции с именами вроде wp_initialize_hash, _load_theme_settings, которые на самом деле инжектят скрипт. — header.php, footer.php — туда вставляют строку с <script> или echo подгружаемого payload. — любой PHP-файл с цифрой в имени или с подчёркиванием в конце — часто это файлы, добавленные взломщиком.

Открываете каждый файл темы и читаете глазами. Если файлов много и не понимаете, что в них — ищите подозрительные конструкции (см. ниже).

wp-content/plugins/

Каждый плагин сравниваете с чистой копией с wordpress.org. Любой «лишний» файл — под подозрение.

Особенно внимательно смотрите плагины, которые вы не помните, как ставили. Иногда злоумышленник создаёт свой «плагин» в wp-content/plugins/wp-cache-helper/ и активирует его, чтобы автозагружался. Содержимое — один файл с шеллом.

wp-content/uploads/

Здесь PHP-файлов быть не должно вообще. Никогда. Команда:

find wp-content/uploads -type f -name "*.php*" -ls
find wp-content/uploads -type f -name "*.phtml" -ls
find wp-content/uploads -type f -name "*.phar" -ls
find wp-content/uploads -type f -name "*.htaccess" -ls

Всё, что нашлось — на изучение и в большинстве случаев на удаление. Часто внутри будет короткий загрузчик второй стадии.

Корневые файлы с непонятными именами

Команда:

ls -la / | grep -v "^d"
find . -maxdepth 1 -type f -ls

Файлы вроде wp-class.php, wp-info.php, news.php, about_us.php в корне — это не WordPress. Это бэкдоры. Уносим в карантин для изучения, потом удаляем.

Шаг 3: какие сигнатуры искать

Здесь grep становится лучшим другом. Несколько паттернов, которые ловят 80% инжектов:

# eval с base64 - самая классика
grep -rEn "evals*(s*base64_decode" wp-content/

# eval с gzinflate
grep -rEn "evals*(s*gzinflate" wp-content/

# preg_replace с модификатором /e (запрещён в PHP 7+, но встречается)
grep -rEn 'preg_replace([^)]*"/e' wp-content/

# заглавные конструкции вызова через переменные функций
grep -rEn "\$_(POST|REQUEST|COOKIE)[[^\]]*]s*(" wp-content/

# create_function (тоже устарел, но в инжектах попадается)
grep -rEn "create_functions*(" wp-content/

# подключения внешних скриптов с подозрительных доменов
grep -rEn "https?://[^"']*.(top|xyz|ru.com|cf|gq|ml|tk)" wp-content/

Выводы сохраняйте в файлы и просматривайте глазами. Не каждое срабатывание — вирус. Например, base64_decode сам по себе встречается в легальных плагинах, важен контекст.

Менее очевидные сигнатуры

Современные инжекты прячутся хитрее. Признаки:

  • Длинная строка без пробелов и переводов строк — часто это закодированный payload.
  • Конструкция вида ${"GLOBALS"}["x"] — попытка обойти статический анализ.
  • Файл с одним коротким PHP-блоком, который читает другой файл и вызывает его.
  • Использование assert() с пользовательским вводом — это форма eval под другим именем.
  • Файл с произвольными байтами в начале и обычным кодом дальше — попытка перебить кодировку.

Я обычно прогоняю ещё две команды:

# Файлы, изменённые за последние 14 дней (если знаете, что не правили)
find wp-content -mtime -14 -type f -name "*.php" -ls

# Файлы с правами 777 (нормально таких быть не должно)
find . -type f -perm 0777 -ls
Папка сайта сканируется зелёной сеткой проверки

Шаг 4: чистка базы данных

Файлы — это половина истории. Вторая половина в БД. Куда смотреть:

Таблица wp_options

Там два опасных поля: siteurl и home. Если в них прописан не ваш домен, а что-то типа http://evil.example/redirect/, это и есть редирект.

Проверка:

SELECT option_id, option_name, option_value
FROM wp_options
WHERE option_name IN ('siteurl', 'home');

Также интересны записи с длинным сериализованным значением и неочевидным option_name. Особенно *_transient_* и *_cron. Иногда туда сохраняют payload, который активируется по cron.

Команда найти потенциально подозрительные autoload-записи большого размера:

SELECT option_id, option_name, LENGTH(option_value) AS size
FROM wp_options
WHERE autoload = 'yes'
ORDER BY size DESC
LIMIT 30;

Если в топе видите неизвестное имя с 100-500 КБ — открывайте и смотрите. Чаще всего там невинный кеш плагина, но иногда лежит и payload.

Таблица wp_users

Лишние пользователи с правами администратора — явный признак взлома. Команда:

SELECT u.ID, u.user_login, u.user_email, u.user_registered, m.meta_value
FROM wp_users u
JOIN wp_usermeta m ON u.ID = m.user_id
WHERE m.meta_key = 'wp_capabilities'
  AND m.meta_value LIKE '%administrator%';

Все администраторы, которых вы не знаете — удалить. Перед удалением сменить пароль вашему админу и проверить email-привязку, чтобы вас не угнали через «восстановить пароль».

Таблица wp_posts

Реже, но бывает: в post_content записи или страницы вшит <script>. Поиск:

SELECT ID, post_title, post_status
FROM wp_posts
WHERE post_content LIKE '%<script%'
   OR post_content LIKE '%<iframe%';

Не каждое срабатывание — вирус (бывают легитимные виджеты), но просмотреть стоит.

Таблицы с префиксом не вашего сайта

Если у вас префикс wp_, а вы видите таблицы с префиксом, например, wp1_ или secrt_ — ищите, кто их создал. Иногда атакующие создают теневые таблицы для своих нужд.

Шаг 5: восстановление и проверка

После чистки нужно убедиться, что сайт жив и работает.

Восстановить ядро

Самый надёжный способ — удалить все файлы ядра WordPress (всё, кроме wp-config.php, wp-content/, корневого .htaccess) и развернуть свежую копию того же релиза. Скачать с wordpress.org/download/releases/.

cd ~/example.ru/
rm -rf public_html/wp-includes
rm -rf public_html/wp-admin
rm -f public_html/wp-*.php
rm -f public_html/index.php
rm -f public_html/license.txt
rm -f public_html/readme.html
# заливаем свежий wordpress без wp-content и wp-config
unzip wordpress-X.X.X.zip -d /tmp/wp/
cp -R /tmp/wp/wordpress/* public_html/

Это убивает всё, что внедрено в core, и не трогает ваш контент.

Восстановить плагины

Те плагины, которые вы оставляете, удалите целиком и поставьте заново из админки или wp-cli:

wp plugin install contact-form-7 --version=5.9 --activate

Темы — аналогично, но если тема кастомная — заливаете из git или из чистого бэкапа двухмесячной давности.

Сменить все секреты

В wp-config.php есть восемь констант с солями. Сгенерируйте новые на https://api.wordpress.org/secret-key/1.1/salt/ и подставьте. Это аннулирует все активные сессии и заставит злоумышленника, если у него есть валидная кука, перелогиниться.

Параллельно сменить: — пароль админа WordPress (через профиль), — пароль базы данных (через панель хостинга и потом обновить в wp-config.php), — пароль FTP/SSH, — пароль панели хостинга, — API-ключи всех интеграций (Метрика, СМС, CRM).

Финальная проверка

# Запустить сайт и пройтись по основным страницам
curl -s https://example.ru/ | grep -iE "eval|atob|fromCharCode"
curl -s https://example.ru/wp-login.php | head -50

# Проверить логи доступа за последний час
tail -1000 /var/log/apache2/access.log | grep -E "POST|wp-admin"

# Прогнать сторонние сканеры
# sitecheck.sucuri.net, virustotal.com, transparencyreport.google.com

Если ничего подозрительного — можно открывать сайт. Перед этим включите Wordfence или аналог в режим максимальной защиты на пару недель, чтобы быстро ловить попытки повторного взлома.

Шаг 6: закрыть точку входа

Это самое важное и его чаще всего пропускают. Сайт почистили, открыли, через два дня снова заражение. Почему? Потому что не нашли, как именно они зашли в первый раз.

Возможные точки входа:

ИсточникЧто проверить
Уязвимый плагин/темаКакая версия стояла на момент взлома и какие CVE на неё известны
Слабый пароль админаЖурнал входов в Wordfence, логи nginx за месяц до взлома
XML-RPC брутфорсwp-cron.php и xmlrpc.php в логах с подозрительной активностью
Хостинг-уровеньЗапросить у хостера, не было ли инцидентов на shared, не утекли ли соседи
FTP-парольЕсли использовался FTP без TLS, пароль мог уйти в открытом канале
Локальная машинаСмотрели ли FTP/SSH-пароли антивирусом, нет ли стилеров

По каждому пункту делаете действие. По плагинам — удаляете старое и не используете уязвимые. По паролю — меняете на длинный сгенерированный, включаете 2FA. По XML-RPC — отключаете, если не нужен:

<Files xmlrpc.php>
    Require all denied
</Files>

По локальной машине — проверяете, чем пользуетесь для FTP, не лежит ли пароль в открытом виде в FileZilla, не было ли подозрительных программ.

Зелёный щит над сайтом, защита включена

Что я делаю на типовом разборе

Чтобы не быть голословным, типовой разбор у меня выглядит так:

  1. Полчаса — снимаю бэкапы, изолирую сайт, ставлю заглушку.
  2. Час — сравнение с эталоном, поиск инжектов в файлах.
  3. 30-40 минут — разбор базы данных, чистка wp_options, проверка пользователей.
  4. Час — перезаливка ядра, переустановка плагинов, смена секретов.
  5. Полчаса — финальная проверка, прогон сканеров, открытие сайта.
  6. 20-30 минут — отчёт клиенту: что было, что сделано, что закрыто на будущее.

Итого 4-5 часов на средний случай. Тяжёлые случаи (заражение нескольких сайтов на shared, давний инжект, который мутировал) могут растянуться на день-два.

Когда вызвать подрядчика

Делать самому имеет смысл, если у вас есть SSH, желание читать PHP и пара свободных вечеров. В остальных случаях это работа специалиста. По моему опыту, когда владелец пытается чистить сайт сам без бэкграунда — в половине случаев он сносит важные кастомные правки в теме, в четверти случаев пропускает скрытый инжект и ловит повторное заражение через неделю.

Сигналы, что пора звать на помощь: — В админку не пускает (заблокировали или сменили пароль). — Хостер прислал письмо «обнаружили вредоносное ПО, заблокируем через 24 часа». — Сайт — основной канал клиентов, и каждый день простоя стоит больше работы специалиста. — Уже почистили, а через два дня снова редирект.

В моих абонементах тариф «Авария» включает разбор такого инцидента полностью: от снятия грязного бэкапа до отчёта и инструкций по защите. Если сейчас горит и сайт лежит — пишите в контактах, смотрим сегодня. Другие материалы по безопасности и восстановлению есть в рубрике безопасность.

Частые вопросы

Как очистить WordPress от вирусов вручную

Изолируйте сайт, снимите копию в заражённом виде, скачайте с wordpress.org чистое ядро своей версии и сравните файлы. Дальше обыщите папку загрузок, темы и плагины, вычистите базу и закройте уязвимость.

Почему плагины-антивирусы не справляются

Они ищут известные сигнатуры. Свежая закладка, собранная под конкретный сайт, под сигнатуру не подходит, а вредонос в базе данных многие сканеры вообще не смотрят.

Сколько времени занимает ручная чистка

Около 4-5 часов на средний случай: примерно 30-40 минут уходит на разбор базы и 20-30 минут на отчёт, остальное на файлы и поиск точки входа.

Что делать после чистки

Сменить все пароли, обновить ядро, темы и плагины, пройти по чек-листу защиты WordPress и понаблюдать за файлами. Без закрытой точки входа сайт заражается снова за недели.

Передайте техническую часть в надёжные руки.

Слежу за хостингом, бэкапами и обновлениями. Когда что-то пойдёт не так – звоните мне, не своему программисту, не в спортзал и не маме.

Короткие вопросы по теме

Все вопросы по теме "Безопасность сайта"

Заявка

Обсудим задачу

Напишите имя и телефон, выберите удобный канал. Перезвоню или напишу в течение часа в рабочее время.

Куда удобнее ответить?