secaudit.eu
Incydenty

Przekierowania spamowe w WordPress: jak je znaleźć i usunąć

Przekierowania spamowe w WordPress: odtwórz warunkowe przekierowanie w curl i DevTools, znajdź je w .htaccess, PHP, bazie danych lub CDN i nie dopuść do ich powrotu.

Autorka: , AngelForce OÜ 7 min czytania

Także po: English Deutsch Français Español Italiano Português Türkçe

Przekierowanie spamowe w WordPress jest zaprojektowane tak, żeby właściciel go nie zobaczył. Złośliwy kod sprawdza, kto odwiedza stronę, i przekierowuje tylko wybranych: użytkowników telefonów, osoby przychodzące z Google lub mediów społecznościowych, a często każdego odwiedzającego tylko raz. Zazwyczaj celowo pomija zalogowanych użytkowników i administratorów.

Jeśli więc klient pisze: „Państwa strona wysłała mnie na fałszywą stronę z nagrodą", a u Państwa na laptopie wszystko wygląda normalnie, trzeba uwierzyć klientowi. Celem są zwykle strony z oszustwami, fałszywe konkursy, agresywne sieci reklamowe lub pliki z malware. Google może oznaczyć witrynę jako niebezpieczną, a odwiedzający przestają jej ufać. Przekierowania to tylko jeden z kilku objawów. Pozostałe opisujemy w artykule 12 oznak włamania na stronę WordPress.

Dlaczego właściciel nigdy nie widzi przekierowania spamowego w WordPress

Warunki są proste i skuteczne. Kod odczytuje nagłówek User-Agent, żeby rozpoznać telefony, oraz nagłówek Referer, żeby rozpoznać ruch z wyszukiwarki. Sprawdza też, czy w przeglądarce jest ciasteczko logowania do WordPressa. Po pierwszym przekierowaniu ustawia własne ciasteczko albo zapisuje adres IP, żeby tej samej osoby nie przekierować ponownie.

Ta kombinacja tłumaczy większość zgłoszeń typu „nie mogę tego odtworzyć". Są Państwo zalogowani, siedzą przy komputerze, wpisują adres ręcznie, a być może przekierowanie już raz się wykonało. Wystarczy jeden z tych warunków, by przekierowanie się nie uruchomiło.

Schemat: warstwy, w których może ukrywać się spamowe przekierowanie, od pamięci podręcznej CDN po JavaScript wstrzyknięty w przeglądarce
Żądanie przechodzi przez kilka warstw. W każdej może siedzieć przekierowanie, a typ odpowiedzi podpowiada, którą część stosu przeszukać.

Bezpieczne odtworzenie przekierowania

Zanim zaczną Państwo przeszukiwać pliki, trzeba ustalić, z jakim rodzajem przekierowania mamy do czynienia. Typ odpowiedzi wskazuje, którą warstwę sprawdzać.

Zacznijmy od przeglądarki. Otwórzą Państwo okno prywatne i upewnią się, że są wylogowani. Następnie uruchomią narzędzia deweloperskie (F12 w Chrome, Edge i Firefoksie) i włączą pasek urządzeń (Ctrl+Shift+M), aby emulować telefon. Na karcie Network zaznaczą Państwo Preserve log (w Firefoksie: Persist Logs w menu ustawień karty Network), żeby wpisy nie zniknęły po zmianie strony. Aby uzyskać prawdziwy referrer z Google, wyszukają Państwo własną domenę w Google i klikną wynik.

Teraz proszę spojrzeć na pierwszy wpis na karcie Network:

  • **Status 301, 302, 307 lub 308 z obcym adresem w Location**: przekierowanie odbywa się po stronie serwera. Dotyczy to CDN, reguł serwera WWW, PHP i samego WordPressa.
  • Status 200, a potem żądanie do obcej domeny: strona załadowała się normalnie, po czym JavaScript (lub znacznik meta refresh) wysłał przeglądarkę gdzie indziej. Szukamy wstrzykniętego skryptu.

