Як знайти й вичистити 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 для захисту від завантаження та виконання шкідливих файлів.