Як знайти й вичистити PHP-webshell зі зламаного сервера

Інше 4 лип. 2026 р.
 Як знайти й вичистити PHP-webshell зі зламаного сервера

Одного разу ви відкриваєте логи Apache — і бачите звернення до файлу, якого не завантажували: щось на кшталт /images/logo.php.jpg або wp-content/uploads/x.php. Або хостер надсилає скаргу на розсилку спаму з вашого IP. Або сайт раптом віддає ліві редиректи тільки пошуковим ботам. Усе це — симптоми одного діагнозу: на сервері оселився PHP-webshell.

Ця стаття — практичний польовий посібник із того, як знайти й вичистити веб-шели зі зламаного сервера та закрити діру, крізь яку вони потрапили. Матеріал суто оборонний: жодного коду самих шелів тут немає — лише те, що потрібно захиснику.

Що таке webshell і чому його важко знайти

Webshell — це PHP-файл (рідше вставка в чужий файл), який дає атакувальнику виконувати команди на сервері через звичайний HTTP-запит. Функціонал буває від примітивного «виконай одну команду» до повноцінних панелей із файловим менеджером, брутфорсером баз і масовим інфікуванням.

Складність у тому, що шели маскуються:

  • ховаються в каталогах завантажень (uploads, cache, tmp), де багато легітимних файлів;
  • називаються як системні (wp-config-sample.php, index_old.php, .class.php);
  • використовують подвійні розширення (photo.php.jpg) та псевдокартинки з PHP-кодом усередині;
  • кодують корисне навантаження в base64, gzinflate, hex або через конкатенацію рядків, щоб не «світити» небезпечні функції;
  • дописуються в кінець легітимних файлів теми чи плагіна — тоді видалення файлу зламає сайт.

Крок 0: не панікуйте, зафіксуйте стан

Перш ніж щось видаляти — зробіть повний бекап у поточному стані (файли + база). Так, разом із малварню. Це ваш матеріал для розслідування: за таймстемпами файлів і логами ви відновите, коли й через що стався злам. Якщо видалити все відразу, ви приберете симптоми, але не причину — і шел повернеться за кілька днів.

Якщо є можливість — тимчасово закрийте сайт (сторінка-заглушка або обмеження по IP), щоб зупинити активну шкідливу діяльність, поки чистите.

Крок 1: пошук за підозрілими функціями

Більшість шелів десь мусять викликати функцію виконання коду або команд. Шукаємо ці виклики по всьому дереву сайту:

grep -rEl --include='*.php' \
  'eval\(|assert\(|system\(|passthru\(|shell_exec\(|popen\(|proc_open\(|base64_decode\(|gzinflate\(|str_rot13\(' \
  /var/www/site/

Ключі: -r рекурсивно, -E розширені регулярки, -l вивести лише імена файлів, --include='*.php' — тільки PHP.

Ці функції є й у легітимному коді (наприклад, base64_decode у бібліотеках), тому список — це кандидати на перевірку, а не автоматичний вирок. Найпідозріліше — коли в одному короткому файлі поєднуються декодування і виконання, та ще й приймається вхід із $_POST/$_GET/$_REQUEST/$_COOKIE:

grep -rEl --include='*.php' \
  '(eval|assert|system|passthru|shell_exec).{0,40}\$_(POST|GET|REQUEST|COOKIE|SERVER)' \
  /var/www/site/

Крок 2: пошук замаскованого коду

Обфусковані шели видають себе довгими беззмістовними рядками та характерними конструкціями. Кілька корисних сигнатур:

# Довгі base64-рядки (60+ символів поспіль)
grep -rEl --include='*.php' '[A-Za-z0-9+/]{60,}={0,2}' /var/www/site/

# Виконання через змінні-функції: $a($b) — улюблений прийом
grep -rEl --include='*.php' '\$[a-zA-Z_]+\s*\(\s*\$' /var/www/site/

# preg_replace зі старим модифікатором /e (виконує код)
grep -rEl --include='*.php' "preg_replace\s*\(.*/e" /var/www/site/

# Виклик через масив символів чи \x-екранування
grep -rEl --include='*.php' '\\x[0-9a-fA-F]{2}\\x[0-9a-fA-F]{2}' /var/www/site/

Крок 3: пошук за часом та невідповідністю розширень

Це найпотужніший метод. Атака сталася в конкретний час, і файли шелів мають свіжіші дати, ніж решта движка.

# PHP-файли, змінені за останні 7 днів
find /var/www/site/ -name '*.php' -mtime -7 -ls

# PHP-код там, де його бути не повинно — у каталозі завантажень
find /var/www/site/wp-content/uploads/ \
  -type f \( -name '*.php' -o -name '*.phtml' -o -name '*.php[0-9]' \) -ls

Останній рядок особливо важливий: у uploads/ ніколи не повинно бути виконуваного PHP. Будь-який .php тут — майже гарантовано шел.

Окремо ловимо файли з подвійним розширенням та псевдокартинки з PHP-тегом усередині:

# «Картинки», які насправді містять PHP
grep -rl '<?php' /var/www/site/wp-content/uploads/

# Подвійні розширення
find /var/www/site/ -regextype posix-extended \
  -regex '.*\.(php|phtml)\.(jpg|jpeg|png|gif|ico|txt)$' -ls

Крок 4: звірка з еталоном

