Kilka dni temu otrzymałem zgłoszenie dotyczące problemów z działaniem kilku serwisów zbudowanych na WordPressie. Rzuciłem okiem na dwie z wymienionych stron i już wiedziałem co się dzieje. Obie witryny zostały zainfekowane jakimś syfem, którego usunięcie zwykle kosztuje sporo pracy i nerwów. Po zalogowaniu się do serwera przez SFTP odkryłem, że na jednej wirtualce działa ponad dwadzieścia stron, z których zainfekowanych zostało co najmniej kilkanaście. Sytuacja nie wyglądała ciekawie, tym bardziej, że trzy strony trafiły już na jedną z „czarnych list”, tak więc tylko kwestią czasu było wciągnięcie ich na najistotniejszą chyba listę Google Safe Browsing.
Na usuwaniu śmieci z wszystkich tych stron spędziłem pół dnia, popełniając przy tym kilka błędów. Ten wpis powstał po to, abyście Wy w takiej sytuacji tych błędów nie powielili.
Podstawowe kroki, które dobrze jest wykonać gdy nasza strona zostanie zainfekowana, opisałem w tym wpisie. Oczywiście wykonałem wszystkie te operacje, włącznie ze skanowaniem stron za pomocą skanera od Sucuri. Co ciekawe, na trzech przetestowanych przeze mnie stronach skaner nie wykrył żadnego złośliwego kodu, ale za to poinformował mnie, że jedna z tych stron znajduje się na „czarnej liście” Spamhaus, w związku z czym jest prawdopodobnie zainfekowana.
Niestety, ostatnia kopia zapasowa wykonywana automatycznie przez firmę hostingową została zrobiona prawie dwa tygodnie wcześniej. Innej kopii nie było – właściciel serwera nie robił własnego backupu.
Pierwszym krokiem było poszukanie na serwerze plików, które zostały w ostatnich dniach zmodyfikowane. Nie było to szczególnie trudne, bo takie pliki można znaleźć sprawdzając datę modyfikacji katalogu. Po kilku minutach miałem już pierwszy plik zawierający złośliwy kod, który wyglądał tak:
Niby sukces, ale jednak nie do końca – w drugim zainfekowanym pliku kod miał podobną strukturę, ale był na tyle inny, że ciężko było znaleźć jakąś część wspólną, na podstawie której można byłoby zautomatyzować proces przeszukiwania plików.
W końcu postanowiłem nie marnować czasu na kombinowanie i posłużyć się występującym w trzech znalezionych plikach wywołaniem funkcji chr(41)
. Na moje szczęście serwer, na którym działały zainfekowane strony, pozwalał na dostęp przez SSH, dzięki czemu nie musiałem robić wszystkiego przez SFTP ani tworzyć lokalnej kopii plików.
Przeszukanie komendą grep -r -l --include *.php 'chr(41)' .
całego katalogu public_html (w nim znajdowały się pliki wszystkich stron) zajęło kilka minut. Przeszukiwanie zawęziłem do skryptów PHP, co znacząco skróciło czas operacji. Wynikiem była lista kilkudziesięciu plików, z których wszystkie zawierały złośliwy kod. Wystarczyło otworzyć każdy z nich, usunąć z niego śmieci i zapisać zmiany. Strasznie ogłupiające zajęcie. W niektórych przypadkach kod został „doklejony” do już istniejącego pliku, w innych znajdował się w specjalnie utworzonym pliku o mylącej nazwie (na przykład file.php
albo users.php
).
Po usunięciu wszystkich (jak mi się wtedy wydawało) śmieci przeszukałem strony pod kątem zainstalowanych starych wersji wtyczki Revolution Slider. Pod koniec ubiegłego roku wykryto w niej poważną lukę związaną z bezpieczeństwem, a sposób infekcji pasował mi do opisywanych przykładów wykorzystania tej luki. Miałem rację – na jednej ze stron znalazłem tę wtyczkę w bardzo starej wersji, a w środku znajdował się katalog ze śladami infekcji (plik /temp/update_extract/revslider/update.php
). Wtyczkę oczywiście usunąłem.
Dotychczasowe działania nie przyniosły jednak oczekiwanych rezultatów – część stron wróciła wprawdzie do życia, ale pozostałe wciąż nie działały lub zawierały złośliwy kod.
Nie mając innych pomysłów przeszukałem jeszcze raz wszystkie pliki, tym razem pod nieco szerszym kątem (grep -r -l --include *.php 'chr(' .
). Niestety, funkcja chr()
występuje w wielu miejscach, zarówno w samym WordPressie, jak i różnych wtyczkach. Znalazłem jednak w ten sposób kilka pominiętych wcześniej zainfekowanych plików.
Trochę przypadkiem trafiłem na jeszcze jeden złośliwy kod, którego niestety nie zachowałem przed usunięciem. Znajdował się on w jednym z zainstalowanych, ale nie aktywowanych motywów, tak więc można założyć, że nie był on w ogóle aktywny.
Po usunięciu złośliwego kodu ze świeżo znalezionych plików nie zmieniło się absolutnie nic. Doszedłem więc do wniosku, że ten konkretny kod nie jest jedynym, jakim zostały zainfekowane strony.
Szczerze mówiąc, nie bardzo wiedziałem gdzie i od czego zacząć. Trochę po omacku zacząłem zaglądać po kolei do plików ładowanych podczas uruchamiania WordPressa i znalazłem to:
Kod ten znajdował się w pliku wp-includes/nav-menu.php
, mniej więcej w jego połowie. Jak widać, nie jest on trudny do odnalezienia – wystarczy poszukać plików zawierających ciąg istart (grep -r -l --include *.php 'istart' .
) albo preg_replace(„/.*/e” (grep -r -l --include *.php 'preg_replace("/.*/e"' .
).
Okazało się, że kod ten jest doklejany tylko do plików nav-menu.php
, co znacząco ułatwia jego usunięcie. W moim przypadku znajdował się na praktycznie wszystkich stronach, z tym że na niektórych z jakiegoś powodu nie działał (ładowała się tylko pusta strona).
Po „zdekodowaniu” kodu okazało się, że pobiera on z pewnego serwera kod HTML, który „wstrzykuje” do kodu naszej strony. Standardowe działanie tego typu złośliwych skryptów.
Skrupulatnie usunąłem złośliwy kod ze wszystkich plików i na wszelki wypadek jeszcze raz przeszukałem cały serwer, tak aby mieć pewność, że niczego nie pominąłem. Niestety, nie mogłem po prostu zastąpić wszystkich zainfekowanych plików plikem oryginalnym, ponieważ na serwerze było zainstalowanych kilka różnych wersji WordPressa. Sprawdziłem po kolei wszystkie strony znajdujące się na serwerze – wróciły do życia i wyglądało na to, że działają tak jak powinny. Powiadomiłem właściciela serwera o moim sukcesie i z poczuciem dobrze spełnionego obowiązku odszedłem od komputera.
Problem powrócił po mniej więcej godzinie.
Pliki, z których wcześniej usunąłem złośliwy kod, zostały ponownie zainfekowane. Oczywiście ponownie usuwanie śmieci byłoby w tym momencie kompletnie bezcelowe – w jakimś innym miejscu musiało znajdować się coś, co infekowało wszystkie strony „od wewnątrz” lub zostawiało otwartą furtkę pozwalającą na wykonanie tego zdalnie.
Oczywiście szukałem pomocy w Google. Problem polega jednak na tym, że większość stron, na których opisywane są tego typu przypadki, umieszcza złośliwy kod na zrzutach ekranu (dokładnie w taki sposób, jak ja w tym wpisie), a to dlatego, aby uniknąć trafienia na „czarną listę” (taki kod umieszczony na stronie może zostać potraktowany jako infekcja). Niestety, ta metoda ma tę wadę, że ciężko trafić na te wpisy szukając fragmentu złośliwego kodu.
Nie mając lepszych pomysłów wróciłem do skanera Sucuri. Przeskanowałem kilka stron, z którymi wciąż były problemy, i dla jednej z nich otrzymałem wynik, który naprowadził mnie na rozwiązanie problemu. Tego typu infekcje zostały opisane dosłownie kilka dni temu na blogu Sucuri. Tak jak podejrzewałem, mechanizm infekcji poza kodem dodawanym do plików nav-menu.php
składał się z jeszcze jednego skryptu. Jego kod wygląda mniej więcej tak:
Kod ten doklejany jest do różnych plików WordPressa (ja znalazłem go m. in. w plikach /wp-admin/includes/class-wp-importer.php
oraz /wp-includes/pomo/po.php
) oraz umieszczany w nowych plikach (np. /wp-includes/theme-compat/hheader.php
, /wp-content/plugins/hhello.php
czy /wp-admin/maint/rrepair.php
). Znalezienie zmodyfikowanych plików metodą „na oko” jest o tyle trudne, że skrypt zachowuje daty modyfikacji zainfekowanych plików.
Nie trzeba być specjalistą od PHP, aby zorientować się co ten kod ma na celu. Daje on każdemu, kto zna hasło (zmienna $_passssword
), możliwość wykonania na serwerze dowolnego kodu PHP. To właśnie w ten sposób usuwany przeze mnie z plików nav-menu.php
złośliwy kod pojawiał się w nich ponownie. Oczywiście nikt nie robi takich rzeczy ręcznie – wszystko to dzieje się automatycznie.
Na szczęście powyższy kod nie był w żaden sposób „zaciemniany”, w związku z czym łatwo było wyszukać pliki, w których się on znajdował – wystarczyła komenda grep -r -l --include *.php 'passssword' .
(przez cztery 's’).
Teraz zostało mi tylko wyczyszczenie wszystkich zainfekowanych plików, przeszukanie jeszcze raz serwera (tak na wszelki wypadek, bo w międzyczasie za pośrednictwem backdoora mogło pojawić się coś nowego) i czekanie z nadzieją, że znalazłem już wszystkie przyczyny infekcji.
Na zakończenie dodam, że właściciel serwera na moją prośbę przejrzał dokładnie jego zawartość i usunął niepotrzebne rzeczy. Tym sposobem z ponad dwudziestu stron na serwerze zostało ich dwanaście. Pozostałe były zapomnianymi wersjami testowymi, które (nie wiedzieć czemu) były zindeksowane przez Google.
Podsumowanie
Z całej tej operacji i popełnionych w jej trakcie błedów wyciągnąłem kilka wniosków, którymi chciałbym się z Wami podzielić. Mam nadzieję, że komuś się one do czegoś przydadzą. Część z nich można potraktować jako uzupełnienie wpisu o naprawianiu zainfekowanych stron.
- Zgubiła mnie rutyna. Różne śmieci usuwam ze stron mniej więcej raz w miesiącu. Nigdy wcześniej nie zdarzyło mi się, aby na jednej stronie występował więcej niż jeden malware. Założyłem więc (błędnie), że w tym przypadku będzie tak samo.
- Sprawdziłem skanerem tylko trzy z ponad dwudziestu działających na serwerze stron. Powinienem był sprawdzić wszystkie – zaoszczędziłbym w ten sposób sporo czasu, który zmarnowałem na szukanie po omacku złośliwego kodu i wykonywanie dwukrotnie tej samej pracy.
- Gdybym nie miał dostępu do serwera przez SSH, to cała operacja wyszukiwania zainfekowanych plików trwałaby znacznie dłużej. Wybierając hosting współdzielony warto więc zwrócić uwagę, czy oferuje on dostęp przez SSH (w przypadku VPSów i serwerów dedykowanych taki dostep mamy zawsze).
- Regularny, najlepiej codzienny backup to podstawa. Nie ma w tym nic trudnego.
- Niepotrzebne serwisy powinny być usuwane z serwera albo regularnie aktualizowane. Gdyby nie stara, zapomniana wersja Revolution Slidera, to prawdopodobnie do opisanej infekcji w ogóle by nie doszło.
- Wersje testowe i rozwojowe stron koniecznie muszą być ukryte przed robotami wyszukiwarek, tak aby nie trafiły przypadkiem do ich indeksów (Ustawienia → Czytanie → Widoczność dla wyszukiwarek → Proś wyszukiwarki o nieindeksowanie tej witryny).