Сайт взломали: в выдаче появились чужие ссылки, посетителей перекидывает на сторонние ресурсы, хостер прислал письмо о рассылке спама. Нужно быстро найти, где лежит вредоносный код, не дожидаясь антивируса панели.
Коротко: по известному фрагменту ищем командой grep -rl 'фрагмент' /var/www/. Если фрагмента нет, ищем по типичным сигнатурам (eval(base64_decode, gzinflate и т. п.), по недавно изменённым файлам через find, по PHP-файлам в uploads. Для WordPress сверяем ядро и плагины с эталоном: wp core verify-checksums и wp plugin verify-checksums --all.
Перед поиском
- Сделайте копию сайта и базы в отдельный каталог: она пригодится для разбора и на случай, если при чистке удалите лишнее.
- Не запускайте найденные файлы и не открывайте их в браузере. Смотрите только в консоли:
less,head. - Если на сервере несколько сайтов, ищите по всем: заражение часто расходится по соседним каталогам одного пользователя.
Поиск по известному фрагменту
Если код уже известен (например, видели его в исходнике страницы или в письме хостера), достаточно grep:
grep -rl 'что ищем' /путь/где/ищем
grep -rl 'evil-domain.example' /var/www/
Ключи: -r — рекурсивно по каталогам, -l — выводить только имена файлов. Полезные дополнения:
-F— искать строку как есть, без регулярных выражений (удобно, если во фрагменте скобки и точки);-i— без учёта регистра;--include='*.php'— только PHP-файлы, так быстрее;-nвместо-l— показать номер строки и саму строку.
grep -rlF --include='*.php' '$_POST[' /var/www/example.com
grep -rn --include='*.js' 'String.fromCharCode' /var/www/example.com/wp-content
Поиск по типичным сигнатурам
Чаще код неизвестен. Тогда ищем конструкции, которые в нормальном коде встречаются редко, а во вредоносном — постоянно: выполнение строки, декодирование base64, распаковка сжатых данных.
grep -rlE --include='*.php' \
'eval\s*\(\s*(base64_decode|gzinflate|gzuncompress|str_rot13)' /var/www/
grep -rlE --include='*.php' \
'(assert|eval|system|shell_exec|passthru)\s*\(\s*\$_(GET|POST|REQUEST|COOKIE)' /var/www/
Будут и ложные срабатывания: base64_decode законно используют многие плагины. Поэтому каждый найденный файл открываю и смотрю глазами. Признаки вредоноса — одна бесконечная строка, бессмысленные имена переменных, код до <?php в начале файла или после закрывающего тега в конце.
Если установлен ClamAV, его стоит прогнать дополнительно: clamscan -ri /var/www/ (-r — рекурсивно, -i — показать только заражённые). Сигнатурный антивирус ловит не всё, но известные веб-шеллы находит.
Недавно изменённые файлы
Если примерно известна дата взлома, find покажет, что менялось после неё:
# PHP-файлы, изменённые за последние 7 дней
find /var/www/example.com -type f -name '*.php' -mtime -7 -ls
# изменённые после указанной даты
find /var/www/example.com -type f -newermt '2026-10-01' -ls
# по ctime: время изменения inode нельзя подделать командой touch
find /var/www/example.com -type f -name '*.php' -ctime -7 -ls
Злоумышленники часто выставляют вредоносным файлам старую дату модификации, чтобы спрятать их среди остальных. ctime так не поменять, поэтому я смотрю оба варианта. Учтите, что обновление плагинов и ядра тоже меняет даты, — сверяйтесь с журналом обновлений.
PHP-файлы там, где их быть не должно
В WordPress каталог wp-content/uploads предназначен для картинок и документов. PHP-файлы в нём почти всегда означают веб-шелл:
find /var/www/example.com/wp-content/uploads -type f \
\( -name '*.php' -o -name '*.phtml' -o -name '*.php[0-9]' \) -ls
find /var/www/example.com -name '.htaccess' -newermt '2026-10-01' -ls
Второй командой проверяем подменённые .htaccess: через них часто настраивают редиректы на сторонние сайты или разрешают выполнение PHP в каталогах с загрузками.
Проверка ядра и плагинов WordPress
WP-CLI умеет сравнивать файлы с контрольными суммами с wordpress.org — это самый надёжный способ найти подменённые файлы ядра:
cd /var/www/example.com
wp core verify-checksums
wp core verify-checksums --include-root
wp plugin verify-checksums --all
Для ядра команда выводит предупреждения File doesn't verify against checksum (файл изменён), File doesn't exist (файла нет) и File should not exist (лишний файл в каталогах ядра). Ключ --include-root добавляет проверку корня сайта: там часто лежат посторонние PHP-файлы. Каталог wp-content этой командой не проверяется.
wp plugin verify-checksums --all работает только для плагинов из каталога wordpress.org. Платные и самописные плагины она пропустит с предупреждением Could not retrieve the checksums …, skipping. Их и тему придётся проверять grep и сравнением с дистрибутивом от разработчика.
Не забудьте про базу: вредоносный JavaScript нередко внедряют в записи и опции. Поиск по всем таблицам: wp db search '<script' --all-tables. Список администраторов (нет ли лишних): wp user list --role=administrator.
Что делать после того, как код найден
- Найдите точку входа. Посмотрите в логах веб-сервера POST-запросы к найденным файлам:
grep 'POST /wp-content/uploads' /var/log/nginx/access.log. Так видно IP и первый запрос, а часто и уязвимый плагин. - Замените ядро на чистое:
wp core download --force --skip-content --version=<текущая версия>. Плагины из каталога переустановите:wp plugin install <slug> --force. - Удалите веб-шеллы и лишние файлы, обновите всё до актуальных версий, удалите неиспользуемые плагины и темы.
- Смените все пароли: администраторов WordPress, пользователя базы данных, SSH/SFTP, панели хостинга. Обновите ключи и соли:
wp config shuffle-salts— это завершит все активные сессии. - Проверьте cron пользователя сайта (
crontab -l -u www-dataили от владельца сайта): вредонос часто восстанавливает себя по расписанию. - Через несколько дней повторите поиск по тем же сигнатурам и по
-ctime.
Если сайт проще перенести на чистый сервер, чем лечить на месте, пригодится инструкция Перенос сайта WordPress через командную строку. Переносите только проверенные файлы и базу. Базовая настройка сервера под WordPress описана в статье Настройка VPS для WordPress.
Частые вопросы
Почему grep ничего не нашёл, а сайт всё равно заражён?
Код может лежать в базе данных, а не в файлах, быть закодирован так, что сигнатуры не совпадают, или находиться за пределами каталога сайта (cron, файлы в /tmp). Проверьте базу через wp db search, задания cron и файлы, изменённые за период взлома.
Можно ли удалить все найденные файлы с base64_decode?
Нет. Эта функция есть во многих легитимных плагинах и в самом ядре. Удаляйте только то, что после просмотра однозначно вредоносно, а файлы ядра и плагинов лучше перезаписать чистыми копиями.
Что делать с файлами, которые verify-checksums пометил как изменённые?
Файлы ядра не должны меняться вообще, поэтому их не правят вручную, а перезаписывают командой wp core download --force той же версии и локали. После этого запустите проверку ещё раз: предупреждений о несовпадении быть не должно. Файлы с пометкой File should not exist просмотрите и удалите.