
Wczoraj dostaliśmy e-mail od klienta. Jego strona zamiast witryny firmy logistycznej wyświetlała indonezyjskie kasyno. Nastąpiło 6 godzin intensywnej pracy przeciwko dobrze zorganizowanemu systemowi ataku. To historia o tym, co odkryliśmy, jak czyściliśmy stronę i czego się z tego nauczyliśmy.

Co się stało
Jedna ze stron WordPress naszych klientów została zhakowana 3 lipca 2026 roku. Atakujący włamali się za pomocą automatycznego skanera, który wykorzystał publicznie znaną podatność (CVE-2026-63030) w przestarzałej wersji WordPress. Klient otrzymał od nas przypomnienia o konieczności aktualizacji, ale pozostały bez odpowiedzi. Najciekawsze nie jest jednak samo włamanie. Najciekawsze jest to, co zrobili potem.
Przez kolejne 66 dni atakujący starannie budowali infrastrukturę persystencji. Kilka niezależnych backdoorów, wyrafinowana obfuskacja, infrastruktura dowodzenia i kontroli przez GitLab i Telegram. Dopiero gdy wszystko było gotowe, wdrożyli końcowy payload: stronę główną zastąpiono statycznym klonem indonezyjskiej marki kasynowej RUBY8000 w celach manipulacji SEO.
Dlaczego to ważne
Gdyby Google zdążył zaindeksować stronę w stanie kasyna, zanim ją oczyściliśmy, reputacja SEO klienta byłaby zniszczona na miesiące. Google szybko wpisuje takie strony na czarną listę, a powrót do normalnych pozycji wymaga dużo czasu i ręcznego zgłoszenia w Google Search Console.
Anatomia ataku
Gdy zaczęliśmy analizę śledczą, zrozumieliśmy, że nie mamy do czynienia z amatorskim script kiddie. Atak miał 8 wyraźnie rozpoznawalnych faz, a każda z nich była zaplanowana jako zapasowa dla poprzedniej:
Faza 1 – Początkowy foothold
Wgranie PHP webshella przez podatny punkt.
Faza 2 – Eskalacja uprawnień
Utworzono 22 konta administratora WordPress według masowo generowanych wzorców (admin_wp2, wpadmin, root_admin…).
Faza 3 – Persystencja przez wtyczki
Osadzono 4 ukryte wtyczki z backdoorem: core-helper-f6003adcd7, wp2shell_badba2c3, wp2shell-f38e02fa54f1, WP-Compact, a także fałszywą wtyczkę optymalizacji WebP wtec-webp z wbudowaną powłoką ALFA_TEAMSHELL.
Faza 4 – Backdoor Auto_prepend_file
W plikach .htaccess i .user.ini umieszczono dyrektywy, które wymuszały wykonanie default.php przed każdym przetworzeniem żądania PHP. Nawet po usunięciu wszystkich wtyczek backdoor nadal by działał.
Faza 5 – Głęboko ukryte pliki index.php
Ponad 15 plików index.php rozmieszczono głęboko w strukturze WordPress (np. wp-includes/theme-compat/plugins/, wp-admin/network/pomo/) z obfuskacją opartą na goto i payloadami base64 uruchamianymi przez eval.
Faza 6 – Kopia zapasowa mu-plugins-old
Alternatywny katalog mu-plugins z backdoorem l10n. Nazwa udawała starą kopię zapasową.
Faza 7 – Komunikacja C&C
Trzy niezależne kanały dowodzenia i kontroli: snippet GitLab do dynamicznego pobierania payloadu, bot Telegram do powiadomień oraz bezpośrednia komunikacja HTTP z serwerem operatora.
Faza 8 – Wdrożenie payloadu
Plik index.php witryny został zastąpiony plikiem index.html, zawierającym pełnoprawny klon kasyna RUBY8000.
Najciekawszy szczegół techniczny – obfuskacja białymi znakami
W pliku default.php (77 KB) znaleźliśmy takie cudo:
$dispatcher = ‘ \t \t\t \t\t \t \t \t \t\t\t\t\t\t …’;
$binary = str_replace([‘ ‘, “\t”], [‘0’, ‘1’], $dispatcher);
// Każde 8 bitów jest zamieniane na znak:
$decoded = ”;
for ($i = 0; $i < strlen($binary); $i += 8) {
$decoded .= chr(bindec(substr($binary, $i, 8)));
}
eval($decoded); // Uruchamia zdekodowany kod PHP
Atakujący wykorzystał spacje i tabulatory jako dane binarne. Każda spacja to 0, każdy tab to 1. Każde 8 znaków tworzy bajt. To sprytne rozwiązanie, ponieważ:
- Automatyczne skanery antywirusowe zazwyczaj skanują printable characters
- Wizualnie plik wygląda jak puste, sformatowane komentarze
- Grep po “eval” znajduje tylko jedno miejsce na dole kodu, a nie 77 KB obfuscated payload
Jak przeprowadziliśmy czyszczenie
Standardowym podejściem byłoby przywrócenie strony z kopii zapasowej. Jednak ostatni czysty backup klienta był starszy niż 66 dni, a w tym czasie strona otrzymała mnóstwo prawidłowych aktualizacji treści. Nie mogliśmy po prostu „cofnąć wszystkiego” i stracić całej tej pracy.
Dlatego wybraliśmy podejście chirurgiczne: precyzyjnie zidentyfikować i usunąć każdy artefakt atakującego, zachowując całą prawidłową treść. Trzy rundy:
Runda 1: Wstępna sanitacja
Bash + wp-cli przez SSH. Dezaktywowaliśmy i usunęliśmy 4 wtyczki typu backdoor, skasowaliśmy default.php, .htaccess z auto_prepend_file oraz .user.ini. Za pomocą wp-cli usunęliśmy 22 konta admina i wyczyściliśmy wszystkie tokeny sesji. Przed jakimikolwiek modyfikacjami wykonaliśmy pełną kopię bazy danych jako siatkę bezpieczeństwa.
Runda 2: Skanowanie dogłębne
Rekurencyjne skanowanie plików PHP w całej instalacji WordPress w poszukiwaniu sygnatur i anomalii. Na przykład: plik index.php większy niż 20 KB głęboko w strukturze WordPress jest podejrzany. Znaleźliśmy jeszcze 10 ukrytych plików backdoor, które nie były widoczne w pierwszej rundzie. Usunęliśmy także katalog ALFA_DATA z CGI wrappers oraz 15 plików .htaccess atakującego.
Runda 3: Czyszczenie natywne dla WordPress
Wykorzystaliśmy integrację Novamira MCP do bezpośredniego dostępu do API WordPress. Zweryfikowaliśmy pliki core WordPress względem oficjalnych checksums (wszystkie czyste). Oczyściliśmy wpisy bazy danych TranslatePress z treści RUBY8000. Wyczyściliśmy cache Elementor i Varnish HTTP.
Utwardzanie serwera po czyszczeniu
Czysta strona, która pozostaje niezmieniona, to jak zamknięte drzwi z tym samym kluczem, który został włamany. Dlatego wzmocniliśmy konfigurację:
// dodatki do wp-config.php
define(‘DISALLOW_FILE_EDIT’, true); // Blokuje edycję wtyczek/motywów z poziomu admina
define(‘AUTOMATIC_UPDATER_DISABLED’, false);
define(‘WP_AUTO_UPDATE_CORE’, ‘minor’); // Auto-updates dla poprawek bezpieczeństwa
define(‘FORCE_SSL_ADMIN’, true); // Wymuszony HTTPS w wp-admin
Dodatkowo zrotowaliśmy wszystkie salts WordPress: AUTH_KEY, SECURE_AUTH_KEY, LOGGED_IN_KEY, NONCE_KEY (co wymusiło wylogowanie każdej pozostałej sesji atakującego), zablokowaliśmy xmlrpc.php, zabezpieczyliśmy wp-config.php przed żądaniami HTTP i wyłączyliśmy wykonywanie PHP w katalogach wp-content/uploads/ oraz wp-includes/.
4 lekcje, które wyciągnęliśmy z tego przypadku
1. Aktualizacje WordPress to nie „jak znajdzie się czas”, są krytyczne
Pierwotna przyczyna była prosta: przestarzała wersja WordPress. Wysyłaliśmy klientowi przypomnienia o potrzebnej aktualizacji, ale pozostały bez odpowiedzi. Gdy publicznie ujawniona zostaje luka w zabezpieczeniach jakiegoś komponentu WordPress, automatyczne skanery zaczynają działać w ciągu kilku godzin. Dlatego aktualizacje trzeba wykonywać w ciągu tygodni, a nie miesięcy.
Praktyczna rekomendacja: podpisz umowę serwisową ze swoim partnerem WordPress, w której jasno zapisano, że partner techniczny sam wykonuje aktualizacje bez każdorazowego zatwierdzania. Dzięki temu unikniesz sytuacji, w której e-mail o krytycznej aktualizacji tygodniami pozostaje nieotwarty.
2. Rotacja backupów jest krytyczna
Klient miał backupy, ale były starsze niż atak. 66-dniowy okres utajenia oznaczał, że malware znajdowało się już we wszystkich „świeżych” backupach. Zalecamy przechowywanie historii backupów z co najmniej 90 dni, z przechowywaniem off-site i niezmiennymi (immutable) kopiami zapasowymi.
3. Rotacja logów co 7 dni to za krótko
Nie byliśmy w stanie w 100% zidentyfikować dokładnego punktu wejścia, ponieważ logi dostępu Nginx były przechowywane tylko przez 7 dni. Co najmniej 180 dni log retention powinno być obowiązkowe dla każdej strony przetwarzającej dane osobowe.
4. WAF to nie luksus, to minimum
Na stronie była zainstalowana wtyczka zabezpieczająca, ale wiele jej funkcji nie było aktywowanych. WAF (Web Application Firewall) działający w czasie rzeczywistym, z aktywnymi, aktualnymi sygnaturami zagrożeń, prawdopodobnie zatrzymałby początkowy skaner. Ponadto 2FA dla wszystkich kont admina to obowiązkowe minimum, a nie opcja.
Co zrobić, jeśli ciebie również dotknął podobny atak
- Nie przywracaj kopii zapasowej, jeśli nie masz pewności, że jest czysta. Najpierw zachowaj kopię zainfekowanej witryny jako dowód.
- Nie zmieniaj haseł z zainfekowanego urządzenia. Jeśli na Twoim komputerze działa keylogger, nowe hasła również zostaną przechwycone.
- Zmień hasła z innego urządzenia: panel administracyjny WordPress, panel hostingu, MySQL, FTP/SSH.
- Zakończ wszystkie aktywne sesje i wymuś 2FA dla wszystkich kont administratorów.
- Skontaktuj się ze specjalistą ds. bezpieczeństwa. Ręczne czyszczenie bez odpowiedniej wiedzy zwykle pozostawia część backdoorów nietkniętą, a atak wraca w ciągu 2-4 tygodni.
CERT.LV dobrowolne zgłaszanieJeśli Twoja witryna padła ofiarą cyberincydentu, możesz dobrowolnie zgłosić to do CERT.LV ([email protected]). Nie wiąże się to z dodatkowymi obowiązkami, a przekazane przez Ciebie IoC mogą pomóc ostrzec inne łotewskie organizacje przed tą samą kampanią.
Czy Twoja witryna WordPress jest zabezpieczona?
Dział bezpieczeństwa EvoEX oferuje audyt bezpieczeństwa, monitoring 24/7 oraz awaryjną reakcję na incydenty. Proaktywna ochrona kosztuje około 10% kosztów jednorazowego incydentu.
PAMIĘTAJ!
Zapobieganie cyberatakom nie jest szybkim ani prostym procesem – wymaga głębokiej wiedzy technicznej, błyskawicznego czasu reakcji i dokumentacji dla odpowiednich organów. Dlatego zalecamy skorzystanie z usługi utrzymania oferowanej przez Twojego dewelopera WordPress. Jeśli nie masz własnego partnera technicznego – skontaktuj się z nami.
