Перейти к содержимому

Восстановление wordpress после взлома: две ошибки и полный аудит сайта

Как я восстанавливал WordPress после взлома и дважды ошибся

После восстановления сайт клиента снова открывался: страницы появились, изображения начали загружаться, формы работали. Однако радоваться было рано. В футере вместо иконки адреса отображалась панель фильтрации товаров, а у 54 страниц в canonical оказался адрес вида `/?page_id=101`.

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

С чего началась атака

Я постоянно обслуживаю 13 сайтов небольших компаний. В начале сентября на одном из них исчезли фотографии товаров, страницы услуг начали возвращать ошибку 404, а вместо главной страницы стал открываться обычный пост из блога. Почти одновременно хостинг прислал уведомление: антивирус обнаружил и удалил вредоносные файлы.

Название домена я не раскрываю, поскольку речь идёт о клиентском проекте.

Этот сайт уже подвергался атаке 24 июля. Тогда удалили найденные шеллы и сменили пароль администратора. Но одна закладка осталась незамеченной. Она находилась не в каталоге `uploads`, а выглядела как обычный установленный плагин.

По серверным журналам и данным базы удалось восстановить последовательность событий.

Хронология

24 июля. В каталоге плагинов появился "инструмент доступности" с обработчиком, принимающим команды через параметр запроса. Во время первой очистки его не обнаружили: файл выглядел как легитимный плагин.

29 июля, 22:32. Через пять дней после смены пароля был создан администратор с именем наподобие `support_a73db02f3003`.

2-3 августа. На сервер загрузили 66 поддельных плагинов и 56 пустых тем с похожими названиями. Каждый такой компонент умел принять файл и показать, под какой учётной записью выполняется код.

10 августа. Я зашёл на сайт по другой задаче и установил защиту форм от ботов. Сам фильтр настроил, но списки пользователей и плагинов не проверил. Это была моя недоработка: о недавнем взломе я знал, поэтому аудит этих разделов следовало провести независимо от основной задачи.

17-27 августа. С разных IP-адресов устанавливались файловый менеджер, обфусцированный плагин удалённой публикации и расширение с подписанными REST-эндпоинтами для добавления блоков в футер.

29 августа, 17:54. Зафиксирована целая цепочка действий: вход в панель, редактирование профиля, установка ещё двух плагинов, создание двух администраторов, обращения к аналитике заказов WooCommerce и платёжным настройкам. После этого были удалены пользователь `admin` и ещё одна учётная запись.

Разные адреса и разные инструменты могут указывать на перепродажу доступа. В журналах также встречались неудачные попытки входа, где в поле пароля подставлялся адрес самого сайта. Вероятно, кто-то ошибся при обработке чужой базы учётных данных. Но это лишь версии: по имеющимся данным невозможно достоверно установить количество злоумышленников или источник компрометации пароля.

Почему удаление администратора повредило сайт

Сайт много лет наполнялся под учётной записью `admin`. Поэтому страницы, изображения и другие материалы принадлежали именно этому пользователю.

В WordPress удаление пользователя - не формальная операция. Если вызвать `wp_delete_user()` без переназначения автора, движок может удалить связанный контент. Конкретный результат зависит от зарегистрированных типов записей, их настроек и подключённых фильтров.

На этом проекте последствия оказались серьёзными:

- 59 страниц, включая главную, переместились в корзину;
- настройка главной страницы сбросилась на вывод последних записей;
- 192 объекта медиатеки были удалены вместе с файлами;
- у 56 записей исчезли связи с изображениями-превью.

Страницы при включённой корзине ещё можно восстановить. С вложениями ситуация сложнее: для медиаданных корзина по умолчанию не используется. При окончательном удалении `wp_delete_attachment()` удаляет не только запись в базе, но и связанные файлы, включая автоматически созданные размеры изображений.

Поэтому 192 объекта медиатеки означали гораздо больше 192 файлов на диске.

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

