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

Чеклист безопасности WordPress: чините скучное, пока оно не стало дорогим

Практичный чеклист безопасности WordPress: пользователи, пароли, обновления, плагины, .htaccess, REST API, XML-RPC и WP-Cron.

Guide9мин чтения
Стена старых ржавых навесных замков, некоторые из них открыты.
Стена старых ржавых навесных замков, некоторые из них открыты.

Большинство взломов WordPress — это не крутые zero-day, и уж точно они не нацелены именно на ваш сайт. В основном это автоматические атаки, которые веером проходятся по тысячам сайтов. Просто у вашего оказалась открыта дверь.

Уязвимости и эксплойты в софте тщательно документируются, оцениваются и сортируются. Любой, у кого есть хоть одна извилина, может скачать список уязвимостей для версии WordPress, плагина, темы или скрипта с CDN, а потом натравить бота искать в интернете подходящие цели.

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

Этот чеклист не обещает неуязвимости. Он про то, как уменьшить поверхность атаки, раньше замечать проблемы и сделать восстановление куда менее болезненным.

Хотите сначала посмотреть, что видит бот? Проверка безопасности WordPress смотрит на ваш сайт снаружи и проверяет то из этого списка, что видно с улицы: публичные логины, XML-RPC, видимую версию, HTTPS и заголовки. Всё остальное — домашнее задание.

Короткая версия

Если у вас всего 30 минут, сделайте это по порядку:

бот › ищет открытую дверь0/12 закрыто
  • Сделайте бэкап всего и убедитесь, что знаете, как его восстановить.
  • Проверьте все аккаунты, удалите ненужные и особенно внимательно посмотрите на администраторов.
  • Дайте каждому пользователю уникальный пароль и включите 2FA для администраторов.
  • Обновите WordPress, PHP, активную тему и все плагины.
  • Удалите неиспользуемые плагины и темы. Деактивированный код — это всё ещё код на вашем сервере.
  • Установите один плагин безопасности и настройте его оповещения.
  • Добавьте логи активности и почты, чтобы будущие загадки оставляли улики.
  • Отключите листинг директорий и защитите чувствительные файлы.
  • Ограничьте ненужные вам маршруты REST API.
  • Отключите XML-RPC, если им ничего не пользуется.
  • Ограничьте или спрячьте вход в админку.
  • Замените WP-Cron, который запускают посетители, на настоящую задачу по расписанию.

А теперь сделаем каждый пункт как следует.

1. Начните с бэкапа

Прежде чем менять настройки безопасности, сделайте бэкап и базы данных, и файлов. Бэкап одного только wp-content — это не полный сайт, и дамп базы без uploads — тоже.

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

И самое главное — знайте, как его восстановить. Непроверенный бэкап — это файл, внушающий надежду, а не план восстановления.

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

2. Проверьте пользователей

Откройте Пользователи → Все пользователи и начните с администраторов.

  • Удалите старые аккаунты разработчиков, агентств, сотрудников и временные аккаунты.
  • Дайте каждому человеку свой аккаунт. Общие аккаунты делают лог активности бесполезным.
  • Используйте минимальную роль, которой хватает для работы. Большинству авторов нужен Редактор, а не Администратор.
  • Проверьте Пароли приложений в профилях пользователей и отзовите те, что больше не относятся к работающей интеграции.
  • По возможности смените типовые логины вроде admin. Это уменьшает шум, но не заменяет хороший пароль.

Для каждого оставшегося администратора:

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

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

Terminal window
# WP-CLI с выключенными плагинами и темами, чтобы никто не отфильтровал список
wp user list --skip-plugins --skip-themes
wp db query "SELECT ID,user_login FROM wp_users"
# Или обычный MySQL: доступы лежат в wp-config.php (или просто откройте phpMyAdmin)
mysql -h DB_HOST -u DB_USER -p DB_NAME -e "SELECT COUNT(*) FROM wp_users;"

wp_ — префикс таблиц по умолчанию; ваш указан в $table_prefix в wp-config.php. Если число не совпадает с Пользователи → Все пользователи, кто-то прячется.

3. Обновите всё, а потом уберите остатки

Обновляйте вместе:

  1. Ядро WordPress
  2. Плагины
  3. Активную тему
  4. PHP
  5. Серверные пакеты, если вы сами управляете сервером

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

Удалите плагины и темы, которыми не пользуетесь. Неактивный не значит недоступный. Их PHP-файлы всё ещё лежат в публичной корневой директории сайта, и к уязвимому файлу всё ещё можно обратиться напрямую.

Оставьте одну актуальную стандартную тему WordPress, только если хотите иметь её как запасной вариант на крайний случай. Удалите кучу старых тем, которые никто не помнит, как устанавливал.

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

4. Поставьте небольшой, но полезный набор плагинов

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

Один плагин безопасности