Якщо у вас WordPress, Joomla, MODX чи інша відома CMS — найнадійніший спосіб знайти дописані шели (ін'єкції в легітимні файли ядра) — це порівняння з чистим дистрибутивом.

# Приклад для WordPress: перевірка контрольних сум ядра
wp core verify-checksums --allow-root

# Для плагінів і тем
wp plugin verify-checksums --all --allow-root

WP-CLI звірить кожен файл ядра з офіційними хешами й покаже змінені. Те саме вручну для будь-якої CMS: завантажте ту саму версію движка й порівняйте через diff -r:

diff -rq /var/www/site/ /tmp/clean-cms/ | grep -i 'differ\|only in /var/www'

Крок 5: аналіз логів — знайти точку входу

Логи Apache/Nginx показують, як атакувальник звертався до шела і, головне, як він потрапив на сервер. Шукаємо POST-запити до підозрілих файлів:

# Звернення до знайденого шела
grep 'shell-name.php' /var/log/apache2/access.log*

# Усі POST-запити до .php у каталозі завантажень
grep -E 'POST .*(uploads|cache|tmp).*\.php' /var/log/apache2/access.log*

# Найактивніші IP за POST-запитами
awk '$6 ~ /POST/ {print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -rn | head

За таймстемпом першого звернення до шела перегляньте, який запит йому передував — часто це вразливий плагін, форма завантаження без перевірки типу файлу або експлойт відомої CVE.

Крок 6: чистка

Тепер, коли список файлів зібрано, діємо за принципом:

  • Окремі файли-шели (яких немає в чистому дистрибутиві) — видаляємо повністю.
  • Ін'єкції в легітимні файли — не видаляємо файл, а замінюємо його чистою версією з дистрибутиву, або акуратно вирізаємо вставлений блок (зазвичай він на початку файлу до <?php ядра або в самому кінці).
  • База даних — WordPress-малварь часто сидить у таблиці wp_options (ключі на кшталт ін'єкцій у siteurl), у wp_posts чи в активних плагінах mu-plugins. Перевірте й базу теж.

Після кожного видалення — повторюйте пошук із кроків 1–3, бо шели часто відновлюють один одного (один «сплячий» файл перестворює решту).

Крок 7: обов'язкова зміна всіх секретів

Атакувальник, який мав доступ до файлів, майже напевно прочитав wp-config.php / configuration.php з паролями бази. Після чистки міняємо все:

  • паролі всіх користувачів БД і паролі бази;
  • секретні ключі та солі (AUTH_KEY тощо у WordPress) — це розлогінить усіх, включно з атакувальником;
  • паролі FTP/SSH/панелі хостингу;
  • паролі адміністраторів CMS; перевірте, чи не з'явилися нові адмін-акаунти.

Крок 8: закрити двері назавжди

Чистка без харденінгу — марна праця: за тиждень усе повернеться. Мінімальний набір заходів на рівні Apache.

Заборонити виконання PHP у каталогах завантажень. У .htaccess всередині uploads/ (або, краще, у конфізі віртуалхоста):

<FilesMatch "\.(?i:php|phtml|php[0-9]|phar)$">
    Require all denied
</FilesMatch>

Заблокувати double-extension атаки. Apache за замовчуванням може виконати file.php.jpg як PHP через застарілу директиву AddHandler. Правильно віддавати на PHP тільки файли, що закінчуються на .php:

<FilesMatch "\.php$">
    SetHandler application/x-httpd-php
</FilesMatch>

# І прямо заборонити «php десь посередині імені»
<FilesMatch "\.ph(p[0-9]?|tml)\.">
    Require all denied
</FilesMatch>

Обмежити небезпечні функції PHP. У php.ini (або пул-конфізі PHP-FPM), якщо додаток їх не потребує:

disable_functions = exec,passthru,shell_exec,system,proc_open,popen,curl_exec,parse_ini_file,show_source

Обережно: спершу переконайтеся, що ваш движок реально не використовує ці функції — інакше щось відвалиться.

Оновити все. CMS, плагіни, теми, PHP. Більшість зламів — через відому вразливість у застарілому плагіні. Приберіть невикористовувані плагіни й теми зовсім: неактивна, але наявна тема — теж вектор атаки.

Чек-лист після інциденту

  • [ ] зроблено бекап «як є» для розслідування;
  • [ ] знайдено та видалено всі файли-шели (пошук повторено до нульового результату);
  • [ ] знайдено та почищено ін'єкції в легітимних файлах і базі;
  • [ ] визначено точку входу за логами й усунено вразливість;
  • [ ] змінено всі паролі й секретні ключі;
  • [ ] перевірено список адмін-акаунтів і cron-завдань;
  • [ ] застосовано харденінг Apache/PHP;
  • [ ] оновлено CMS та всі компоненти.

Висновок

Очищення зламаного сервера — це не «видалити підозрілий файл», а методичний процес: зафіксувати стан, знайти всі шели за функціями, обфускацією та часом, звірити з еталоном, за логами вирахувати точку входу, почистити, змінити секрети й закрити діру. Пропустите останні два кроки — і повторний злам стане питанням днів.

Найкраща стратегія, звісно, — не доводити до цього: заборона виконання PHP у uploads/, коректна обробка double-extension, регулярні оновлення й позаофлайнові бекапи. У наступній статті детально розберемо конфігурацію Apache для захисту від завантаження та виконання шкідливих файлів.