Важно понимать: для такого ущерба не требуется эксплойт или доступ к командной строке. Достаточно прав администратора. Антивирус хостинга удалит найденный вредоносный файл, но не станет проверять, какие пользователи были созданы в WordPress и кому принадлежит контент.

Почему полный откат из резервной копии не помог

Первой мыслью был полный откат сайта из бэкапа. Но подход оказался непригодным: резервная копия была сделана уже после предыдущего заражения и удаления пользователя. Она сохраняла повреждённое состояние базы.

Кроме того, полный откат возвращал старые заказы, заявки и изменения, которые появились после даты создания копии. Для коммерческого сайта это означало риск потерять реальные данные клиентов.

В итоге пришлось восстанавливать проект выборочно:

1. сохранить текущую базу и файлы в отдельный архив;
2. проверить резервные копии на наличие закладок;
3. вернуть страницы из корзины;
4. восстановить медиаданные из более ранней копии;
5. повторно связать изображения с записями;
6. проверить настройки чтения, постоянные ссылки и меню;
7. удалить подозрительных пользователей и плагины;
8. заменить пароли и ключи доступа.

Перед любыми исправлениями важно делать копию именно текущего состояния. Даже повреждённая база может содержать сведения, которые понадобятся для расследования: журналы действий, идентификаторы пользователей, даты публикаций и связи между объектами.

Первая ошибка: поиск файла по имени

После восстановления футера я нашёл в теме файл с тем же названием, которое использовалось для нужной иконки. Отредактировал его, очистил кеш и проверил страницу. На сервере всё выглядело правильно, но у клиента по-прежнему отображалась панель фильтров.

Причина оказалась в том, что файл с таким же именем не был источником проблемного блока. Вредоносный код добавлял разметку через REST-обработчик и сохранял её в настройках сайта. Изменение шаблона никак не влияло на уже записанное значение.

Моя первая ошибка заключалась в предположении: если найден файл с правильным названием, значит, найден источник вывода. В WordPress один и тот же визуальный элемент может формироваться темой, виджетом, конструктором, шорткодом, настройкой плагина или содержимым базы.

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

Вторая ошибка: страница открывается - значит, восстановлена

После возврата страниц из корзины большинство URL стало открываться. Я решил, что восстановление завершено. Позже выяснилось, что у 54 страниц сохранились неправильные canonical-адреса:

```text
/?page_id=101
```

Внешне страницы выглядели нормально, но поисковые системы получали неверный сигнал о предпочтительном URL. Причина находилась не в самой странице, а в метаданных SEO-плагина. Часть значений была записана в базе во время работы вредоносного расширения и пережила удаление файлов.

Это показало важную вещь: HTTP-код 200 и корректное отображение страницы ещё не означают, что сайт восстановлен. Необходимо проверять исходный HTML, заголовки, canonical, robots-мета, структурированные данные, редиректы и ссылки внутри контента.

Как проверять сайт после очистки

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

- список пользователей и их роли;
- плагины, темы и дату их установки;
- задания WP-Cron;
- настройки `siteurl` и `home`;
- постоянные ссылки;
- меню и виджеты;
- содержимое таблиц `options` и метаданных;
- неизвестные REST-эндпоинты;
- PHP-файлы в `uploads`;
- правила `.htaccess` и конфигурацию веб-сервера;
- новые административные сессии;
- изменения в SEO-полях;
- ссылки и скрипты в футере и хедере.

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

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

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

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

Также добавлена регулярная проверка:

- новых пользователей;
- изменений плагинов и тем;
- PHP-файлов в каталогах загрузок;
- подозрительных запросов к REST API;
- изменений в SEO-метаданных;
- неожиданных заданий планировщика.

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

Главный вывод

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

Мои две ошибки были типичными: я искал проблему по совпадению имени файла и счёл страницу восстановленной после того, как она начала открываться. В обоих случаях требовалось проверить не внешний симптом, а весь путь формирования результата - от пользователя и записи в базе до шаблона, кеша и финального HTML.

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

Прокрутить вверх