Ваш сайт на WordPress здається «нормальним», поки не настала 3:07 ранку і телефон на чергуванні вібрує так, ніби намагається пробити тумбочку. Симптом завжди однаковий: помилки входу, процесор на максимумі, підключення до бази накопичуються, а в хроніці інциденту з’являється запис «хтось змінив щось, пов’язане з безпекою».
Жорстка політика безпеки — не ворог. Погана жорсткість — ворог. Це чеклист для продакшену, який допоможе посилити безпеку WordPress без того, щоб перетворити легітимних редакторів, клієнтів і користувачів SSO на випадкові жертви.
Модель загроз на одній сторінці: що ви насправді захищаєте
Жорстка безпека WordPress стає розумною, коли ви перестаєте ставитися до неї як до повітряного відчуття і починаєте будувати модель загроз. «Атакувачі», яких ви побачите найчастіше, — не кібергерої з фільмів. Це боти, масове шкідливе ПЗ і опортуністи, які сканують інтернет у пошуках відомих вразливостей. Ваше завдання — зробити сайт нецікавим для нападів і стійким під час атаки.
Що найчастіше атакують
- /wp-login.php brute force (credential stuffing, password spraying, зловживання API-токенами).
- /xmlrpc.php (історично використовувався для pingback та віддаленої публікації; часто зловживається для ампліфікації та брутфорсу).
- Вразливості плагінів/тем (RCE, довільне завантаження файлів, обхід аутентифікації, CSRF).
- Записуваний webroot (одна помилка завантаження файлу стає персистенцією й порчою сайту).
- Дрейф ланцюжка постачання (плагіни оновлюються «коли завгодно», без ролбеків, без канарки і без етапного розгортання).
Що означає «не блокує входи»
Безпека входу — це не просто «я можу увійти зараз». Вона включає:
- Людей на динамічних IP (домашній інтернет, подорожі, мобільні хот-споти).
- Легітимну автоматизацію: моніторинг аптайму, безголова публікація, клієнти API WooCommerce, callback-и SSO, health check-и реверс-проксі.
- Робочі процеси редакторів з різними ролями: адміністратори, автори, контрибутори.
- Надзвичайний доступ коли ваш WAF або сервіс аутентифікації деградують.
Жорсткість має бути відкатною, спостережуваною й багаторівневою. Якщо зміна може заблокувати власний персонал, вона має мати план аварійного доступу (краще такий, який ви зможете виконати о 3 ранку з одним відкритим оком і без геройств).
Цитата, яку варто приклеїти до монітора: «Сподівання — не стратегія.»
— Gordon R. Sullivan.
Цікаві факти та коротка історія (що пояснює сучасну плутанину)
- WordPress почався у 2003 році як форк b2/cafelog; його архітектура плагінів допомогла йому перемогти, але також дала зловмисникам величезну площу атаки.
- xmlrpc.php існував до REST API; він дозволяв віддалену публікацію задовго до появи сучасних патернів аутентифікації в CMS.
- Брутфорс перейшов від «цільового» до «фонового» коли ботнети і витоки облікових записів стали дешевими; вас атакують не тому, що ви особливі, а тому, що ви існуєте.
- Автооновлення ядра WordPress (для мінорних релізів) стало стандартною страховкою з плином часу, але головний ризик досі — плагіни.
- Плагіни безпеки з’явилися як категорія здебільшого тому, що шарінг-хостинг ускладнював доступ до рівня сервера для звичайних власників сайтів.
- Солі в wp-config.php існують тому, що куки та сесії потребують криптографічної унікальності для кожного сайту; старі сайти іноді тягнуть давні солі через міграції.
- Рекомендації щодо прав файлів еволюціонували тому, що ранні інсталяції часто запускали PHP від імені користувача, який володів файлами, що заохочувало всесвітньо записувані директорії.
- CDN змінили історію входу: лімітування і пом’якшення ботів перейшли на край, але неправильна конфігурація origin все ще дає прямий доступ до wp-login.
Принципи жорсткості, що запобігають блокуванням
1) Зменште площу атаки перед тим, як посилювати примус
Блокувати весь світ від /wp-admin приємно. Це також ламає мобільних редакторів, REST-клієнтів і будь-кого, у кого змінюється IP. Натомість почніть із видалення того, чим ви не користуєтесь (xmlrpc, невикористані плагіни, старі теми), потім додайте аутентифікацію й обмеження частоти, і лише потім додавайте правила «deny», де це безпечно.
2) Кожен механізм безпеки повинен бути спостережуваним
Якщо ви не можете відповісти на «хто був заблокований?» і «чому?» з логів, ви не займаєтесь безпекою. Ви займаєтесь ритуалом. Записуйте рішення про відмову в логах з достатньою кількістю полів для дій: IP, шлях, статус, user agent, ідентифікатор запиту та час відповіді upstream.
3) Віддавайте перевагу обмеженню частоти та викликам замість allowlist-ів по IP
Allowlists працюють для офісних мереж і VPN. Вони зазнають невдачі в умовах віддаленої/гібридної роботи. Обмеження частоти + сильна аутентифікація дають захист без крихких припущень про стабільність IP.
4) Розділяйте ідентичність і авторизацію
Двофакторна аутентифікація (або SSO) укріплює ідентичність. Вона не замінює авторизацію. Тримайте ролі мінімальними. Не давайте «Administrator» щоб вирішити проблеми робочого процесу. Це як додавати бензин до двигуна через незвичний шум.
5) Зменшуйте радіус ураження
Запускайте PHP-FPM під виділеним користувачем. Заблокуйте власність файлів. Якщо вразливість плагіна потрапить, ваша мета — «обмежена шкода», а не «весь файловий простір — полотно».
Жарт №1: Єдина річ більш наполеглива, ніж шкідливе ПЗ WordPress — це людина, яка наполягає, що пароль адміністратора має бути «CompanyName2024!».
Швидкий плейбук діагностики: знайдіть вузьке місце перед «виправленням»
Коли після «посилення» виникають проблеми з входом, поводьтеся з цим як із інцидентом. Не здогадуйтеся. Не відкочуйтеся необережно. Тріажуйте в цьому порядку, бо він швидко звужує поле пошуку і не дає звинуватити неправильний шар.
Перш за все: підтвердіть, що саме ламається (браузер vs сервер vs upstream)
- Повертає
/wp-login.php200 з формою, чи 403/429/503? - Помилка лише для деяких користувачів (за роллю, IP, гео)?
- Чи origin доступний напряму, обходячи CDN/WAF?
По-друге: перевірте рішення rate limiting / WAF / Fail2ban
- Шукайте сплески 403/429 на ендпойнтах входу.
- Підтвердіть, чи ваші блок-листи не включають IP офісу/VPN або IP моніторингу.
- Перевірте, що «виклик» не ламає клієнтів без браузера (REST-виклики, мобільні додатки).
По-третє: перевірте продуктивність аплікації та бази даних
- Вхід може бути повільним через повільну базу даних (роздутий options, автозавантажувані опції, відсутні індекси).
- Або тому, що PHP-процеси насичені (забагато або занадто мало php-fpm child’ів, багато повільних плагінів).
- Або через проблеми диска (сплески IO wait; логи та uploads на одному томі).
По-четверте: перевірте коректні контролі
- Права/власність блокують запис сесій, завантаження, оновлення плагінів.
- Неправильна конфігурація object cache викликає петлі з auth cookie.
- Заголовки безпеки або прапорці cookie невірно встановлені між HTTP/HTTPS.
Якщо ви не можете визначити вузьке місце за 10–15 хвилин, припиніть зміни, зробіть сніпшот стану (конфіги + логи), а потім робіть контрольований ролбек. Адреналін — поганий менеджер змін.
Практичні завдання з жорсткості (команди, результати, рішення)
Це реальні операційні завдання, які ви можете виконати на типовому Linux-хості (Debian/Ubuntu). Кожне завдання включає: команду, як виглядає «добрий» вихід і яке рішення потрібно прийняти.
Завдання 1: Визначте, хто реально обслуговує WordPress (Nginx/Apache, користувач PHP-FPM)
cr0x@server:~$ ps -eo user,comm,args | egrep 'nginx:|apache2|php-fpm' | head
root nginx: nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
www-data nginx: nginx: worker process
www-data php-fpm8.2 php-fpm: pool www
www-data php-fpm8.2 php-fpm: pool www
Що це означає: worker-и Nginx і PHP-FPM працюють як www-data. Це визначає вибір власності файлів і прав доступу.
Рішення: Стандартизувати на виділеному сервісному користувачі (www-data підходить) і переконатися, що файли WordPress належать адміністраторові/групі під контролем, а не є записуваними веб-користувачем, якщо ви явно не хочете оновлень на місці.
Завдання 2: Підтвердьте шлях WordPress і знайдіть випадкові множинні інсталяції
cr0x@server:~$ sudo find /var/www -maxdepth 3 -name wp-config.php -print
/var/www/site1/wp-config.php
Що це означає: Знайдено лише один wp-config.php під очікуваними веб-рутами. Багато конфігів часто означає забуті staging-сайти, відкриті в інтернеті.
Рішення: Якщо знайдете зайві інсталяції — видаліть їх, закрийте фаєрволом або захистіть паролем. Приховані staging-сайти — місце для шопінгу атакувальників.
Завдання 3: Перевірте власність файлів і поверхні для запису
cr0x@server:~$ cd /var/www/site1
cr0x@server:~$ sudo find . -maxdepth 2 -type d -perm -0002 -print | head
./wp-content/uploads
Що це означає: Директорії, доступні для запису «для інших», — червоні прапорці. WordPress потребує записуваності uploads, але не обов’язково світової записуваності.
Рішення: Приберіть біт world-writable. Залиште записуваними лише те, що має бути доступне для PHP-користувача (зазвичай wp-content/uploads і, можливо, кеш-каталоги).
Завдання 4: Застосуйте розумні права (без ламання завантажень медіа)
cr0x@server:~$ sudo chown -R root:www-data /var/www/site1
cr0x@server:~$ sudo find /var/www/site1 -type d -exec chmod 0750 {} \;
cr0x@server:~$ sudo find /var/www/site1 -type f -exec chmod 0640 {} \;
cr0x@server:~$ sudo chown -R www-data:www-data /var/www/site1/wp-content/uploads
cr0x@server:~$ sudo find /var/www/site1/wp-content/uploads -type d -exec chmod 0750 {} \;
cr0x@server:~$ sudo find /var/www/site1/wp-content/uploads -type f -exec chmod 0640 {} \;
Що це означає: Core-файли не записувані для runtime-користувача; uploads — записувані. Це блокує велику категорію персистенції шляхом запису webshell в core.
Рішення: Якщо ви залежите від оновлень з панелі адміністрування, або прийміть контрольований шлях запису (і сильний моніторинг), або перейдіть на розгортання через деплой (рекомендовано для серйозних середовищ).
Завдання 5: Заблокуйте wp-config.php окремо
cr0x@server:~$ sudo stat -c '%a %U:%G %n' /var/www/site1/wp-config.php
640 root:www-data /var/www/site1/wp-config.php
Що це означає: Веб-користувач (у групі www-data) може читати конфіг, але не записувати його. Зазвичай це бажано.
Рішення: Якщо файл записуваний www-data — виправте. Якщо веб-сервер не може його прочитати — сайт не запуститься. Не «вирішуйте» це правами 777.
Завдання 6: Перевірте цілісність ядра та плагінів за допомогою WP-CLI
cr0x@server:~$ cd /var/www/site1
cr0x@server:~$ sudo -u www-data wp core verify-checksums
Success: WordPress installation verifies against checksums.
Що це означає: Core-файли відповідають очікуваним контрольним сумам. Якщо це не так, можливо, була підміна, або ви на версії/збірці, яка не відповідає сумам.
Рішення: Невідповідності вимагають розслідування. Якщо ви навмисно не патчили core — ставтеся до невідповідності як до підозрілої і перевстановіть core з довіреного джерела.
Завдання 7: Перелічіть плагіни і видаліть ті, що не використовуєте
cr0x@server:~$ sudo -u www-data wp plugin list --status=inactive
+---------------------+----------+-----------+---------+
| name | status | update | version |
+---------------------+----------+-----------+---------+
| hello-dolly | inactive | none | 1.7.2 |
| old-seo-plugin | inactive | available | 2.1.0 |
+---------------------+----------+-----------+---------+
Що це означає: Неактивні плагіни все ще є кодом на диску. Вразливості не цікавляться, чи вимкнений ваш чекбокс у UI.
Рішення: Видаліть неактивні плагіни, якщо у вас немає вагомої причини їх лишати (у такому випадку зберігайте артефакт пакета замість живого коду).
Завдання 8: Оновлюйте безпечно з прев’ю «що зміниться»
cr0x@server:~$ sudo -u www-data wp plugin update --all --dry-run
Available plugin updates:
- woocommerce (7.9.0 -> 7.9.2)
- wordfence (7.10.1 -> 7.10.2)
Success: Checked available updates.
Що це означає: Ви бачите радіус ураження перед тим, як тиснути зміни. Dry run мало хто використовує, бо людям подобаються сюрпризи, напевно.
Рішення: Стадіруйте оновлення, особливо для WooCommerce або сайтів з платним трафіком. Виконуйте роллфорвард з бекапами і планом відкату.
Завдання 9: Виявте, чи xmlrpc.php досі доступний (і вирішіть, що робити)
cr0x@server:~$ curl -s -o /dev/null -w '%{http_code}\n' https://example.com/xmlrpc.php
200
Що це означає: xmlrpc.php доступний. Це не автоматично погано, але часто зловживається.
Рішення: Якщо ви не користуєтесь Jetpack, старою мобільною публікацією чи pingback-ами — блокувати. Якщо користуєтесь — обмежте методи або сильно лімітуйте частоту.
Завдання 10: Блокуйте xmlrpc.php на вебсервері (Nginx) без торкання wp-login
Приклад сниппету для Nginx (застосуйте в server block):
cr0x@server:~$ sudo nginx -T 2>/dev/null | sed -n '1,120p'
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Що це означає: Синтаксис валідний. Тепер додайте явний location block (показано нижче) і повторно протестуйте.
cr0x@server:~$ sudo tee /etc/nginx/snippets/wordpress-xmlrpc-block.conf >/dev/null <<'EOF'
location = /xmlrpc.php {
deny all;
access_log /var/log/nginx/xmlrpc-block.log;
return 403;
}
EOF
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
cr0x@server:~$ sudo systemctl reload nginx
Рішення: Якщо пізніше ви виявите бізнес-залежність від XML-RPC, відкатайте цей сниппет і перейдіть до обмеження частоти замість повної заборони.
Завдання 11: Обмежте частоту для wp-login.php і wp-admin (тротлінг, не ламання)
Обмеження частоти повинні націлюватися на зловмисні патерни, а не на нормальну роботу редакторів. Тримайте ліміти помірними і моніторте.
cr0x@server:~$ sudo tee /etc/nginx/snippets/wordpress-login-ratelimit.conf >/dev/null <<'EOF'
limit_req_zone $binary_remote_addr zone=wp_login:10m rate=10r/m;
location = /wp-login.php {
limit_req zone=wp_login burst=20 nodelay;
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.2-fpm.sock;
}
location ~* ^/wp-admin/ {
limit_req zone=wp_login burst=40 nodelay;
try_files $uri $uri/ /index.php?$args;
}
EOF
cr0x@server:~$ sudo nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
Що це означає: Ви застосовуєте обмеження запитів по IP до шляхів входу/адміну. Burst дозволяє короткі легітимні сплески (завантаження сторінок) без покарання людей.
Рішення: Якщо бачите багато 429 від реальних користувачів — послабте rate або збільшіть burst. Якщо атаки тривалі — посиліть і додайте Fail2ban зверху.
Завдання 12: Підтвердіть, що заголовки і поведінка cookie не саботують входи
cr0x@server:~$ curl -I https://example.com/wp-login.php | egrep -i 'set-cookie|strict-transport|content-security|x-frame|x-content-type'
strict-transport-security: max-age=31536000; includeSubDomains
x-frame-options: SAMEORIGIN
x-content-type-options: nosniff
Що це означає: У вас є базові заголовки безпеки. Відсутність заголовків не завжди ламає вхід, але неправильні прапорці cookie між HTTP/HTTPS можуть.
Рішення: Якщо логіни зациклюються або cookie не зберігаються, перевірте TLS-термінацію, WP_HOME/WP_SITEURL і заголовки проксі (див. розділ «поширені помилки»).
Завдання 13: Перевірте, чи wp-login зазнає штурму (логи доступу)
cr0x@server:~$ sudo awk '$7 ~ /wp-login.php/ {print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
842 203.0.113.50
611 198.51.100.24
402 192.0.2.10
Що це означає: Топ IP-адреси, що звертаються до логіну. Великі лічильники вказують на брутфорс або credential stuffing.
Рішення: Якщо кілька IP домінують — Fail2ban може допомогти. Якщо тисячі IP розподілені — віддавайте перевагу CDN/WAF викликам і обмеженням частоти.
Завдання 14: Встановіть і перевірте Fail2ban для WordPress (приклад для Nginx)
cr0x@server:~$ sudo apt-get update
Hit:1 http://archive.ubuntu.com/ubuntu jammy InRelease
Reading package lists... Done
cr0x@server:~$ sudo apt-get install -y fail2ban
Setting up fail2ban (0.11.2-6) ...
Створіть фільтр, що відповідає повторюваним невдалим входам (потрібен log_format, що включає шлях запиту і статус; адаптуйте під свій формат). Приклад jail:
cr0x@server:~$ sudo tee /etc/fail2ban/jail.d/wordpress-login.conf >/dev/null <<'EOF'
[wordpress-login]
enabled = true
port = http,https
filter = wordpress-login
logpath = /var/log/nginx/access.log
findtime = 600
bantime = 3600
maxretry = 20
EOF
cr0x@server:~$ sudo tee /etc/fail2ban/filter.d/wordpress-login.conf >/dev/null <<'EOF'
[Definition]
failregex = ^<HOST> .* "(GET|POST) /wp-login\.php.*" (200|401|403|404) .*
ignoreregex =
EOF
cr0x@server:~$ sudo systemctl restart fail2ban
cr0x@server:~$ sudo fail2ban-client status wordpress-login
Status for the jail: wordpress-login
|- Filter
| |- Currently failed: 0
| |- Total failed: 0
| `- File list: /var/log/nginx/access.log
`- Actions
|- Currently banned: 0
|- Total banned: 0
`- Banned IP list:
Що це означає: Jail активний. «Currently failed» зростає при збігу regex. «Currently banned» показує активні бани.
Рішення: Налаштуйте failregex під фактичний формат access.log. Не розгортайте правила Fail2ban, які не протестували, якщо вам не подобається банити Wi‑Fi вашого CEO у відпустці.
Завдання 15: Підтвердіть TLS-термінацію і заголовки проксі (часта причина зациклення входу)
cr0x@server:~$ sudo -u www-data wp option get siteurl
https://example.com
cr0x@server:~$ sudo -u www-data wp option get home
https://example.com
Що це означає: WordPress думає, що працює через HTTPS. Якщо ви термінуєте TLS на балансувальнику і форвардите HTTP до origin, ви маєте також форвардити X-Forwarded-Proto і налаштувати WordPress відповідно.
Рішення: Якщо ці опції мають http://, а користувачі заходять по https://, виправте їх і переконайтеся, що ваш реверс-проксі встановлює X-Forwarded-Proto https.
Завдання 16: Перевірте насичення PHP-FPM (помилки входу під навантаженням)
cr0x@server:~$ sudo tail -n 20 /var/log/php8.2-fpm.log
[04-Feb-2026 03:12:41] WARNING: [pool www] server reached pm.max_children setting (10), consider raising it
Що це означає: PHP-воркери вичерпані. Під атакою це виглядає як «входи зламані», але насправді — нестача ресурсів.
Рішення: Підвищуйте pm.max_children тільки після перевірки CPU/RAM запасів. Також зменшіть роботу на запит (кеш, обмежте важкі плагіни, додайте WAF/обмеження частоти).
Завдання 17: Підтвердіть здоров’я бази даних (повільні запити аутентифікації можуть виглядати як блокування)
cr0x@server:~$ mysql -e "SHOW PROCESSLIST\G" | sed -n '1,40p'
*************************** 1. row ***************************
Id: 41
User: wpuser
Host: 127.0.0.1:48822
db: wordpress
Command: Query
Time: 12
State: Sending data
Info: SELECT option_name, option_value FROM wp_options WHERE autoload = 'yes'
Що це означає: Запит автозавантажуваних опцій займає час. Це часто роздувається роками, коли плагіни зберігають сміття в options.
Рішення: Аудитуйте розмір автозавантаження і скорочуйте його. Жорсткість не допоможе, якщо ендпойнт входу витрачає секунди на підвантаження сміттєвого контейнера опцій кожного запиту.
Завдання 18: Перевірте тиск на диск (бо входи теж потребують IO)
cr0x@server:~$ df -h /var/www /var/log | tail -n 2
/dev/sda1 80G 74G 2.1G 98% /
/dev/sda1 80G 74G 2.1G 98% /
Що це означає: Файлова система майже заповнена. Це ламає сесії, завантаження, логування, оновлення і іноді записи в базі даних. Також робить кожен інструмент безпеки голоснішим.
Рішення: Звільніть простір негайно. Потім відокремте логи/uploads від кореневого файлового системи і додайте оповіщення до стану перед 90%.
Жарт №2: Диск на 98% — це спосіб сервера сказати «Я не злюсь, я просто розчарований.»
Три корпоративні міні-історії з передової
Інцидент №1: Неправильне припущення (IP allowlisting «для безпеки») спричинило спіраль блокувань
Середня компанія вела сайт на WordPress для підтримки та збору лідів. Після невеликого підозрілого трафіку — інженер зробив те, що багато хто з нас робив під тиском: вони закрили доступ до /wp-admin і /wp-login.php лише для офісного діапазону IP. Це спрацювало миттєво. Шум логінів зник. Панель здавалась мирною.
Потім команда продажів почала їздити у відрядження. Домашній Wi‑Fi, аеропорти, готелі, мобільні роздавальники. Раптом звіти «WordPress не працює» почали зростати, бо з їхньої перспективи так і було. Інженери думали, що це помилка користувача, потім VPN, потім IdP. Тим часом маркетингова команда почала ділитись одним адмін-аккаунтом «щоб просто працювати», бо доступ мав лише один співробітник.
Ситуація погіршилась: зовнішнє агентство з SEO більше не могло доступитися до сайту. Вони попросили доступ; хтось тимчасово відкрив allowlist «для всіх», забув його закрити, і endpoint для входів знову опинився під ударом. Команда отримала найгірше з обох світів: блокування реальних користувачів і експозицію до первісної загрози.
Виправлення не було героїчним. Вони замінили allowlist багаторівневою системою: сильні паролі + 2FA, помірний Nginx rate limit на /wp-login.php і Fail2ban для повторних спроб. Також вони переконались, що origin недоступний, крім як через CDN/WAF. Доступ стабілізувався, а шум атак знизився до керованого, спостережуваного рівня.
Урок: IP allowlist — це не «безпека». Це крихка система ідентичності. Використовуйте її для панелей інфраструктури і приватних staging. Для WordPress в 2026 році за замовчуванням застосовуйте тротлінг і сильну аутентифікацію.
Інцидент №2: Оптимізація, що відкотила назад (агресивне кешування зламало аутентифікацію)
Інша організація запускала WordPress за Nginx з кешуючим шаром. Продуктивність була головним KPI; команда більше переймалась часом завантаження, ніж багато інших. Інженер додав правила кешування, щоб прискорити динамічні сторінки. Хіт-рейт підскочив. CPU впав. Усі раділи.
За кілька годин з’явились тікети підтримки: користувачі не могли увійти, або входили і одразу виходили. Дехто бачив, як панелі адміна інших користувачів миготіли — рідко, але лякаюче. Команда спочатку підозрювала XSS або крадіжку сесій. Паніка — гучна.
Справжній винуватець виявився банальний: кешуючий шар кешував відповіді, які не мав кешувати. Деякі сторінки віддавались з неправильними куками, і кеш не варіювався за станом аутентифікації. Оптимізація була корисною для анонімного контенту і катастрофічною для всього, що стосується сесій.
Відновлення полягало в ретельному переписуванні правил кешування: ніколи не кешувати /wp-login.php, не кешувати /wp-admin, і уникати кешування сторінок, коли присутні auth cookie. Також додали явні умови bypass для WooCommerce і куків членства. Після цього стабільність входів повернулась, а приріст продуктивності залишився — просто не на тих ендпойнтах, де коректність є незаперечною.
Урок: оптимізація продуктивності може стати інцидентом безпеки, якщо ламає ізоляцію. Кешуйте по-державному: визначте, що не можна кешувати, а потім перевіряйте тестами і заголовками.
Інцидент №3: Бронебійна, але правильна практика врятувала ситуацію (принцип найменших привілеїв + відпрацювання відновлення)
SaaS-компанія хостила маркетинговий сайт на WordPress, який здавався простим, але мав серйозну бізнес-цінність: сторінки цін, форми реєстрації та кейси клієнтів використовувались у переговорних процесах з підприємствами. Вони ставились до нього як до продакшену, бо так воно і було. Команда мала політику, що звучала банально: власність файлів під контролем, жодних in-place інсталяцій плагінів у продакшені і щомісячні тренування з відновлення бази даних і uploads.
Одного вихідного дня у новинах з’явилась вразливість плагіна. Боти рухались швидко. Сайт почав отримувати шкідливі запити, спрямовані на upload-ендпоінт цього плагіна. Деякі запити потрапляли до PHP, але експлойт не міг закріпитися, бо веб-користувач не мав прав записувати core директорії або директорію плагіна. Атакуючі могли тикати — але не моголи оселитись.
Вони все одно не святкували. Передбачаючи, що «дещо могло вдатися десь», вони провели перевірки цілісності, порівняли директорії плагінів з відомо чистими артефактами і проаналізували логи на предмет аномальних POST-запитів. Поралили ключі/солі і примусили обнулити паролі адміністраторів, щоби перестрахуватися.
Справжній виграш прийшов від нудної практики: команда мала перевірену процедуру відновлення. Вони зняли snapshot, відновили в ізольованому середовищі і перевірили поведінку сайту і файлову структуру. Цього разу відновлення не знадобилось, але це перетворило страх на контрольований етап перевірки. Без вгадувань, без забобонів.
Урок: коли ви вмієте відновлювати — ви можете жорстко посилювати заходи. Не тому, що плануєте провал — а тому, що не дозволяєте себе затиснути провалом.
Поширені помилки: симптом → корінь проблеми → виправлення
Сторінка входу повертає 403 для всіх
Симптоми: Усі користувачі бачать 403 на /wp-login.php. Адмін-доступ мертвий. Редактори кричать.
Корінь проблеми: Надто широке правило deny (Nginx/Apache), WAF у режимі «block» замість «challenge», або гео/IP-обмеження, застосоване до endpoint-а входу.
Виправлення: Відкотіть правило, націлюючи тільки /xmlrpc.php або відомі погані шляхи; перейдіть на обмеження частоти; додайте аварійну обхідну доріжку доступну лише через VPN.
Користувачі можуть завантажити wp-login, але облікові дані не зберігаються (зациклення входу)
Симптоми: Користувач вводить правильний пароль, отримує перенаправлення назад на логін. Cookie не зберігаються.
Корінь проблеми: Невідповідність HTTPS-термінації (siteurl/home невірні), відсутній X-Forwarded-Proto, або кешування відповідей логіну.
Виправлення: Виправте home і siteurl на HTTPS, налаштуйте заголовки проксі, відключіть кешування для auth-ендпойнтів, перевірте COOKIE_DOMAIN і канонічний хост.
Деякі користувачі отримують 429 Too Many Requests, а інші — ні
Симптоми: Віддалені команди скаржаться; офісні користувачі в порядку. Мобільний додаток працює нестабільно.
Корінь проблеми: Лімітування частоти ключується неправильно (наприклад, за спільним NAT-IP, або за заголовком, що зливає різних користувачів в одну корзину).
Виправлення: Ключуйте ліміти за $binary_remote_addr на краю; якщо за proxy — переконайтеся, що справжній IP клієнта коректно встановлений (real_ip module). Збільшіть burst. Додайте CAPTCHA/виклики лише там, де очікуються люди.
Адмін-сторінки повільні, входи таймаутять під навантаженням
Симптоми: 504/502 помилки, періодичні проблеми з входами, зростання CPU і IO wait.
Корінь проблеми: Насичення PHP-FPM, повільні запити до БД або диск майже повний. Іноді брутфорс-атака лише підсилює вбудовану неефективність.
Виправлення: Збільшіть потужність PHP-FPM, якщо дозволяють ресурси; правильно додайте кеш; налаштуйте базу; скоротіть автозавантажувані опції; і додайте захист на краю (WAF/обмеження частоти).
Після «жорсткості» оновлення плагінів і завантаження медіа не працюють
Симптоми: «Could not create directory», помилки оновлення, проблеми з правами.
Корінь проблеми: Власність/права надто суворі або непослідовні між webroot і wp-content.
Виправлення: Прийміть рішення: оновлення через деплой (бажано) або дозвольте контрольований запис до wp-content. Не робіть core записуваним, щоб вирішити проблему з плагіном.
Fail2ban банить реальних користувачів повторно
Симптоми: Користувачі блокуються після кількох спроб; бани корелюють з офісним/VPN NAT.
Корінь проблеми: Regex занадто широкий, maxretry занадто малий, NAT концентрує багатьох користувачів за одним IP.
Виправлення: Збільшіть maxretry, скоротіть bantime, налаштуйте regex під реальні невдачі (наприклад, 200 на wp-login не завжди означає «помилка»), і віддавайте перевагу викликам WAF, де можливо.
Контрольні списки / покроковий план (не імпровізуйте в продакшені)
Фаза 0: Передпольотна перевірка (зробіть це до зміни контролів безпеки)
- Інвентаризація залежностей: Чи використовуєте ви XML-RPC? Jetpack? Мобільну публікацію? Які зовнішні системи звертаються до REST-ендпойнтів?
- Визначте, де відбуватиметься примус: CDN/WAF спочатку, потім вебсервер, потім плагіни в аплікації. Не кладіть усе в WordPress, якщо можете уникнути.
- Встановіть аварійний доступ: VPN-шлях, bastion або тимчасова процедура техобслуговування, що не залежить від входу в WordPress.
- Бекапи + тест відновлення: Переконайтесь, що можете відновити базу даних і
wp-content/uploadsв чистому середовищі. - Вікно змін і план відкату: Знайте точно, які конфіг-файли і правила ви змінюєте і як швидко відкотитись.
Фаза 1: Приберіть прості виграші (найменший ризик, найбільша користь)
- Видаліть невикористані плагіни і теми. Неактивні — не безпечні; вони просто сидять і чекають.
- Оновіть WordPress core і плагіни. Стадіруйте спочатку, якщо сайт критичний для доходу.
- Встановіть коректні права і власність. Тримайте core доступним лише для читання для runtime; uploads — записуваними.
- Згенеруйте нові солі/ключі якщо підозрюєте компрометацію або якщо сайт міг мігрувати через кількох людей.
Фаза 2: Захистіть вхід, не зламавши його
- Обмежте частоту
/wp-login.phpна вебсервері або на краю. Уникайте жорстких заборон, якщо не впевнені. - Увімкніть 2FA для адміністраторів (або SSO зі суворою політикою). Це найбільший редуктор ризику для входу.
- Відключіть XML-RPC якщо не використовується; в іншому випадку обмежте і моніторьте.
- Обмежте шляхи створення адміністраторів (не давайте плагінам автоматично підвищувати ролі; регулярно аудитуйте користувачів).
Фаза 3: Додайте запобіжні механізми і видимість
- Централізуйте логи (принаймні access + error логи Nginx/Apache, логи PHP-FPM і логи, важливі для аутентифікації в аплікації).
- Налаштуйте оповіщення: сплески запитів до
/wp-login.php, зростання 403/429, використання диска, насичення PHP-FPM, кількість повільних запитів БД. - Моніторинг цілісності файлів для core і директорій плагінів (хеші або контрольні суми). Якщо зміни відбуваються поза деплоєм — це тривога.
- Практикуйте реакцію на інциденти: ізолюйте origin, ротейт ключі, відновіть у чистому середовищі, перевірте.
Список «Не робіть цього» (бо вас тягне)
- Не ховайте
/wp-adminплагінами безпеки і не вважайте це «завершеним». Боти краулать, а люди забувають. - Не ставте права 777, щоб «вирішити» оновлення. Це не вирішення — це капітуляція.
- Не покладайтеся на IP-allowlist для користувачів, які подорожують. Це те, як з’являються тіньові адмін-акаунти.
- Не розгортайте нетестовані правила кешування на auth-ендпойнтах. Якщо мусите — тестуйте з кількома сесіями і cookie.
FAQ
1) Чи варто вимикати xmlrpc.php?
Якщо ви не використовуєте функції, що залежать від нього — так, блокуйте на вебсервері. Якщо використовуєте (Jetpack, деяка спадкова публікація) — залишайте, але обмежуйте і моніторьте.
2) Чи справді зміна URL входу — це серйозна міра безпеки?
Це радше зменшення шуму, ніж основний захід. Воно може зменшити кількість дурних ботів, але не зупинить цілеспрямовані атаки або експлойтнуті плагіни. Використовуйте лише якщо це не ламає інтеграції і у вас все ще є обмеження частоти та сильна аутентифікація.
3) Чи лімітування частоти зламає легітимних користувачів за NAT?
Може. Тому встановлюйте розумні rate і burst, і ключуйте по реальному IP клієнта. Починайте помірно, стежте за 429 і потім підтягуйте.
4) Чи вистачає Fail2ban без WAF/CDN?
Fail2ban допомагає, коли атаки концентруються на повторюваних IP. Credential stuffing часто розподілений по багатьом IP, де кращі захисти на краю. На практиці: використовуйте обидва, якщо сайт має значення.
5) Які права файлів повинні бути у WordPress?
Core має бути доступним лише для читання для runtime-користувача. Uploads мають бути записуваними. Звичний патерн: core належить root, група www-data, файли 0640 і директорії 0750; uploads — належать www-data.
6) Чи дозволяти WordPress оновлювати плагіни з панелі?
Для персональних сайтів — можливо. Для продакшен-бізнесу — краще оновлення через деплой, щоб runtime не міг записувати виконуваний код. Якщо змушені дозволяти це, обмежте права запису до wp-content і моніторьте цілісність.
7) Чому входи ламаються після ввімкнення політики заголовків безпеки?
Занадто суворий Content Security Policy (CSP) може блокувати скрипти на сторінці входу, особливо з кастомним темами або плагінами, що вставляють активи. Впроваджуйте CSP у режимі звітів спочатку, потім жорсткійте.
8) Як зрозуміти, чи origin відкритий за CDN?
Перевірте DNS і фаєрвол. Якщо origin IP доступний і віддає WordPress напряму, атакувальники можуть обійти ваші крайові контролі. Закрийте origin для всіх, окрім IP-діапазонів CDN або приватного мережевого шляху.
9) Чи потрібен плагін безпеки?
Не завжди. Серверні контроли (rate limits, WAF, права, оновлення, моніторинг) виконують більшу частину роботи. Плагіни безпеки можуть додати 2FA і зручні фічі, але це ще код у вашому додатку.
10) Що мінімально потрібно зробити сьогодні, якщо я в паніці?
Оновіть core/плагіни, видаліть невикористані плагіни/теми, зафіксуйте права, додайте rate limiting на /wp-login.php і увімкніть 2FA для адміністраторів. Потім підключіть логування/оповіщення, щоб бачити, що відбувається.
Висновок: наступні кроки, що дійсно дають результат
Жорсткість WordPress без ламання входів — це переважно стриманість. Не бийте по всіх одразу. Зменшіть площу атаки, закрийте те, що записується, обмежуйте зловживання, і додайте сильну аутентифікацію. Потім інструментуйте все, щоб ваші механізми безпеки не стали наступною причиною простою.
Зробіть наступне:
- Запустіть перевірки цілісності і інвентаризацію (WP-CLI checksums, список плагінів, права).
- Реалізуйте rate limiting на вебсервері для
/wp-login.phpі блокуйте/xmlrpc.php, якщо не використовується. - Увімкніть 2FA для адміністраторів (або нав’яжіть політику SSO) і проведіть аудит адміністраторських акаунтів.
- Перевірте коректність proxy/TLS, щоб уникнути зациклення входів.
- Додайте оповіщення: сплески 403/429, попередження pm.max_children у PHP-FPM, використання диска і частоту запитів на входи.
- Заплануйте drill з відновлення. Не тому, що ви параноїк — а тому, що любите спати.