CleanOps

Gdzie WordPress chowa malware — 9 miejsc, które trzeba sprawdzić, i jak to zrobić

18 października 2026 • 3 min czytania • WordPress, malware, mu-plugins, uploads, diagnostyka

Rozłożone na części mechanizmy ułożone w rzędach na białym tle

WordPress to ponad 40 % stron w internecie, więc atakujący znają jego strukturę lepiej niż większość właścicieli stron. Wiedzą, które katalogi są zapisywalne, które panel pokazuje, a których nie, i gdzie plik PHP będzie wyglądał „normalnie”. Poniżej dziewięć miejsc, które sprawdzamy przy każdej naprawie, w kolejności od najczęstszych.

1. wp-content/uploads/

Katalog na pliki wgrywane przez panel: obrazki, PDF‑y. Ma prawa zapisu, bo musi. Dlatego jest pierwszym miejscem, do którego trafia web‑shell po udanym wykorzystaniu luki we wtyczce. Pliki noszą nazwy jak image.php, thumb.php, .cache.php, czasem logo.jpg.php.

find wp-content/uploads -type f \( -name "*.php" -o -name "*.phtml" -o -name "*.php.*" \)

Poprawna odpowiedź: pusto. Każde trafienie to infekcja.

2. wp-content/mu-plugins/

„Must-use plugins”: wtyczki, które WordPress ładuje zawsze, bez aktywacji, i których nie pokazuje w zwykłej liście wtyczek. Katalogu domyślnie nie ma. Jeśli jest, a Ty go nie tworzyłeś, zajrzyj. Backdoor w mu-plugins przeżywa dezaktywację wszystkich wtyczek i wygląda w panelu jak nic.

3. Wtyczki o nazwach udających systemowe

W wp-content/plugins/ szukaj katalogów, których nie instalowałeś: wp-core-helper, security-update, wordpress-seo-pro (prawdziwa wtyczka nazywa się inaczej), akismet2. Atakujący wybierają nazwy, które właściciel zostawi w spokoju. Porównaj listę katalogów z listą wtyczek w panelu; każdy katalog bez odpowiednika w panelu to kandydat.

4. Motyw: functions.php, header.php, footer.php

Pliki motywu są wykonywane przy każdym żądaniu, więc kod wstrzyknięty tam działa na każdej podstronie. Typowe: kilkadziesiąt linii zaciemnionego kodu doklejonych na początku functions.php, <script> z obcą domeną w header.php tuż przed </head>, include do pliku w uploads/.

Porównaj z oryginałem motywu, jeśli jest w repozytorium WordPressa albo masz paczkę. Dla motywu customowego: porównaj z kopią albo czytaj ręcznie, zaczynając od pierwszych i ostatnich 30 linii każdego pliku.

5. wp-config.php

Plik konfiguracyjny. Atakujący dopisuje na końcu include do swojego pliku albo zmienia WP_SITEURL. Nikt nie ogląda tego pliku po instalacji, więc zmiana przeżywa miesiące.

grep -n "include\|require\|eval\|base64" wp-config.php

Poprawna odpowiedź: tylko standardowe require_once ABSPATH . 'wp-settings.php'.

6. Pliki rdzenia: wp-includes/, wp-admin/

Tu atakujący ma dwie opcje: zmienić istniejący plik (np. doklejony kod w wp-includes/load.php) albo dodać własny o systemowo brzmiącej nazwie (wp-includes/class-wp-cache-helper.php, wp-admin/includes/update-core2.php). Oba przypadki wykrywa porównanie z oficjalnym wydaniem tej samej wersji: zmieniony plik ma inną sumę kontrolną, dodany plik nie występuje w wydaniu.

Wersję sprawdzisz w wp-includes/version.php. Paczka tej wersji jest do pobrania z oficjalnego archiwum wydań.

7. .htaccess w katalogu głównym i podkatalogach

W głównym: auto_prepend_file ładujący plik PHP przed każdym żądaniem, RewriteRule dla telefonów i botów, ErrorDocument kierujący na plik PHP. W podkatalogach (szczególnie uploads/): reguły pozwalające wykonywać PHP tam, gdzie hosting go domyślnie blokuje. Domyślny .htaccess WordPressa to kilkanaście linii między # BEGIN WordPress a # END WordPress; wszystko poza tym wymaga wyjaśnienia.

8. Baza danych

wp_options (siteurl, home, active_plugins, opcje widżetów, klucz cron), wp_posts (skrypt doklejony do treści), wp_users (obce konta). Skaner plików tego nie widzi. Szczegóły i zapytania są w osobnym wpisie o malware w bazie danych.

9. Katalogi, których „nie ma”

Katalogi z kropką (.well-known/, .tmp/), katalogi o nazwach systemowych w katalogu głównym (cgi-bin/, .quarantine/), katalogi w wp-content/upgrade/ i wp-content/cache/. FTP domyślnie ukrywa pliki z kropką; włącz ich pokazywanie. Przez SSH: ls -la i find . -path "*/.*" -name "*.php".

Jak sprawdzić wszystko naraz

Dwie komendy, które pokrywają większość powyższych punktów:

# PHP tam, gdzie nie powinno go być
find . -type f -name "*.php" \( -path "*/uploads/*" -o -path "*/cache/*" -o -path "*/upgrade/*" -o -path "./.*" \)

# Pliki PHP zmienione w ostatnich 30 dniach (jeśli nikt nic nie aktualizował — powinno być pusto)
find . -type f -name "*.php" -mtime -30 -not -path "*/cache/*"

Plus porównanie rdzenia z wydaniem i przegląd bazy. Jeśli strona ma kilkadziesiąt tysięcy plików, ręczne sprawdzanie zajmie dzień. To dokładnie ta część, którą automatyzuje skan w CleanOps: lista miejsc jest ta sama, tylko wykonana w minuty i z werdyktem przy każdym pliku, zanim cokolwiek się zmieni.

Czytaj też