Новости
Разбор бэкдора в WordPress: как реальный взлом нашли и вычистили
Вход через устаревший плагин, фальшивые плагины, скрытые папки и однострочный загрузчик в файлах ядра. Пошаговый разбор реального взлома WordPress.
· 12 мин. чтения

Недавно мы работали с WordPress-сайтом нашего клиента: сайт был взломан, и чужой код работал на нём несколько месяцев. В этой статье покажем, как атакующий попал внутрь, что он оставил, как мы это нашли и как вычистили. Цель — не напугать, а показать, как распознать похожую ситуацию на своём сайте и с чего начинать.
Данные клиента обезличены: домен, тип бизнеса и другие подробности не приводятся.
Точка входа: устаревший плагин
На сайте стоял плагин Backup Migration версии 1.4.0. В этой версии есть известная уязвимость — CVE-2023-6553: удалённое выполнение кода без аутентификации (unauthenticated RCE). То есть атакующему не нужны ни логин, ни пароль: отправив специальный запрос, он может выполнить свой PHP-код на сервере.
Уязвимость была опубликована и исправлена давно. Но плагин не обновляли, и в августе атакующий разместил веб-шеллы прямо в папке этого плагина. Через два месяца пришла вторая волна: новые файлы, новые способы маскировки.
Большинство взломов начинается не со сложной атаки, а с одного необновлённого плагина.
Что оставил атакующий
В ходе анализа нашлось следующее:
- около 20 фальшивых плагинов со случайными именами —
w2sи затем hex-символы (например,w2s3fa1c9). В админ-панели среди обычных плагинов они не бросаются в глаза. - Фальшивая тема рядом с настоящими.
- Дропперы
wp-validate.phpво множестве папок. Имя похоже на файл WordPress, но в ядре такого файла нет. - Скрытые папки — начинающиеся с точки, со случайным hex-суффиксом, внутри пара из маленьких
.htaccessиindex.php. Они размещены внутри настоящих плагинов — WooCommerce, Yoast, Elementor — и внутриwp-includes. Поэтому «удалить чужой плагин» их не уберёт. - PHP-файлы в
uploads— в этой папке PHP вообще не должно быть. - Загрузчики, скачивающие код с внешнего домена, обфусцированные через rot13.
- Подделанные временные метки — дата изменения файлов выставлена на 2020 год (timestomping), чтобы они выглядели как «старые нетронутые файлы».
- Удалённый
wp-login.php— стандартная страница входа уничтожена.
Каждый из этих пунктов — отдельный путь повторного входа. Оставите один — атакующий вернётся, что и показала вторая волна.
Самое скрытое: однострочный загрузчик в файлах ядра
Самой интересной находкой стал постоянный (persistent) загрузчик. В 9 файлов ядра WordPress и в wp-config.php была добавлена одна строка:
@include_once("php://filter/read=zlib.inflate|string.rot13|convert.base64-decode/resource=wp-includes/...");
Что она делает? С помощью обёртки потоков php://filter PHP читает файл с диска, последовательно декодирует его из base64, применяет rot13 и распаковывает zlib — а результат выполняет как PHP-код. То есть вредоносный код не лежит на диске в виде открытого PHP.
Кроме того:
- Строка записана в виде hex-escape (
"\x70\x68\x70...") и перемешана со случайными комментариями — простойgrep 'php://filter'её не находит. - Основная нагрузка (payload) спрятана в фальшивых файлах
.svg,.css,.js. Имя и расширение выглядят безобидно. - Нагрузка при каждом открытии страницы делала внешний запрос. Это проявлялось в PHP error log странными строками — именно это и стало первой зацепкой для анализа.
Как мы это нашли
1. Сравнение файлов ядра с официальными контрольными суммами
WordPress публикует MD5-суммы файлов ядра для каждой версии через api.wordpress.org/core/checksums. WP-CLI выполняет эту проверку одной командой:
wp core verify-checksums
wp plugin verify-checksums --all
Первая команда показывает изменённые файлы ядра и файлы, которых в ядре быть не должно. Вторая проверяет плагины из каталога wordpress.org (для платных плагинов контрольных сумм может не быть). Именно здесь всплыли 9 изменённых файлов ядра.
2. Поиск PHP в uploads
find wp-content/uploads -type f -iname '*.php*'
На нормальном сайте эта команда ничего не выводит.
3. Поиск по известным признакам
find . -name 'wp-validate.php'
find wp-content/plugins -maxdepth 1 -type d -name 'w2s*'
find . -type d -name '.*' ! -name '.' ! -name '.well-known'
grep -rlE '(\\x[0-9a-fA-F]{2}){8,}' --include='*.php' .
grep -rl 'php://filter' --include='*.php' .
Поиск последовательностей hex-escape помогает найти обфусцированные строки.
4. Группировка файлов по времени
Если сгруппировать файлы по времени изменения, видна «пачка», созданная в одну и ту же минуту. Но из-за timestomping mtime доверять нельзя. Поэтому проверяется и ctime (время изменения inode) — подделать его обычными средствами гораздо сложнее:
find . -type f -printf '%C+ %T+ %p\n' | sort | awk '{print substr($1,1,16)}' | uniq -c | sort -rn | head
Файлы, у которых mtime показывает 2020 год, а ctime — август, сразу подозрительны.
5. Анализ логов
В access-логе нашлись POST-запросы к файлам веб-шеллов и обращения к эндпоинту уязвимого плагина, а в PHP error log — строки, связанные с внешними запросами загрузчика. Это помогло определить время обеих волн.
Как мы чистили
Порядок важен. Мы работали в такой последовательности:
- Сначала сохранить доказательства. Полная копия файлов сайта и базы, хеши файлов, логи веб-сервера и PHP были перенесены в отдельное место. Ответить на вопрос «что же произошло» после очистки можно только по этим копиям.
- Заблокировать пути бэкдоров на веб-сервере. Чтобы атакующий не мог воспользоваться файлами, пока мы с ними работаем, известные пути были закрыты на уровне веб-сервера:
location ~* /wp-content/uploads/.*\.php$ { deny all; }
location ~* /wp-validate\.php$ { deny all; }
location ~* /\.(?!well-known) { deny all; }
- Карантин, а не удаление. Вредоносные файлы не удалялись, а переносились в папку карантина за пределами веб-корня. Это позволяет продолжать анализ.
- Заменить ядро чистыми файлами. Ядро WordPress было заново загружено из официального источника, контрольные суммы проверены повторно:
wp core download --version=$(wp core version) --force --skip-content
wp core verify-checksums
- Убрать загрузчики из остальных файлов. Добавленные строки в
wp-config.phpи других файлах были удалены вручную. - Удалить уязвимый плагин. Backup Migration 1.4.0 удалён полностью.
- Сменить соли WordPress. Ключи и соли в
wp-config.phpобновлены — это аннулирует все существующие сессии:
wp config shuffle-salts
Шаги, которые ещё нужно выполнить
Очистка файлов — только часть работы. После неё обязательно выполняется следующее:
- Аудит базы данных: посторонние пользователи-администраторы, внедрённые записи в таблице
wp_options, скрипты внутри постов, спам-страницы. - Переустановка плагинов из чистых источников — вместо попыток вычистить существующие папки.
- Смена всех паролей: пользователи WordPress, база данных, панель хостинга, FTP/SFTP, SSH.
- Обновление всего: ядро, плагины, темы, PHP.
Начальные команды для проверки базы:
wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
wp db query "SELECT option_name FROM wp_options WHERE option_value LIKE '%<script%' OR option_value LIKE '%eval(%';"
wp db query "SELECT ID, post_title FROM wp_posts WHERE post_content LIKE '%<script%';"
Почему это важно для экспертизы
Худший сценарий при официальной экспертизе взломанного сайта — когда проверяющий сам находит веб-шелл или посторонние файлы. Даже если сайт вроде бы работает нормально, уязвимая версия плагина, исполняемые PHP-файлы в uploads, открытые скрытые папки — каждое из этого отдельная находка. Поэтому перед подачей WordPress-сайта на экспертизу нужно убедиться, что он чист: одного «обновления» недостаточно. Профилактические шаги мы собрали в статье чек-лист безопасности WordPress.
Короткий чек-лист
wp core verify-checksumsиwp plugin verify-checksums --allдают чистый результат.- В
wp-content/uploadsнет PHP-файлов, и веб-сервер запрещает там выполнение PHP. - В списке плагинов нет незнакомых плагинов или тем со случайными именами.
- Нет скрытых папок, начинающихся с точки, и посторонних файлов вроде
wp-validate.php. - В файлах ядра и
wp-config.phpнетphp://filter, длинных hex-строк или постороннихinclude. - В PHP error log нет непонятных внешних запросов.
- Список администраторов проверен, пароли и соли сменены.
- Все плагины последней версии, неиспользуемые удалены.
Итог
В этом случае один необновлённый плагин дал атакующему полный контроль над сайтом, и он построил себе десятки путей повторного входа. Чтобы их найти, понадобились вместе контрольные суммы, анализ файловой системы и логи. А очистка должна начинаться с сохранения доказательств и заканчиваться проверкой паролей и базы.
Хотите убедиться, что на вашем WordPress-сайте нет похожих следов? Оставьте заявку на сканирование — агент Claude AI и наш специалист изучат файлы, плагины и настройки сервера, а мы поможем закрыть найденные дыры.