Wynik potwierdzimy za pomocą curl. Curl nie wykonuje JavaScriptu i nie podąża za przekierowaniami, dopóki mu tego nie każemy. Poniższe polecenie wysyła zwykłe żądanie GET i wypisuje tylko nagłówki odpowiedzi; proszę unikać curl -I, bo część wstrzykniętego kodu ignoruje żądania HEAD:

curl -s -o /dev/null -D - \
  -A "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Mobile/15E148 Safari/604.1" \
  -e https://www.google.com/ \
  https://example.com/

Linia Location: wskazująca nieznaną domenę potwierdza przekierowanie po stronie serwera. Jeśli w linii statusu widać 200, pobierzemy treść strony i przeszukamy ją pod kątem kodu przekierowującego i zaciemniania:

curl -s -A "Mozilla/5.0 (iPhone; CPU iPhone OS 17_5 like Mac OS X)" \
  -e https://www.google.com/ https://example.com/ \
  | grep -Eio 'window\.location|location\.href|location\.replace|http-equiv=.?refresh|atob\(|eval\(|fromCharCode|script[^>]+src="[^"]+"'

Test proszę powtórzyć bez -A i -e i porównać wyniki. Potem sprawdzić kilka adresów URL, na przykład wpis na blogu, stronę produktu i stronę kontaktową. Wiele infekcji dotyczy tylko niektórych szablonów.

Czysty wynik testu nie dowodzi, że witryna jest czysta. Jeśli kod przekierowuje każdy adres IP tylko raz albo celuje wyłącznie w wybrane kraje, test może niczego nie wykryć. Warto powtórzyć go z innej sieci, na przykład z telefonu na danych komórkowych.

Przekierowania po stronie serwera: .htaccess, konfiguracja serwera i PHP uruchamiany jako pierwszy

Jeśli curl pokazuje kod 3xx do obcej domeny, przeszukujemy serwer oraz kod, który uruchamia się, zanim WordPress cokolwiek wyrenderuje.

Na Apache należy sprawdzić każdy plik .htaccess, nie tylko ten w katalogu głównym witryny. Szukamy warunków przepisywania opartych na user agencie lub referrerze oraz dyrektywy auto_prepend_file. Dyrektywa ta powoduje, że PHP ładuje plik atakującego przed każdym żądaniem:

grep -rn --include=.htaccess -E "HTTP_(USER_AGENT|REFERER)|auto_prepend_file" .
grep -rn --include=.user.ini --include=php.ini "auto_prepend_file" .

Uzasadnione linie RewriteCond %{HTTP_USER_AGENT} istnieją, na przykład w niektórych wtyczkach cache. Reguła, która kieruje ruch mobilny lub z Google do nieznanej domeny, na pewno do nich nie należy.

Następnie wypiszmy pliki PHP i pliki konfiguracyjne zmienione ostatnio. Atakujący potrafią resetować znaczniki czasu plików, więc traktujmy tę listę jako punkt wyjścia, nie dowód. Polecenie uruchamiamy w katalogu głównym WordPressa:

find . -type f \( -name "*.php" -o -name ".htaccess" -o -name ".user.ini" \) -mtime -14 -print

Szczególnie uważnie proszę obejrzeć wszystko, co dopisano na początku lub na końcu wp-config.php, oraz każdy plik w wp-content/mu-plugins. Wtyczki must-use ładują się automatycznie i nie pojawiają się na zwykłej liście wtyczek. Sprawdźmy też plik functions.php aktywnego motywu. Potem uruchamiamy wp core verify-checksums, aby wykryć zmodyfikowane pliki rdzenia. Jak czytać wynik tego polecenia i bezpiecznie przeinstalować rdzeń, opisujemy w poradniku o weryfikacji sum kontrolnych WordPressa.

Na nginx reguły znajdują się w konfiguracji serwera, nie w .htaccess. W hostingu zarządzanym możecie Państwo nie mieć do nich wglądu. W obu przypadkach warto poprosić dostawcę hostingu o sprawdzenie reguł przekierowań i ustawień prepend na poziomie serwera.