Wordfence — разумный универсальный вариант. В нём есть файрвол, сканирование на вредоносный код и целостность файлов, контроль входа, двухфакторная аутентификация и оповещения о безопасности.

После установки:

  • Запустите первое сканирование и разберитесь с результатами.
  • Включите двухфакторную аутентификацию для администраторов.
  • Настройте защиту от брутфорса.
  • Отправляйте оповещения на ящик, который кто-то действительно читает.
  • Не включайте все агрессивные опции разом. Поменяйте пару настроек, потом протестируйте сайт.

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

WP 2FA

WP 2FA добавляет 2FA через email на случай, если коды из приложения-аутентификатора (TOTP) в Wordfence слишком сложны для ваших пользователей. Честно говоря, такое можно написать самому за пару часов, но всегда удобнее, когда кто-то уже всё настроил и поддерживает за вас.

WP Activity Log

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

Лог отвечает на вопрос, который всегда возникает после инцидентов: «Кто это поменял и когда?» Настройте срок хранения, чтобы база не росла бесконечно, и ограничьте доступ к логу теми, кому он действительно нужен.

WP Mail Logging

WP Mail Logging записывает письма, которые WordPress пытался отправить. Полезно, когда сбросы пароля, формы обратной связи или письма о заказах растворяются в тумане.

Логи почты могут содержать имена, адреса, тексты писем и ссылки для сброса пароля. Ограничьте круг тех, кто может их читать, храните их только пока они полезны и не считайте логирование доказательством доставки. Оно доказывает лишь то, что WordPress попытался отправить письмо. Дойдёт ли оно, решают SPF, DKIM и DMARC.

Не про безопасность, но раз уж вы там

WP Rocket (кеширование) и WebP Express (изображения полегче) не остановят ни одного атакующего, но за WP Rocket вы и так тянетесь почти в каждой установке. Заодно возьмите отключение кеша для админов, если планируете тестировать на проде, а поскольку WebP Express добавляет правила rewrite, проверьте картинки через кеш/CDN в приватном окне, прежде чем праздновать победу.

5. Укрепляем Apache, не ломая WordPress

Этот раздел для серверов Apache или LiteSpeed, которые поддерживают .htaccess. Nginx этот файл не читает; вместо этого пропишите аналогичные правила в конфигурации сервера Nginx.

Сначала сделайте бэкап текущего .htaccess. Свои правила ставьте выше # BEGIN WordPress. WordPress может перезаписать всё, что находится между его начальным и конечным маркерами.

Добавьте это в .htaccess в корне WordPress:

# Не показывать список файлов, если в директории нет index-файла.
Options -Indexes
# Блокировать файлы, которые никогда не должны скачиваться через веб-сервер.
<FilesMatch "(?i)^(wp-config\.php|\.htaccess|\.htpasswd|debug\.log)$">
Require all denied
</FilesMatch>
# Блокировать типичные файлы бэкапов, конфигов, логов и дампов базы.
<FilesMatch "(?i)\.(bak|conf|dist|ini|log|sh|sql|swp)$">
Require all denied
</FilesMatch>

Затем создайте отдельный .htaccess внутри wp-content/uploads/:

# Загруженные файлы не должны выполняться как PHP.
<FilesMatch "(?i)\.(php[0-9]?|phtml|phar|phps)$">
Require all denied
</FilesMatch>
Бросьте запрос
.htaccess корень WordPress
Options -Indexes
<FilesMatch "(?i)^(wp-config\.php|\.htaccess|\.htpasswd|debug\.log)$"> Require all denied </FilesMatch>
<FilesMatch "(?i)\.(bak|conf|dist|ini|log|sh|sql|swp)$"> Require all denied </FilesMatch>
перезаписывает WordPress
# BEGIN WordPress
# Any changes to the directives between these markers will be overwritten.
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress
wp-content/uploads/.htaccess
<FilesMatch "(?i)\.(php[0-9]?|phtml|phar|phps)$"> Require all denied </FilesMatch>
ничего не поймало: Apache отдаёт как есть
Ваши правила

apache › 403. Бэкап остаётся бэкапом, а не загрузкой.

После добавления правил проверьте главную, админку, медиатеку, формы и обработку изображений. 500 Internal Server Error обычно означает, что хостинг не разрешает какую-то из этих директив. Восстановите бэкап и попросите хостинг сделать аналог на уровне сервера.

Ещё добавьте это в wp-config.php, чтобы убрать из админки встроенный редактор кода плагинов и тем:

define('DISALLOW_FILE_EDIT', true);

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

6. Аккуратно ограничьте REST API

Откройте эти URL, не залогинившись:

https://example.com/wp-json/
https://example.com/wp-json/wp/v2/users

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

