Ваш сайт на WordPress поводиться дивно. Трафік упав. Search Console сигналізує. Клієнти надсилають скріншоти з посиланнями на казино, які ви точно не публікували. Хтось у Slack каже «просто видаліть сайт і відновіть з резервної копії». Хтось інший каже «встановіть плагін безпеки». Обидва намагаються допомогти. Обидва можуть все погіршити.
Коли WordPress зламаний, технічну проблему вирішити можна. Операційна проблема складніша: не знищуйте докази, не поширюйте інфекцію, не обманюйте себе «зеленим» сканером і не відновлюйте скомпрометовану резервну копію в продакшен із довірою, яку ви не заслужили.
Основні правила: стабілізуйте, перш ніж «виправляти»
Реагування на інцидент — це не атмосфера. Це послідовність. Ви можете діяти швидко, але не можна пропускати кроки, не заплативши потім — зазвичай о 2-й ночі на дзвінку з фінансовим директором.
Ось правила, які вберігають від самонавіщуваної катастрофи:
- Не входьте через wp-admin, щоб «подивитися, що відбувається», якщо підозрюєте крадіжку облікових даних. Спочатку використовуйте доступ до сервера.
- Не запускайте інструменти «очищення» першими. Вони мутують докази і можуть видалити єдину підказку про те, як атакувальник проник.
- Не відновлюйте поверх працюючого скомпрометованого хоста. Якщо хост скомпрометовано, ваш «свіжий» WordPress — просто новий орендар.
- Спочатку локалізуйте, потім розслідуйте. Майте на увазі бізнес-наслідки, але спочатку звузьте радіус ураження, перш ніж починати турбувати ведмедя.
- Визначте вашу мету: чи це маркетинговий сайт, який можна взяти офлайн, чи критичний шлях оплати? Від цього залежить стратегія локалізації.
Цитата, яку варто запам’ятати (перефразована думка): Джин Кранц, директор польотів NASA, наполягав на «жорсткості й компетентності» як стандарті роботи — без виправдань, без паніки, лише дисципліновані дії.
Швидкий план діагностики (перший/другий/третій)
Це план «увійти й не спіткнутися об власні ноги». Мета — визначити вузьке місце: чи це компроміс на рівні WordPress, рівні хоста, ланцюжок постачання (плагін/тема) або просто наслідок трафіку/SEO?
Перший: підтвердьте вплив і пріоритет локалізації (5–10 хвилин)
- Сайт зараз віддає шкідливий контент? Якщо так: локалізуйте на краю (WAF/CDN) або на вебсервері (режим обслуговування / правила deny).
- Чи під загрозою дані? Якщо сайт має облікові записи користувачів, оформлення замовлень або адмін-логіни: вважайте крадіжку облікових даних доведеною, поки не доведете протилежне.
- Хост спільний? Спільний хостинг або мультиорендний VM означає можливість латерального руху. Локалізуйте агресивніше.
Другий: визначте шар компромісу (10–20 хвилин)
- Ознаки лише WordPress: інжектований JS у публікаціях, підозрілі адміні користувачі, змінені файли теми, зламані плагіни, дивні cron-події.
- Ознаки хоста: невідомі процеси, SSH-ключі, яких ви не впізнаєте, поведінка на кшталт rootkit, підозрілі вихідні з’єднання.
- Ознаки ланцюжка постачання: оновлення плагіна/теми поряд із першим виявленням або «nulled» тема в історії. (Nulled — це піратська копія з ймовірним шкідливим вмістом.)
Третій: оберіть найшвидший безпечний шлях
- Якщо ймовірний компроміс хоста: перебудуйте хост. Не торгуйтеся з ним.
- Якщо ймовірний компроміс WordPress: перемістіть сайт на чистий хост, відновіть з відомої доброї резервної копії, потім вибірково мігруйте контент.
- Якщо бракує добрих резервних копій: робіть судову експертизу і хірургічне очищення, але все одно плануйте перебудову.
Дев’ять фактів і трохи історії (що змінюють рішення)
- WordPress почався в 2003 році як форк b2/cafelog. Його успіх зробив його мішенню, бо зловмисники люблять ROI.
- Ера «TimThumb» (початок 2010-х) навчила екосистему болючому уроку: один популярний компонент, який використовується всюди, стає множником масової експлуатації.
- XML-RPC (введений для віддаленого публікування) неодноразово зловживався для брутфорсу та шаблонів підсилення. Багатьом сайтам він не потрібен.
- wp-cron.php — це не справжній cron; він запускається вебзапитами. Під навантаженням або атакою може стати самонанесеним DoS або каналом стійкості.
- Цілісність файлів — це суперсила: ядро WordPress детерміноване. Якщо ви не можете швидко сказати, що змінилося, ви працюєте в темряві.
- Більшість компромісів — не «нульовий день»; це застарілі плагіни/теми, повторне використання паролів, витоки облікових даних або файли з правом запису.
- SEO-спам часто «умовний»: атакувальники віддають чисті сторінки адміністраторам і шкідливі — пошуковим ботам або певним user-agent, щоб затримати виявлення.
- Бекдори люблять нудні місця: mu-plugins, директорії must-use, кеш-директорії, uploads та «тимчасові» папки — поширені сховища, бо їх не звіряють.
- Чисте сканування не доводить чистоти: багато сканерів працюють за сигнатурами. Атакувальники можуть бути креативніші за регулярний вираз.
Фаза 1: локалізація без побічних збитків
Локалізація — це про зупинку шкоди. Не про доведення кореневої причини. Не про ідеальне очищення. Зупиніть кровотечу.
Локалізуйте на краю, якщо можете
Якщо у вас є CDN/WAF, переключіть сайт у сторінку обслуговування або блокуйте підозрілі шляхи. Це швидко, відкатно і не мутує скомпрометований хост.
- Блокуйте доступ до
/wp-adminі/wp-login.php, окрім від відомих IP. - Обмежуйте частоту або блокуйте запити з шаблонами експлойтів на відомі вразливі кінцеві точки.
- Тимчасово вимкніть XML-RPC, якщо він не потрібен.
Локалізуйте на сервері, коли край недоступний
Якщо ви на одному VM без WAF, ви все ще можете локалізувати проблему. Робіть це так, щоб зберегти логи і не знищити файлову систему.
Короткий жарт №1: Якщо ваша перша реакція — «chmod -R 777, щоб працювало», вітаємо — ви винайшли підписку на майбутні зараження.
Фаза 2: збереження доказів (дешева страховка)
Збереження доказів — це не про косплей судових експертів. Це про відповіді на два питання пізніше: «Як вони потрапили всередину?» і «Чи ми справді чисті?» Без доказів ви будете гадати. Гадання дорого коштує.
Збережіть:
- Вебсерверні логи (access і error).
- Логи PHP-FPM, якщо є.
- Дерево директорій WordPress (або принаймні хеші + метадані).
- Знімок бази даних.
- Системні логи авторизацій (
/var/log/auth.logабо еквівалент). - Список процесів і мережевих з’єднань.
Фаза 3: триаж з командами (що це означає, що вирішуєте)
Ці завдання розраховані на типовий Linux-хост (Debian/Ubuntu-подібний). Підлаштуйте шляхи під ваш дистрибутив і стек. Кожне завдання включає: команду, що звичайний вивід означає, і рішення, яке ви приймаєте.
Завдання 1: ідентифікуйте корінь вебу та дрейф власності
cr0x@server:~$ ps aux | egrep 'nginx|apache2|httpd|php-fpm' | head
root 812 0.0 0.3 56400 7420 ? Ss Dec26 0:02 nginx: master process /usr/sbin/nginx
www-data 913 0.0 0.4 59824 10240 ? S Dec26 0:11 nginx: worker process
root 1021 0.0 0.6 225320 13240 ? Ss Dec26 0:05 php-fpm: master process (/etc/php/8.2/fpm/php-fpm.conf)
www-data 1055 0.0 0.7 225320 15400 ? S Dec26 0:40 php-fpm: pool www
Що це означає: Ваші web/PHP-воркери працюють від www-data. Якщо файли WordPress належать www-data, вебпроцес може їх змінювати — зручно для оновлень і дуже зручно для атакувальників.
Рішення: Якщо сайт скомпрометовано і файли належать веб-користувачу, вважайте цілісність файлів недовірливою. Плануйте перебудову з відомих добрих джерел, а не «очищення на місці».
Завдання 2: зафіксуйте вигляд файлової системи (швидка інвентаризація недавніх змін)
cr0x@server:~$ sudo find /var/www/example -type f -mtime -3 -printf '%TY-%Tm-%Td %TT %u %g %p\n' | head
2025-12-26 09:12:44.000000000 www-data www-data /var/www/example/wp-content/uploads/2025/12/cache.php
2025-12-26 09:12:50.000000000 www-data www-data /var/www/example/wp-includes/version.php
2025-12-26 09:13:02.000000000 www-data www-data /var/www/example/wp-content/plugins/hello/hello.php
Що це означає: Файли ядра, як-от wp-includes/version.php, не повинні змінюватися внаслідок завантажень. cache.php у uploads — класичний маркер «PHP у uploads».
Рішення: Якщо ви бачите модифікації файлів ядра поза плановими оновленнями, піднімайте це як «пошкодження ядра». Зазвичай це означає повну заміну кодової бази.
Завдання 3: перевірте виконання PHP у uploads (перевірка конфігурації)
cr0x@server:~$ grep -R --line-number -E 'location\s+~\s+\*?\s*\^?/wp-content/uploads|deny\s+all|php' /etc/nginx/sites-enabled/* 2>/dev/null
/etc/nginx/sites-enabled/example.conf:42:location ~* ^/wp-content/uploads/.*\.php$ { deny all; }
Що це означає: На цьому сервері є правило, що забороняє PHP у uploads. Якщо ви не бачите цього (або еквіваленту), атакувальники можуть кинути «.php» бекдор у uploads і виконати його.
Рішення: Якщо правило відсутнє, додайте його під час відновлення. Якщо правило є, але ви все одно бачите бекдори у uploads — шукайте альтернативні шляхи виконання (наприклад, .phtml, неправильно налаштовані обробники або пряме маршрутування до PHP-FPM).
Завдання 4: перевірте веб-логи на сплески експлойтів і підозрілі кінцеві точки
cr0x@server:~$ sudo awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
4123 /wp-login.php
1988 /xmlrpc.php
744 /wp-admin/admin-ajax.php
321 /wp-content/uploads/2025/12/cache.php
275 /wp-json/wp/v2/users
Що це означає: Великий об’єм запитів до wp-login.php і xmlrpc.php вказує на атаки на облікові дані. Запити до PHP-файлу в uploads вказують на спроби виконання або на успішне використання.
Рішення: Якщо ви бачите прямі звернення до підозрілого файлу, пріоритезуйте ізоляцію та збереження цього файлу, потім шукайте сусідні файли та механізми стійкості.
Завдання 5: зіставте підозрілі запити з кодами відповіді
cr0x@server:~$ sudo grep 'cache.php' /var/log/nginx/access.log | tail -5
203.0.113.44 - - [26/Dec/2025:09:12:56 +0000] "GET /wp-content/uploads/2025/12/cache.php?cmd=id HTTP/1.1" 200 31 "-" "curl/7.74.0"
203.0.113.44 - - [26/Dec/2025:09:13:10 +0000] "GET /wp-content/uploads/2025/12/cache.php?cmd=uname+-a HTTP/1.1" 200 98 "-" "curl/7.74.0"
Що це означає: Відповідь 200 на ?cmd=id — це майже зізнання. Це інтерактивна веб-оболонка або виконавець команд.
Рішення: Розглядайте як підтверджене віддалене виконання коду. Вважайте крадіжку облікових даних і спроби латерального руху. Перебудуйте хост, якщо не можете довести межі ізоляції.
Завдання 6: зніміть знімок запущених процесів (шукайте дивні PHP або cron)
cr0x@server:~$ ps aux --sort=-%cpu | head -10
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
www-data 21144 85.2 1.9 412300 38220 ? R 09:14 3:12 php /var/www/example/wp-content/uploads/2025/12/cache.php
www-data 1055 4.1 0.7 225320 15400 ? S Dec26 0:40 php-fpm: pool www
Що це означає: PHP, що виконує файл у uploads як довготривалий процес — ненормально. Зазвичай це веб-оболонка, яка виконує роботу (розсилка спаму, сканування, майнінг, вихідні колбеки).
Рішення: Локалізуйте негайно (блокуйте запити; виведіть сайт з мережі), потім збережіть докази. Припинення процесу допустиме, але не обмежуйтеся цим — замініть файл і знайдіть, як він опинився там.
Завдання 7: перевірте вихідні з’єднання (чи «дзвонить додому»?)
cr0x@server:~$ sudo ss -tpn | head
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
ESTAB 0 0 10.0.0.12:52418 198.51.100.77:443 users:(("php",pid=21144,fd=12))
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=812,fd=6))
Що це означає: PHP-процес, що встановлює TLS-з’єднання назовні, часто означає ексфільтрацію, C2 або подачу спаму. Може бути й легітимний API-запит, але PID вказує на підозрілий процес.
Рішення: Заблокуйте вихідний egress на рівні фаєрволу для інстансу, якщо можливо, принаймні тимчасово, потім переходьте до перебудови/ліквідації.
Завдання 8: перевірте цілісність ядра WordPress (не довіряючи веб-інтерфейсу)
cr0x@server:~$ cd /var/www/example && sudo -u www-data wp core verify-checksums
Warning: File should not exist: wp-includes/wp-tmp.php
Warning: File should not exist: wp-admin/css/colors/coffee/coffee.php
Error: Checksum verification failed for: wp-includes/version.php, wp-settings.php
Що це означає: Несподівані файли в директоріях ядра й помилки контрольних сум вказують на підтасовування. Це не «просто поганий плагін». Це глибше.
Рішення: Повністю замініть ядро WordPress з надійного джерела. Не намагайтеся редагувати файли рядок за рядком, окрім як для судової експертизи.
Завдання 9: перерахунок адміністраторів і пошук «сплячих» акаунтів
cr0x@server:~$ sudo -u www-data wp user list --role=administrator --fields=ID,user_login,user_email,user_registered
+----+------------+----------------------+---------------------+
| ID | user_login | user_email | user_registered |
+----+------------+----------------------+---------------------+
| 1 | editor-in-chief | eic@example.com | 2021-04-12 10:22:19 |
| 42 | wp_support | wp-support@proton.tld | 2025-12-26 09:10:03 |
+----+------------+----------------------+---------------------+
Що це означає: Нові адміністративні акаунти, створені в час компрометації, — поширена стійкість. Домен пошти може бути підказкою, але не покладайтеся на стереотипи — атакувальники використовують нормальні адреси теж.
Рішення: Відключіть/видаліть підозрілі акаунти після збереження доказів. Потім обов’язково змініть усі паролі адміністраторів і скиньте сесії.
Завдання 10: дамп і сканування cron-подій WordPress на наявність стійкості
cr0x@server:~$ sudo -u www-data wp cron event list | head
+----------------------+---------------------+---------------------+----------+
| hook | next_run_gmt | recurrence | args |
+----------------------+---------------------+---------------------+----------+
| wp_version_check | 2025-12-27 02:10:00 | twice_daily | |
| wp_update_plugins | 2025-12-27 02:12:00 | twice_daily | |
| wp_tmp_cache_refresh | 2025-12-26 09:20:00 | every_minute | |
+----------------------+---------------------+---------------------+----------+
Що це означає: Нестандартний хук, що запускається щохвилини, підозрілий, особливо якщо ваш сайт не потребує частого планування задач.
Рішення: Знайдіть, хто зареєстрував цей хук (плагін/тема/mu-plugin) і видаліть його. Якщо не можете впевнено атрибутувати — вважайте його шкідливим, поки не доведено протилежне.
Завдання 11: ідентифікуйте mu-plugins і must-use стійкість
cr0x@server:~$ ls -la /var/www/example/wp-content/mu-plugins
total 24
drwxr-xr-x 2 www-data www-data 4096 Dec 26 09:09 .
drwxr-xr-x 8 www-data www-data 4096 Dec 26 09:05 ..
-rw-r--r-- 1 www-data www-data 8120 Dec 26 09:09 security-update.php
Що це означає: Багато сайтів взагалі не використовують mu-plugins. Нещодавно створений mu-plugin — високосигнальне місце стійкості, бо воно завантажується автоматично.
Рішення: Карантинуйте та проінспектуйте файл. Якщо він шкідливий, у вас є ймовірний механізм стійкості та часовий маркер у логах.
Завдання 12: пошук загальних шаблонів обфускації (швидко, не ідеально)
cr0x@server:~$ sudo grep -R --line-number -E 'base64_decode\(|gzinflate\(|str_rot13\(|eval\(|preg_replace\(.*/e' /var/www/example/wp-content | head
/var/www/example/wp-content/uploads/2025/12/cache.php:1:
/var/www/example/wp-content/themes/example/functions.php:402:$a=gzinflate(base64_decode($b));
Що це означає: Ці функції не завжди злі, але в WordPress-світі їх часто використовують для приховування payload. Хіт у uploads — майже напевно шкідливий.
Рішення: Розглядайте як індикатори компромісу. Замініть тему/плагін з надійної копії. Не «вирізайте один рядок» і не вважайте це вирішенням.
Завдання 13: перевірте базу даних на ін’єкції та підозрілі опції
cr0x@server:~$ sudo mysql -NBe "SELECT option_name FROM wp_options WHERE option_name IN ('active_plugins','siteurl','home')"
home
siteurl
active_plugins
Що це означає: Це підтверджує наявність ключів опцій (нормально). Далі потрібно перевірити їх значення: чи вказують вони туди, куди очікуєте, і чи список активних плагінів адекватний?
Рішення: Якщо siteurl/home змінені — це може бути ін’єкція редиректу або захоплення адмінства. Якщо active_plugins містить невідомі плагіни — це вектор стійкості.
Завдання 14: перевірте базу на підозрілі редиректи (умовний SEO-спам)
cr0x@server:~$ sudo mysql -NBe "SELECT ID,post_title FROM wp_posts WHERE post_content LIKE '%document.location%' LIMIT 5"
1931 Summer Sale
2077 About Us
Що це означає: JavaScript-редиректи в полях контенту часто призводять до SEO-спаму, афілійованих редиректів або експлойт-китів.
Рішення: Якщо знайдено — плануйте очищення контенту (експорт, санітизація, повторний імпорт) і інвалідну кешів/CDN. Також припускайте, що атакувальник мав запис у БД або адмінський доступ.
Завдання 15: підтвердіть історію SSH-доступів і несподівані ключі
cr0x@server:~$ sudo tail -50 /var/log/auth.log | egrep 'Accepted|Failed|session opened' | tail
Dec 26 09:08:14 server sshd[18422]: Accepted password for ubuntu from 203.0.113.44 port 53122 ssh2
Dec 26 09:08:15 server sshd[18422]: pam_unix(sshd:session): session opened for user ubuntu by (uid=0)
Що це означає: Успішний SSH-логін з тієї ж IP, що й доступ до вашої веб-оболонки — дуже поганий сигнал. Це вже не «злам WordPress», це «коробка під контролем».
Рішення: Починайте повну перебудову хоста. Обертайте всі секрети, що колись торкалися цього хоста. Перегляньте журнали хмари на предмет змін ключів і знімків.
Завдання 16: підтвердіть, що резервні копії не скомпрометовані (перевірте зразок)
cr0x@server:~$ sudo tar -tf /backups/example/wp-files-2025-12-20.tar.gz | egrep 'wp-content/uploads/.*\.php|mu-plugins|wp-includes/wp-tmp' | head
var/www/example/wp-content/uploads/2025/12/cache.php
var/www/example/wp-content/mu-plugins/security-update.php
Що це означає: Якщо у вашому бекапі ті самі шкідливі артефакти, відновлення відтворить компроміс. Резервні копії зберігають правду, не чесноту.
Рішення: Знайдіть останню відому добру резервну копію. Якщо не можете — перебудуйте з чистих джерел і мігруйте лише валідуваний контент.
Поширені вектори проникнення й як їх довести
Ви не маєте права заявляти «вразливий плагін», бо це звучить правдоподібно. Доведіть це або ставте систему як скомпрометовану багатьма способами.
1) Вкрадені адмін-облікові дані
Як це відбувається: повторне використання паролів, фішинг, credential stuffing, витік сесії браузера або інфікований ноутбук адміністратора.
Як довести:
- Шукайте успішні логіни та адміністративні дії біля часу першого компромісу в логах доступу.
- Перевірте наявність нових адміністративних акаунтів (Завдання 9).
- Перевірте сесії WordPress і паролі додатків, якщо використовувалися.
Що робити: обертайте всі адмін-облікові дані, примусово виведіть сесії, увімкніть MFA і перегляньте, хто має адмінські права.
2) Вразливий плагін/тема або компроміс ланцюга постачання
Як це відбувається: застарілий плагін з відомою вразливістю або оновлення плагіна, взяте з скомпрометованого джерела.
Як довести:
- Порівняйте файли плагіна з відомими добрими версіями.
- Перевірте часові мітки: директорія плагіна, змінена перед компромісом, підозріла.
- Перегляньте логи на наявність запитів до специфічних для плагіна кінцевих точок перед появою шкідливого файлу.
Що робити: видаліть/замістьте плагін; фіксуйте джерела оновлень; радикально скорочуйте кількість плагінів.
3) Файлова система з правом запису + ланцюг RCE
Як це відбувається: як тільки атакувальник має виконання коду, він записує механізми стійкості у файли WordPress, uploads або mu-plugins.
Як довести: підозрілі PHP у uploads (Завдання 2/5/6/12), невідповідність контрольних сум (Завдання 8), несподівані mu-plugins (Завдання 11).
Що робити: заблокуйте права запису; забороніть виконання PHP у доступних для запису директоріях; переходьте на іммутабельні деплоя, якщо можливо.
4) Скомпрометований хостинговий акаунт / SSH
Як це відбувається: слабкий пароль SSH, витік ключа, повторне використання облікових даних, вкрадений токен API хмари.
Як довести: auth-логи показують несподівані входи (Завдання 15), нові ключі в ~/.ssh/authorized_keys або несподіване використання sudo.
Що робити: перебудуйте хост, обертайте секрети, додайте MFA до консолі хмари, заблокуйте SSH, перегляньте IAM.
Фаза 4: ліквідація (фактичне видалення зловмисника)
Ліквідація — це місце, де люди стають безпечними. Вони видаляють веб-оболонку, почуваються героєм і йдуть далі. Тим часом атакувальник зберігає доступ через mu-plugin, cron-хук, підозрілий акаунт або інжектор у базі. Ви нічого не ліквідували; ви виконали інтерпретаційний танець на живій системі.
Бажаний підхід: перебудова на чистому хості
Якщо є хоча б натяк на компроміс рівня хоста — перебудуйте. Це означає новий VM/контейнер, чистий образ ОС, запатчені пакунки, мінімальні сервіси, чисті SSH-ключі, доступ до БД найменшого привілею і відновлення під вашим контролем.
Так, це більше роботи, ніж «очищення». Але це швидше, ніж грати у whack-a-mole два тижні.
Коли потрібно чистити на місці (останній варіант)
Іноді реальність кусає: немає запасних потужностей, немає автоматики інфраструктури, немає чистої резервної копії, бізнес не дозволяє простій. Якщо ви чистите на місці, робіть це дисципліновано:
- Замініть ядро WordPress свіжою копією (не патчте окремі файли ядра).
- Замініть кожен плагін і тему з надійного джерела або видаліть їх.
- Видаліть невідомі PHP-файли в uploads/cache/temp після збереження доказів.
- Скиньте всі WordPress salts/keys і всі паролі користувачів (принаймні адміністраторів).
- Обертайте креденшіали бази даних і переконайтесь, що користувач БД має найменше привілеїв.
- Вимкніть XML-RPC, якщо не потрібен; увімкніть MFA для адміністраторів; обмежте wp-admin за IP, якщо можливо.
Дві області, які люди забувають: база даних і планувальник
Атакувальники люблять стійкість, що переживе заміну файлів. Це зазвичай: (1) опції WordPress, (2) заплановані задачі, (3) адміністраторські акаунти, (4) інжектований контент у постах, (5) системні cron-джоби.
Фаза 5: відновлення й безпечне повернення в роботу
Відновлення — це не «сайт завантажився». Відновлення — це «сайт працює, і ми можемо захистити це твердження». Правильне відновлення відповідає: що ми відновили, чому ми йому довіряємо, що ми змінили і як ми виявимо повторну інфекцію.
Стратегія відновлення, яка не заново вас інфікує
- Підніміть чисте середовище (новий VM/контейнер, запатчена ОС).
- Встановіть ядро WordPress з надійного джерела (та ж версія, що у чистій резервній копії, або оновлена, якщо ви можете перевірити сумісність).
- Відновіть базу даних з останнього відомо доброго знімка, потім перевірте її на інжекції контенту/опцій.
- Відновлюйте uploads обережно. У uploads ховаються бекдори. Копіюйте лише медіа; блокуйте виконання PHP; скануйте на
.php,.phtml,.pharі несподівані «зображення» з PHP. - Додавайте плагіни і теми з надійних джерел, а не зі старої файлової системи.
Перевірте перед відкриттям доступу
Верифікація багатошарова:
- Контрольні суми ядра проходять перевірку.
- Немає PHP у uploads (і сервер все одно блокує його).
- Немає несподіваних адміністраторів.
- Логи після відкриття показують нормальні патерни трафіку.
- Вихідні з’єднання очікувані й мінімальні.
Короткий жарт №2: «Ми просто повернемо онлайн і уважно спостерігатимемо» — це у світі реагування на інциденти еквівалент «Почну їсти здорово з понеділка».
Фаза 6: підсилення, що витримує контакт з людьми
Підсилення — це не список best-practices, який ви копіюєте в таск і ігноруєте. Це мінімум запобіжних рейок, які зменшують масштаб наступного інциденту.
Права файлів і модель деплою
- Зробіть ядро WordPress доступним лише для читання для веб-користувача. Оновлення мають бути дією деплою, а не ефектом виконання в рантаймі.
- Uploads повинні бути записуваними, але не виконуваними. Забезпечте це на рівні вебсервера і, якщо можливо, через опції монтування файлової системи.
- Віддавайте перевагу іммутабельним деплоям (побудова артефактів, деплой, не редагуйте в проді). Якщо це занадто великий крок, хоча б обмежте права запису.
Гігієна облікових даних (неприваблива, але важлива)
- Увімкніть MFA для адміністраторів WordPress і акаунтів хостингу.
- Обертайте salts/keys WordPress після інциденту; скидайте сесії.
- Використовуйте унікальні паролі і менеджер; не надсилайте креденшіали електронкою.
- Вимикайте невикористані акаунти і видаляйте тимчасових «вендорських» адміністраторів.
Принцип найменших привілеїв для доступу до бази даних
Користувач бази WordPress зазвичай не потребує глобальних привілеїв. Він потребує доступу тільки до власної бази даних. Якщо атакувальник отримує дані БД, обмежуйте радіус ураження.
Логування, яке допомагає, а не просто існує
- Зберігайте веб-логи достатньо довго, щоб покрити ваш інтервал виявлення. Якщо ви помічали проблему через 10 днів, 2 дні логів — це перформанс-арт.
- Централізуйте логи поза хостом, якщо можете. Атакувальники видаляють локальні логи при “прибиранні”.
- Налаштуйте оповіщення про дивні сплески: спроби логіну, звернення до xmlrpc, POST на uploads, раптове створення адміністраторів.
Резервні копії: проектуйте як на випадок потреби
Резервні копії повинні бути імутабельними (або принаймні доступ-контрольованими), протестованими і відокремленими від скомпрометованого середовища. Найкраща резервна копія — та, яку атакувальник не може зашифрувати або видалити.
Чеклісти / покроковий план
Ці чеклісти написані для людей, які мають робити це під тиском. Роздрукуйте, якщо ви старої школи. Скопіюйте в канал інцидентів, якщо ви сучасні.
Чекліст A: перша година (локалізація + збереження)
- Відкрийте канал інциденту. Призначте лідера інциденту. Одразу одна людина приймає рішення.
- Локалізуйте: сторінка обслуговування / блок WAF / обмеження wp-admin за IP.
- Заблокуйте вихідні з’єднання з хоста, якщо можливо (тимчасово).
- Збережіть: скопіюйте логи поза хост (веб, auth, PHP), зафіксуйте список процесів і мережеві з’єднання.
- Зробіть знімок: снапшот файлової системи і дамп БД (запис тільки для читання, якщо можливо).
- Підтвердіть область ураження: один сайт, один хост чи кілька орендарів?
Чекліст B: той самий день (триаж + план ліквідації)
- Запустіть перевірку контрольних сум ядра і скан на підозрілі файли/функції.
- Перерахуйте адміністраторів, cron-події, mu-plugins.
- Прийміть рішення: перебудова хоста або очищення на місці (за замовчуванням — перебудова, якщо підозрюється компроміс хоста).
- Знайдіть останню відому добру резервну копію (перевірте, що вона не заражена).
- Обертайте облікові дані: хостинг, SSH-ключі, користувачі БД, адміністратори WordPress, API-ключі плагінів.
- План комунікацій: хто має знати (юристи, безпека, клієнти) залежно від ризику витоку даних.
Чекліст C: відновлення й валідація (перед повторним відкриттям)
- Запустіть чисте середовище з запатченою ОС і мінімумом сервісів.
- Встановіть чисте ядро WordPress; встановлюйте плагіни/теми лише з надійних джерел.
- Відновіть БД і uploads обережно; забезпечте «неможливість виконання PHP у uploads» на вебсервері.
- Перевірте: контрольні суми пройшли; нема підозрілих адміністраторів; нема дивних cron-хуків; логи виглядають нормально.
- Відкривайте доступ поступово, якщо можливо; спостерігайте логи і вихідні з’єднання.
- Після інциденту: документуйте кореневу причину (або найкращу гіпотезу), додайте моніторинг, заплануйте патчинг.
Типові помилки: симптом → корінна причина → виправлення
Тут ми перестаємо робити вигляд, що в усіх ідеальний процес. Це передбачувані помилки.
1) Симптом: «Ми видалили шкідливий файл, але він повернувся.»
Корінна причина: стійкість через mu-plugin, cron-подію, підозрілий адміністратор або компроміс хоста, що перезаписує файли.
Виправлення: Перевірте mu-plugins (Завдання 11), cron (Завдання 10), адмінів (Завдання 9). Якщо хост показує підозрілі SSH або процеси (Завдання 15/6), перебудуйте.
2) Симптом: «Головна сторінка в порядку, але Google показує спам-сторінки.»
Корінна причина: умовний SEO-спам, що віддається ботам, або ін’єкція в пости/опції; інколи видно лише для певних user-agent.
Виправлення: Запитуйте БД на інжект-скрипти (Завдання 14), інспектуйте файл functions.php теми й шукайте логіку клоакінгу. Після очищення почистіть кеші.
3) Симптом: «Користувачі скаржаться на випадкові редиректи.»
Корінна причина: інжектований JavaScript у контент, скомпрометований плагін, що інжектує заголовки, або змінений .htaccess/nginx-конфіг.
Виправлення: Порівняйте конфіг вебсервера, інспектуйте БД контенту й замініть плагіни/теми. Перевірте опції home/siteurl (Завдання 13).
4) Симптом: «wp-admin повільний і CPU завантажений.»
Корінна причина: шкідливі cron-завдання, відправка спаму, brute-force або майнінг через PHP.
Виправлення: Інспектуйте процеси (Завдання 6), з’єднання (Завдання 7) і логи на предмет сплесків (Завдання 4). Локалізуйте й перебудовуйте, якщо потрібно.
5) Симптом: «Ми відновили з резервної копії і хак повернувся одразу.»
Корінна причина: резервна копія вже була скомпрометована або креденшіали залишилися скомпрометованими, і атакувальник повернувся.
Виправлення: Перевірте резервні копії (Завдання 16), обертайте всі секрети, увімкніть MFA і закрийте початковий вектор перед відновленням.
6) Симптом: «Плагін безпеки каже чисто, але браузери все ще попереджають.»
Корінна причина: сканер пропустив обфускацію, або шкідливий контент віддається умовно, або залишився кеш із шкідливими активами.
Виправлення: Ручна перевірка: контрольні суми ядра (Завдання 8), сканування на шаблони обфускації (Завдання 12), інспекція логів, очищення кешів і перевірка з чистого клієнта.
Три корпоративні історії з поля бою
Міні-історія 1: інцидент через хибне припущення
У середній компанії з моделлю «маркетинг керує WordPress» команда припустила, що компроміс обмежується WordPress, бо перший симптом — SEO-спам. Сайт був за CDN, тож вони увімкнули сторінку обслуговування і сказали керівництву: «Відновимо з резервної копії.»
Вони відновили файли й БД на тому ж VM. Спам зник. Приблизно на день. Потім редиректи повернулися, але тільки для мобільних user-agent і в певних країнах. Команда звинуватила кешування і витратила години на очищення шарів, які не мали значення.
Ключова підказка була нудною: вихідні з’єднання з PHP до кількох IP на порт 443, що зберігалися навіть у режимі обслуговування. Швидкий список процесів показав PHP-процес, що працює з файлу у uploads. Це мало бути неможливо — тільки їхня Nginx-конфіг не блокувала виконання PHP у uploads, і PHP-FPM маршрутизація через try_files дозволяла все це виконувати.
Хибне припущення було: «Якщо ми відновимо WordPress, ми чисті.» Вони не довели чистоту хоста і не обернули секрети. Атакувальник мав адмін-сесію і веб-оболонку. Команда грала в шашки проти супротивника, який міг повертатися коли забажає.
Вони перебудували VM, відновили з валідованої резервної копії та примусово скинули паролі для привілейованих користувачів. Інцидент завершився. Найважливіший запис у постмортемі був культурний: перестати сприймати WordPress як щось окреме від «реальної інфраструктури». Він працює на сервері. Сервери бувають скомпрометовані.
Міні-історія 2: оптимізація, що зіграла проти
Велика організація вирішила «оптимізувати деплои», дозволивши WordPress автооновлювати плагіни і теми прямо в продакшені. Мотив був чесний: менше тикетів, швидші патчі безпеки, менше рутини. Проблема була в імплементації: вони зробили весь деревір WordPress записуваним для веб-користувача, щоб оновлення не падали.
Через шість місяців вразливий кінцевий пункт плагіна дав атакувальнику доступ. Оскільки файлова система була записуваною, атакувальнику не треба було приховувати сліди. Він модифікував файли ядра, кинув mu-plugin і посадив бекдор у файлі теми, що маскувався під аналітику. Пейлоуд був невеликий і умовний.
Виявлення затрималося, бо сайт в основному працював. Атакувальник показував спам лише ботам і використовував сервер для розсилки пакетних листів через скомпрометований PHP mailer. Першою реальною тривогою стала доставляння — їхні транзакційні листи почали потрапляти в спам через падіння репутації IP.
Коли вони реагували, очищення постійно зазнавали фіаско, бо автооновлення повторно додавали плагінові файли і маскували дрейф. Система постійно змінювалася, тож команда не могла відрізнити, які зміни — зловмисні, а які — «корисна автоматизація».
Виправлення не було «ніколи оновлювати». Виправлення — оновлювати через контрольований деплой і тримати рантайм здебільшого лише для читання. Вони й надалі отримували швидкі патчі, але атакувальник втратив можливість переписувати застосунок на свій розсуд.
Міні-історія 3: нудна, але правильна практика, що врятувала день
Компанія з кількома WordPress-властивостями зробила дещо нудне: централізоване логування і імутабельні резервні копії. Не ідеально, не яскраво, просто послідовно. Веб-логи відправлялися поза хостом, дампи БД були «write-once» на період зберігання, а відновлення практикували щоквартально, бо хтось в опс був упертий у кращому сенсі.
Коли один сайт почав редиректити, вони швидко локалізували на CDN. Потім витягли логи з централізованого сховища і виявили вузьке вікно: сплеск POST до кінцевої точки плагіна, потім створення нового адміністратора, потім звернення до PHP-файлу у uploads. Чисто, лінійно, огидно.
Команда не сперечалася, чи «справді компрометовано». У них були докази. Вони перебудували хост, скориставшись існуючим пайплайном автоматизації. Вони відновили з резервної копії, зробленої до першого exploit-запиту. Обернули секрети і вимкнули XML-RPC, бо сайту він не потрібен. Загальний час простою вимірювався годинами, а не днями.
Пізніше керівництво поставило звичайне питання: «Як ви так швидко перемістилися?» Відповідь не була героїчною. Це була нудна практика логів, які пережили атакувальника, і резервних копій, які не можна було тихо перезаписати.
Це не було гламурно. Це було правильно. А це найвища похвала в опсах.
FAQ
1) Чи варто негайно взяти сайт офлайн?
Якщо сайт активно розповсюджує шкідливий код, робить редиректи або витікають дані: так. Локалізуйте на краю, якщо можливо, щоб не знищити докази на хості.
2) Чи можна просто перевстановити ядро WordPress і все?
Перевстановлення ядра допомагає, але рідко достатньо. Компроміси часто включають підозрілі адміністраторські акаунти, cron-стойкість, модифіковані плагіни/теми і ін’єкції в базу. Перевіряйте всі шари.
3) Чи виправляють плагіни безпеки зламаний сайт?
Вони можуть виявляти і іноді видаляти відомі шаблони. Вони також можуть дати хибне відчуття безпеки. Використовуйте їх як вхідні дані, а не як вирок. Вам потрібні логи, контрольні суми і жорсткі права доступу.
4) Як зрозуміти, чи сам сервер скомпрометовано?
Індикатори: несподівані SSH-входи, невідомі процеси, підозрілі вихідні з’єднання, нові системні користувачі або зміни поза директорією WordPress. Якщо підозрюєте — перебудуйте.
5) Який найпоширеніший механізм стійкості в компромісах WordPress?
Підозрілі адміністраторські акаунти і PHP-бекдори у записуваних директоріях (uploads, кеш, mu-plugins). Атакувальникам подобається стійкість, що переживає зміну тем.
6) Чи безпечні резервні копії для відновлення?
Тільки якщо ви їх валідували. Перевірте на підозрілі артефакти і підтвердіть, що резервна копія зроблена до компромісу. Також обертайте креденшіали, інакше вас можуть знову скомпрометувати одразу.
7) Чи варто обертати креденшіали БД і salts WordPress?
Так. Після компромісу вважайте, що паролі і куки могли бути вкрадені. Обертайте креденшіали БД, згенеруйте нові salts WordPress і примусово перевірте автентифікацію користувачів — принаймні адміністраторів.
8) Чому хак «показується» лише Google або в певних країнах?
Клоакінг. Атакувальники віддають чистий контент вам і шкідливий — ботам або певним user-agent, щоб уникнути виявлення. Ось чому ви використовуєте логи і перевірку цілісності, а не лише браузерний тест.
9) Якщо я перебудую хост, чи потрібна судова експертиза?
Дещо так. Ви перебудовуєте, щоб отримати чистоту. Судова експертиза потрібна, щоб уникнути повторення і оцінити витік. Мінімум: ідентифікуйте вхідну точку, часовий інтервал і які дані могли бути задіяні.
10) Який найшвидший безпечний шлях для малого бізнесу без SRE?
Локалізуйте (сторінка обслуговування), отримайте чистий хост (керований WordPress або новий VM), відновіть з останньої відомої доброї резервної копії, замініть плагіни/теми з надійних джерел, обертайте креденшіали і додайте MFA.
Висновок: що робити наступного тижня, а не лише сьогодні
Сьогодні ваша задача — локалізація, збереження доказів і чистий план відновлення. Будьте рішучими: якщо хост виглядає як «власність» атакувальника — перебудуйте. Якщо резервні копії інфіковані — припиніть вдавати, що маєте план відновлення, і почніть будувати чистий.
Наступного тижня зробіть роботу, що запобіжить сиквелу:
- Зробіть ядро WordPress доступним лише для читання під час рантайму; блокуйте виконання PHP у uploads.
- Скоротіть кількість плагінів/тем; дотримуйтеся ритму патчів, який ви реально можете підтримувати.
- Централізуйте логи поза хостом; зберігайте їх достатньо довго, щоб покрити інтервал виявлення.
- Тестуйте відновлення; зберігайте резервні копії імутабельними; документуйте критерії «остання відома добра» копія.
- Увімкніть MFA і принцип найменших привілеїв скрізь: WordPress, хостинг, SSH, база даних.
Якщо зробите правильно, наступний момент «WordPress зламано» стане контрольованою неприємністю, а не тижневим кризою з репутаційними збитками. Це — планка.