Безопасность WordPress: защита от взлома без плагинов

WordPress из коробки достаточно безопасен, но стандартная конфигурация оставляет несколько уязвимых точек, которые атакуют боты и скрипт-кидди. Закрыть их можно без плагинов — правками в .htaccess, wp-config.php и functions.php. Рассмотрим основные меры защиты, которые блокируют 90% типовых атак.

Защита wp-login.php от брутфорса

Страница авторизации WordPress — главная цель ботов. Тысячи запросов в минуту к wp-login.php не только угрожают безопасности, но и нагружают сервер. Самый эффективный способ защиты — ограничение доступа по IP или добавление серверной HTTP-авторизации.

# .htaccess — ограничение доступа к wp-login.php по IP
<Files wp-login.php>
    Order Deny,Allow
    Deny from all
    Allow from 123.45.67.89
    Allow from 98.76.54.0/24
</Files>

# Альтернатива — HTTP Basic Auth поверх wp-login.php
<Files wp-login.php>
    AuthType Basic
    AuthName "Restricted Area"
    AuthUserFile /home/user/.htpasswd
    Require valid-user
</Files>

Если IP динамический, HTTP Basic Auth — лучший выбор. Файл .htpasswd создаётся командой htpasswd -c /home/user/.htpasswd admin. Располагайте его выше document root, чтобы он не был доступен через веб.

Дополнительно можно ограничить количество попыток входа через PHP, добавив в functions.php счётчик неудачных попыток:

<?php
// functions.php — блокировка после 5 неудачных попыток входа
add_filter( 'authenticate', function( $user, $username, $password ) {
    if ( empty( $username ) ) {
        return $user;
    }

    $ip = $_SERVER['REMOTE_ADDR'];
    $transient_key = 'login_attempts_' . md5( $ip );
    $attempts = get_transient( $transient_key );

    if ( $attempts && $attempts >= 5 ) {
        return new WP_Error(
            'too_many_attempts',
            'Слишком много попыток входа. Попробуйте через 15 минут.'
        );
    }

    return $user;
}, 30, 3 );

add_action( 'wp_login_failed', function( $username ) {
    $ip = $_SERVER['REMOTE_ADDR'];
    $transient_key = 'login_attempts_' . md5( $ip );
    $attempts = get_transient( $transient_key );

    set_transient( $transient_key, ( $attempts ? $attempts + 1 : 1 ), 15 * MINUTE_IN_SECONDS );
});

// Сброс счётчика при успешном входе
add_action( 'wp_login', function() {
    $ip = $_SERVER['REMOTE_ADDR'];
    delete_transient( 'login_attempts_' . md5( $ip ) );
});

Отключение XML-RPC

Протокол XML-RPC (xmlrpc.php) использовался для удалённой публикации через Blogger-клиенты и мобильное приложение WordPress. Сегодня его заменяет REST API, а XML-RPC остаётся вектором атак — через метод system.multicall боты могут перебирать пароли, отправляя сотни комбинаций в одном HTTP-запросе.

# .htaccess — полная блокировка xmlrpc.php
<Files xmlrpc.php>
    Order Deny,Allow
    Deny from all
</Files>

Если на сервере используется Nginx, добавьте в конфиг:

# nginx — блокировка xmlrpc.php
location = /xmlrpc.php {
    deny all;
    access_log off;
    log_not_found off;
    return 444;
}

Дополнительно отключите XML-RPC на уровне WordPress, чтобы он не обрабатывал запросы даже если файл доступен:

<?php
// functions.php — отключение XML-RPC
add_filter( 'xmlrpc_enabled', '__return_false' );

// Убираем заголовок X-Pingback из HTTP-ответов
add_filter( 'wp_headers', function( $headers ) {
    unset( $headers['X-Pingback'] );
    return $headers;
});

// Убираем ссылку на xmlrpc из <head>
remove_action( 'wp_head', 'rsd_link' );

Права на файлы и каталоги

Неправильные права доступа — частая причина взломов на shared-хостингах. WordPress рекомендует следующие права:

  • Каталоги: 755 (rwxr-xr-x) — владелец может всё, остальные только читать и переходить
  • Файлы: 644 (rw-r–r–) — владелец читает и пишет, остальные только читают
  • wp-config.php: 440 или 400 — только чтение для владельца и группы
  • .htaccess: 444 — только чтение (после настройки)

Установить права массово можно командами:

# Права на каталоги
find /path/to/wordpress -type d -exec chmod 755 {} \;