Opcje siteurl i home

Inny, bardziej prymitywny wariant zmienia opcje siteurl i home. WordPress przekierowuje wtedy wszystkich odwiedzających do domeny atakującego, łącznie z Państwem. Sprawdzamy obie wartości:

wp option get siteurl
wp option get home

Bez WP-CLI otwieramy phpMyAdmin i zaglądamy do tabeli wp_options. Prefiks tabel może różnić się od wp_, więc proszę sprawdzić $table_prefix w wp-config.php. Aby naprawić wartości:

wp option update siteurl 'https://example.com'
wp option update home 'https://example.com'

Warto też uruchomić grep -n "WP_HOME\|WP_SITEURL" wp-config.php. Jeśli te stałe są zdefiniowane, nadpisują wartości z bazy danych. Atakujący może je tam podrzucić, a wtedy poprawka w bazie będzie wyglądać na nieskuteczną.

Przekierowania JavaScript we wpisach, widżetach, plikach motywu i skryptach w nagłówku

Odpowiedź 200, po której następuje skok do innej domeny, oznacza, że w stronie działa skrypt. Może pochodzić z kilku miejsc:

  • Treść wpisów: wstrzyknięte znaczniki script w post_content, często w kilkudziesięciu wpisach naraz.
  • Opcje: wiersze widżetów (widget_*), opcje motywu i ustawienia page buildera w wp_options.
  • Ustawienia „Insert headers and footers": wtyczki do wstawiania fragmentów kodu i page buildery przechowują tam skrypty dla całej witryny. Atakującemu z loginem administratora wystarczy wkleić jedną linię.
  • Pliki motywu i wtyczek: header.php, footer.php oraz dołączane pliki JavaScript.

Szybkie przeszukanie bazy danych (proszę zmienić wp_posts, jeśli prefiks tabel jest inny):

wp db query "SELECT ID, post_title FROM wp_posts WHERE post_content LIKE '%script%src=%';"
wp db search 'fromCharCode' --all-tables
wp option list --search='widget_*' --fields=option_name