Но не надо в ответ сносить весь /wp-json/. На нём работает блочный редактор, а также WooCommerce, Contact Form 7, Jetpack и большинство headless-сборок.

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

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

  • Страницы и записи
  • Формы обратной связи
  • Поиск
  • Оформление заказа и страницы аккаунта
  • Блочный редактор
  • Мобильные приложения, вебхуки и внешние интеграции

Цель не «заставить /wp-json/ возвращать ошибку». Цель — «открыть только те маршруты, которые нужны сайту». Сам WordPress не рекомендует полностью отключать REST API.

7. Отключите XML-RPC, если он никому не нужен

XML-RPC — это удалённый API, который был у WordPress до REST API. Сегодня им в основном пользуются Jetpack, некоторые сценарии мобильных приложений, pingback’и и боты для брутфорса, которые его обожают, потому что в один запрос можно засунуть сотни вариантов пароля. Если ничем легитимным из этого вы не пользуетесь, заблокируйте его на уровне веб-сервера, чтобы WordPress ради него даже не просыпался.

Apachexmlrpc.php: 403zZ⁨PHP + WordPress⁩CPU0%⁨бот⁩POST /xmlrpc.php403⁨500 паролей, один конверт⁩⁨1 запрос · проверено: 500⁩
Куда
WordPress

xmlrpc › 500 попыток, один запрос. Лимитер насчитал 1.

Добавьте это в корневой .htaccess, выше маркеров WordPress:

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

Затем откройте https://example.com/xmlrpc.php. Вы должны получить 403 Forbidden, а не привычное сообщение «XML-RPC server accepts POST requests only».

Если Jetpack или другая интеграция перестала работать, верните доступ и вместо этого отключите через плагин безопасности только аутентификацию XML-RPC или pingback’и.

8. Ограничьте или спрячьте вход в админку

Заблокировать XML-RPC, включить 2FA и сменить адрес входа — хорошая практика. Но лучшее решение — просто пускать к wp-login.php и /wp-admin/ только известные IP, через белый список IP или Tailscale. Тогда, даже если атакующий знает ваши логин и пароль или у него есть cookie вашей сессии, он не сможет ими воспользоваться, чтобы войти или что-то поменять на сайте.

Сделать это несложно. В Apache это несколько строк Require ip, в сети полно инструкций, и, наверное, когда-нибудь я добавлю свою. Оставьте открытым /wp-admin/admin-ajax.php: формы, корзины и поиск на публичной части сайта часто к нему обращаются.

9. Замените WP-Cron, а не просто выключите его

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

000306091215182124?!?!??????⁨? = проверка · ! = нашлась работа⁩⁨10 проверок⁩⁨бэкап⁩ 03:00+3:31⁨пост⁩ 09:00+2:42⁨Пропущено по расписанию⁩24:00⁨← тяни →⁩
Кто запускает задачи
Посетители
тихолюдно10/день

wp-cron › Пост на 09:00 вышел в 11:42. Спасибо первому посетителю.

Сначала добавьте это в wp-config.php:

define('DISABLE_WP_CRON', true);

Затем запускайте наступившие события каждые пять минут настоящим cron-заданием. Если установлен WP-CLI, вот самый чистый вариант:

*/5 * * * * cd /path/to/wordpress && /usr/local/bin/wp cron event run --due-now --quiet

Подставьте правильные пути к WordPress и WP-CLI для вашего сервера и запускайте задание от имени владельца сайта, а не от root. В большинстве хостинг-панелей есть экран Cron Jobs или Scheduled Tasks, где можно добавить ту же команду. Чтобы проверить, когда расписание на самом деле срабатывает, вставьте его в расшифровку cron.

Только после того, как задание заработает, закрывайте публичный доступ к wp-cron.php:

<Files "wp-cron.php">
Require all denied
</Files>

Проверьте так:

Terminal window
wp cron event list

Если запланированные посты, бэкапы, задачи очистки или действия WooCommerce потихоньку перестали выполняться, значит, ваша замена cron не работает. Уберите DISABLE_WP_CRON, пока не почините. Сломанный cron не кидает ошибок, он просто перестаёт что-либо делать, а вы замечаете это через несколько недель. В справочнике по плагинам WordPress описан тот же процесс замены.

Поддерживайте это

Безопасность — это в основном обслуживание, только с более приятными последствиями.

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

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

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

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

Полезные ссылки


Обложка: старинные замки, музей «Сокровища в стене», Акко, фото Adam Jones, CC BY-SA 2.0.

Комментарии

Комментарии живут в GitHub Discussions (через giscus), так что для участия нужен аккаунт GitHub.

Чирикну, когда выйдет новый пост

Одно письмо на каждый новый пост, обычно по пятницам. Больше ничего, а отписаться можно в любой момент.

Удобнее читать в RSS-ридере? Есть RSS-лента.

© Alex Kovalev. Все права защищены. · Политика конфиденциальности