# Права на файлы
find /path/to/wordpress -type f -exec chmod 644 {} \;

# Особые права для wp-config.php
chmod 440 /path/to/wordpress/wp-config.php

Заголовки безопасности в .htaccess

HTTP-заголовки безопасности защищают от XSS-атак, clickjacking и MIME-sniffing. Добавьте их в .htaccess:

# .htaccess — заголовки безопасности
<IfModule mod_headers.c>
    # Защита от clickjacking
    Header always set X-Frame-Options "SAMEORIGIN"

    # Защита от MIME-sniffing
    Header always set X-Content-Type-Options "nosniff"

    # XSS-фильтр браузера
    Header always set X-XSS-Protection "1; mode=block"

    # Referrer Policy — не передавать URL при переходе на HTTP
    Header always set Referrer-Policy "strict-origin-when-cross-origin"

    # Запрет встраивания в iframe на чужих сайтах
    Header always set Content-Security-Policy "frame-ancestors 'self'"

    # Permissions Policy — отключаем камеру, микрофон, геолокацию
    Header always set Permissions-Policy "camera=(), microphone=(), geolocation=()"
</IfModule>

Проверить наличие заголовков можно через curl -I https://yourdomain.com или сервис securityheaders.com.

Отключение редактора файлов в админке

WordPress позволяет редактировать файлы тем и плагинов прямо из админки (Внешний вид → Редактор). Если злоумышленник получит доступ к административной учётной записи, он сможет внедрить бэкдор через встроенный редактор. Отключите его одной строкой в wp-config.php:

// wp-config.php — отключение редактора файлов
define( 'DISALLOW_FILE_EDIT', true );

// Ещё жёстче — запрет установки и обновления плагинов/тем через админку
define( 'DISALLOW_FILE_MODS', true );

Константа DISALLOW_FILE_MODS также отключает автоматические обновления. Используйте её только если обновляете WordPress через WP-CLI, Git или CI/CD.

Смена префикса таблиц БД

Стандартный префикс wp_ облегчает SQL-инъекции — атакующий заранее знает имена таблиц. Менять префикс лучше при установке WordPress, но можно и на работающем сайте. Сначала измените значение в wp-config.php:

// wp-config.php — кастомный префикс
$table_prefix = 'krc_';

Затем переименуйте все таблицы в базе данных через SQL:

-- Переименование основных таблиц (MySQL)
RENAME TABLE wp_posts TO krc_posts;
RENAME TABLE wp_postmeta TO krc_postmeta;
RENAME TABLE wp_options TO krc_options;
RENAME TABLE wp_users TO krc_users;
RENAME TABLE wp_usermeta TO krc_usermeta;
RENAME TABLE wp_terms TO krc_terms;
RENAME TABLE wp_termmeta TO krc_termmeta;
RENAME TABLE wp_term_taxonomy TO krc_term_taxonomy;
RENAME TABLE wp_term_relationships TO krc_term_relationships;
RENAME TABLE wp_comments TO krc_comments;
RENAME TABLE wp_commentmeta TO krc_commentmeta;
RENAME TABLE wp_links TO krc_links;

-- Обновление мета-ключей, которые содержат префикс
UPDATE krc_options SET option_name = REPLACE(option_name, 'wp_', 'krc_')
WHERE option_name LIKE 'wp_%';

UPDATE krc_usermeta SET meta_key = REPLACE(meta_key, 'wp_', 'krc_')
WHERE meta_key LIKE 'wp_%';

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

Дополнительные меры

Несколько быстрых правок, которые закрывают оставшиеся дыры:

  • Скройте версию WordPress — удалите мета-тег generator: remove_action('wp_head', 'wp_generator');
  • Запретите листинг каталогов: добавьте Options -Indexes в .htaccess
  • Отключите вывод ошибок PHP на продакшне: define('WP_DEBUG', false);
  • Используйте HTTPS — добавьте define('FORCE_SSL_ADMIN', true); в wp-config.php
  • Ограничьте REST API для неавторизованных пользователей, если он не используется на фронтенде
  • Удалите readme.html и license.txt из корня — они раскрывают версию WordPress

Все описанные меры работают на уровне WordPress и веб-сервера. Не забывайте про серверную безопасность: обновляйте PHP, настройте fail2ban для SSH и wp-login.php, используйте сложные пароли и двухфакторную аутентификацию. Безопасность — это не одноразовая настройка, а набор привычек.