Należy spodziewać się zaciemnionego kodu. Wstrzyknięty kod rzadko zawiera docelowy adres URL wprost. Szukamy atob(, długich ciągów base64, String.fromCharCode oraz źródeł skryptów, które udają znane domeny CDN lub analityczne, ale mają nieco inną pisownię. Technikę przeszukiwania bazy i sposób oczyszczania omawiamy dokładniej w artykule o spamie SEO w WordPress.

Kopie w pamięci podręcznej: wtyczki cache, cache serwera i CDN

Można całkowicie oczyścić serwer źródłowy, a odwiedzający nadal będą przekierowywani, bo zainfekowana strona wciąż leży gdzieś zapisana. Kopie mogą przetrwać w plikach cache stron (wp-content/cache), w pamięci podręcznej obiektów, takiej jak Redis lub Memcached, w cache serwera u dostawcy hostingu oraz na serwerach brzegowych CDN.

Czyścimy w kolejności żądania, od serwera źródłowego na zewnątrz:

  1. Kończymy czyszczenie plików i bazy danych na serwerze źródłowym.
  2. Czyścimy cache stron we wtyczce cache.
  3. Opróżniamy pamięć podręczną obiektów poleceniem wp cache flush.
  4. Czyścimy cache serwera w panelu hostingu.
  5. Czyścimy wszystko w CDN.
  6. Ponownie testujemy powyższymi poleceniami curl, z mobilnym user agentem i referrerem Google oraz bez nich.

Trzeba też wziąć pod uwagę przypadek odwrotny: ktoś ustawił przekierowanie w CDN lub na poziomie DNS. Każdy, kto ma dostęp do konta CDN, może utworzyć regułę przekierowania bez dotykania WordPressa. Sprawdźmy reguły przekierowań i reguły stron w panelu CDN oraz rekordy DNS. Warto też przejrzeć, kto ma dostęp do tych kont, i odebrać uprawnienia osobom, które już ich nie potrzebują.

Znajdź wektor wejścia, inaczej przekierowanie wróci

Przekierowanie to ładunek, nie luka. Jeśli usuną Państwo tylko ładunek, zwykle wróci w ciągu kilku dni, bo droga wejścia nadal jest otwarta. Typowe wektory wejścia to podatne lub pirackie (nulled) wtyczki, przejęte dane logowania administratora lub hostingu, web shelle w wp-content/uploads oraz pozostawione backdoory, które ponownie wstrzykują kod przez zadanie cron lub opcję w bazie danych.

Krótka wstępna kontrola:

  • Nieznani administratorzy: wp user list --role=administrator
  • Hasła aplikacji, których Państwo nie tworzyli (Użytkownicy > Profil)
  • Nieznane zaplanowane zdarzenia: wp cron event list
  • Pliki PHP w katalogu uploads: find wp-content/uploads -name "*.php"
  • Nieaktualne lub pirackie wtyczki i motywy

Gdy luka zostanie zamknięta, zmieniamy hasła do panelu administracyjnego, hostingu, bazy danych, SFTP i CDN. Następnie rotujemy sole poleceniem wp config shuffle-salts. To wyloguje wszystkich użytkowników. Pełny plan usuwania złośliwego oprogramowania z WordPressa obejmuje zabezpieczenie dowodów oraz decyzję, czy czyścić, czy przywracać z kopii.

Atakujący mógł też mieć możliwość odczytu danych klientów, zgłoszeń z formularzy kontaktowych lub zamówień. W takim przypadku incydent należy ocenić jako możliwe naruszenie ochrony danych osobowych. Zob. zgłoszenie naruszenia w ciągu 72 godzin według RODO.

FAQ

Dlaczego przekierowywani są tylko użytkownicy mobilni?

Osoby korzystające z telefonów rzadziej zauważają dziwny adres w pasku przeglądarki i chętniej klikają w strony z oszustwami. Właściciele zwykle sprawdzają też swoją witrynę na komputerze, będąc zalogowanymi. Warunek „tylko mobilnie" ma ukryć przekierowanie przed Państwem.

Google wyświetla komunikat „Ta witryna mogła zostać zhakowana". Co dalej?

Najpierw trzeba oczyścić witrynę, zamknąć wektor wejścia i wyczyścić wszystkie pamięci podręczne. Dopiero potem należy poprosić o ponowne sprawdzenie w Google Search Console. Procedurę opisujemy w artykule o spamie SEO w WordPress. Jeśli wniosek o weryfikację zostanie złożony, zanim przekierowanie naprawdę zniknie, zwykle zostaje odrzucony.

Czy wtyczka bezpieczeństwa może usunąć przekierowanie za mnie?

Skanery pomagają je znaleźć. Nie zastępują jednak ręcznego czyszczenia ani poszukiwania wektora wejścia. Wtyczka audytowa secaudit.eu porównuje integralność plików ze stanem bazowym i zgłasza wskaźniki włamania, takie jak ukryci administratorzy, podejrzane zdarzenia cron i spam w bazie danych. Skanuje też w poszukiwaniu ukrytego spamu SEO. Wykrywa i raportuje. Usunięcie kodu i zamknięcie luki należy do Państwa lub do wynajętego specjalisty.

Sprawdź swoją stronę

Security Audit & Compliance to wtyczka WordPress od secaudit.eu: wyszukuje złośliwe oprogramowanie, ukryty spam, zmienione pliki rdzenia i podatny kod oraz przypisuje wyniki do art. 32 RODO, art. 21 NIS2 i OWASP Top 10.

Zobacz, co sprawdza wtyczka
Melike Görel

Melike Görel pisze poradniki secaudit.eu o bezpieczeństwie WordPressa i zgodności z przepisami UE dla AngelForce OÜ — firmy programistycznej z Tallinna, która stoi za secaudit.eu.

Przygotowano z pomocą AI i sprawdzono przed publikacją. Ten artykuł ma charakter ogólnoinformacyjny i nie stanowi porady prawnej.