Aktualizacja, maj 2026. Krótko po pierwszym opisaniu tego przypadku ten sam sklep został zaatakowany ponownie. Ponowna infekcja nie przyszła przez backdoor, który przeoczyliśmy, weszła frontowymi drzwiami: przez niezałataną lukę na poziomie aplikacji (SQL injection w module listy życzeń blockwishlist, połączone z RCE przez cache MySQL w Smarty, CVE-2022-36408), którą nasze czyszczenie na działającym serwerze ograniczyło, ale nigdy nie zamknęło. Usunięcie malware'u to nie to samo, co zamknięcie wektora wejścia. Głębsza naprawa na poziomie aplikacji, wirtualne łatanie konkretnych podatności i zamknięcie originu za Cloudflare, to osobny temat opisany w artykule Zaawansowane utwardzanie PrestaShop dla sklepów, których nie możesz jeszcze zaktualizować. Wniosek pozostaje bez zmian: ograniczenie szkód kupuje czas; migracja z 1.7.x jest właściwym leczeniem.
To prawdziwy incydent ze sklepu PrestaShop 1.7.x, który odzyskiwaliśmy. Identyfikatory zostały zanonimizowane, ale przepływ sterowania, lokalizacje plików, struktura payloadów i logika wykrywania są zachowane dokładnie. Nie opisujemy tego po to, żeby kogokolwiek straszyć, chodzi o to, aby inni właściciele sklepów i developerzy mieli konkretny punkt odniesienia: jak nowoczesny atak w stylu Magecart na PrestaShop wygląda od środka, tak aby po zobaczeniu podobnych kształtów we własnych plikach rozpoznać je w kilka minut, a nie po kilku dniach.
Właściciel skontaktował się z nami, bo na stronie finalizacji zamówienia pojawił się formularz płatności, którego nie rozpoznawał. Wyglądał jak formularz kart Stripe Elements. Sklep rzeczywiście używa Stripe, więc wizualne podobieństwo było na tyle duże, że przez prawie dwa dni nikt tego nie zakwestionował. Gdy zaczęliśmy analizę, skimmer po cichu kopiował dane kart od każdego klienta, który doszedł do finalizacji zamówienia. Ten artykuł prowadzi przez dochodzenie w kolejności, w jakiej naprawdę się rozwijało, pierwsza wskazówka, payload po stronie serwera, trik z eksfiltracją, JavaScript, przejęcie roota i wreszcie punkt wejścia, bo właśnie ta kolejność jest najważniejszą lekcją.
Pierwsza wskazówka: formularz płatności, którego nie powinno być

Stripe Elements renderuje pola kart wewnątrz iframe'ów cross-origin. Strona nadrzędna nie może odczytać tego, co klient w nich wpisuje. Ta izolacja jest całym powodem istnienia Elements i właśnie ona chroni poprawnie zintegrowany formularz Stripe przed skimmingiem nawet wtedy, gdy sam sklep został przejęty. Dlatego pierwsze pytanie przy podejrzeniu skimmera jest proste: czy pola karty są w iframe, czy są zwykłymi inputami na stronie?
Formularz, na który patrzyliśmy, używał zwykłych, własnych inputów HTML:
| Wstrzyknięte pole | Co z tego wynika |
|---|---|
<input id="cardNumber" onkeyup="smenu(this)"> | Brak iframe'a. Strona nadrzędna może odczytać każde naciśnięcie klawisza |
<input id="cc_cid" onkeyup="smenu(this)"> | Handler onkeyup na każdym polu, wywołujący niestandardową funkcję smenu() |
<button onclick="processF()">PLACE ORDER</button> | Niestandardowy handler wysyłki formularza, który omija prawdziwy moduł płatności |
Trzy natychmiastowe sygnały: brak iframe'ów, handler naciśnięć klawiszy na każdym polu i subtelna niespójność językowa między wstrzykniętym formularzem a resztą sklepu. Legalny moduł Stripe nadal był zainstalowany i skonfigurowany, fałszywy formularz został po prostu namalowany na nim. Korzyść ze znajomości tej jednej zasady (prawdziwe bramki używają iframe'ów; surowe pola kart w finalizacji zamówienia PrestaShop są prawie zawsze złośliwe) jest taka, że każdy sprzedawca może sprawdzić to sam, jeszcze dziś, w inspektorze przeglądarki i bez specjalnych narzędzi, lista wskaźników znajduje się na końcu tego wpisu.
Odnalezienie payloadu po stronie serwera
Keylogger w przeglądarce ma sens tylko wtedy, gdy może gdzieś wysłać to, co przechwyci. Przeszukaliśmy cały katalog config/ pod kątem podejrzanych wzorców i znaleźliśmy ten sam blok dopisany na początku każdego pliku PHP w tym katalogu: fragment kodu, który czekał na pole $_POST['order_llx'], a gdy je zobaczył, przekazywał zawartość przez curl do dwóch zakodowanych base64 adresów URL z wyłączonym CURLOPT_SSL_VERIFYPEER, po czym usuwał pole, żeby nic dalej w aplikacji tego nie zauważyło.
Trzynaście plików w config/ zawierało ten blok i wszystkie miały ten sam znacznik czasu zmiany inode, co do sekundy. Atakujący wdrożył to skryptem. Dwa ciągi base64 dekodowały się do adresów IP dwóch serwerów u dużego azjatyckiego dostawcy chmury: endpointu eksfiltracji. Jeden plik, settings.inc.php, został podmieniony w całości, został w nim tylko złośliwy kod, a sklep i tak działał dalej, bo w PrestaShop 1.7 prawdziwa konfiguracja bazy danych znajduje się w app/config/parameters.php, a nie w przestarzałym settings.inc.php. Atakujący albo o tym wiedział, albo miał szczęście.
Dwustopniowy trik z eksfiltracją
Naprawdę sprytną częścią tego ataku jest sposób, w jaki dane opuszczają sklep. Większość polityk Content Security Policy, blokerów reklam i rozszerzeń antyskimmerowych jest zbudowana tak, aby łapać sytuację, w której przeglądarka rozmawia bezpośrednio z serwerem atakującego. Ten skimmer nigdy tego nie robi. Przejęty sklep działa jako przekaźnik:
- Krok 1 (przeglądarka → sklep). Handler
processF()fałszywego formularza wysyła przechwycone dane karty metodą POST do/w domenie własnej klienta, w polu o nazwieorder_llx. Z perspektywy przeglądarki jest to zwykły ruch first-party, ten sam origin, brak CORS, nic, co CSP nastawione wyłącznie na strony trzecie mogłoby oznaczyć. - Krok 2 (sklep → C2). Wstrzyknięty PHP w
config/łapie to pole po stronie serwera i przekazuje je przezcurlna adresy IP atakującego. To żądanie serwer-serwer, którego przeglądarka nigdy nie widzi: nie pojawia się w DevTools, w zakładce sieci ani u administratora sieci obserwującego połączenie klienta.
Na tym polega całe obejście. Administrator sieci widzi połączenie z legalnym sklepem i kończy analizę; CSP blokujące tylko skrypty zewnętrzne nie łapie niczego. Taki relay w tym samym originie jest jedną z trudniejszych odmian Magecart do wykrycia po stronie klienta. Trzeba uczciwie powiedzieć, że nie jest to jedyny wzorzec, Sansec i inni obserwatorzy nadal widzą równolegle eksfiltrację bezpośrednio z przeglądarki, ukryte przechowywanie po stronie serwera oraz warianty z opóźnionym pobieraniem, bez jednego dominującego schematu, ale relay jest tym wariantem, który pokonuje większość gotowych zabezpieczeń klienckich.
Skimmer JavaScript i różnica rozmiaru, która go zdradziła
Sam fałszywy formularz znajdował się w motywie. Przeszukaliśmy unikalne tokeny, które już widzieliśmy, cc_owner, processF, smenu, i znaleźliśmy wstrzyknięcie dopisane do pliku ps_shoppingcart.js motywu. Sama różnica rozmiaru pliku była niepodważalnym dowodem: czysty ps_shoppingcart.js ma około 2,7 KB, a zainfekowana kopia miała mniej więcej 340 KB. Jeden blob o rozmiarze 337 KB został doklejony na końcu 74 różnych plików JavaScript w motywie nadrzędnym i potomnym, do każdego theme.js, każdego custom.js i każdego front-endowego skryptu modułu w vendored folderach modułów motywu.
Payload był zaciemniony, ale niezbyt pomysłowo: heksadecymalnie zakodowana tablica stringów (nazwana _0x5aa5, 225 wpisów), do której odwoływano się indeksami w całym kodzie, więc document[_0x5aa5[39]](_0x5aa5[158]) po prostu rozwiązywało się do document.getElementById("cardNumber"). Dekodowanie tablicy pokazuje cały zamiar, wpisy dla cardNumber, exp-date, cc_owner, cc_cid, pól rozliczeniowych, nazwy pola eksfiltracji order_llx i tagu kampanii Zhang. Skimmer składa rozdzielony pionowymi kreskami ciąg ze wszystkich przechwyconych pól, koduje go w hex, opakowuje base64 i wysyła metodą POST jako FormData do /. Kilka pól-wabików (products_hash, amount_hash, billing_hash) jedzie razem jako losowy szum, dzięki czemu POST w logach wygląda jak rutynowy beacon analityczny.
Pułapka skompilowanych zasobów (specyficzna dla PrestaShop)
Zastąpiliśmy zainfekowany ps_shoppingcart.js czystą kopią. Fałszywy formularz nadal się pojawiał. To pułapka, w którą wpadają osoby czyszczące konkretnie sklep PrestaShop, dlatego warto zrozumieć ją precyzyjnie.
Pipeline CCC PrestaShop (Combine, Compress, Cache) łączy skrypty front-office zarejestrowane dla bieżącej strony: wszystko, co zadeklarowano przez registerJavascript bez async/defer, w scalone pliki typu bottom-<hash>.js w themes/<theme>/assets/cache/. Zainfekowane skrypty były zarejestrowane dokładnie w ten sposób, więc złośliwy kod został wypieczony w skompilowanych paczkach, gdy źródło nadal było zainfekowane. Dopóki nie wyczyściliśmy tego katalogu cache, przeglądarka wciąż dostawała skimmer ze skompilowanego bundle'a, niezależnie od tego, co robiliśmy z plikiem źródłowym. Naprawa polegała na zastąpieniu całego drzewa themes/ ze znanego, czystego źródła i wyczyszczeniu każdego cache zasobów, themes/<theme>/assets/cache/, var/cache/ oraz var/compile/, plus ewentualnego cache obiektowego Redis lub Memcached. Wszystko poniżej tego poziomu zostawia bundle'e regenerujące się z zainfekowanego źródła.
Większy problem: przejęcie roota
Podczas szukania kolejnych payloadów skimmera uruchomiliśmy find / -perm -4000 -type f i trafiliśmy na niemiłą niespodziankę: dwa binaria ELF leżące w webroocie, zamaskowane rozszerzeniami .js. Ustawiony bit SUID, właściciel root, po 1,7 MB każdy, identyczny SHA256, i wcale nie JavaScript, tylko wykonywalne pliki x86_64 ELF (file zgłaszał setuid ELF 64-bit LSB executable, statically linked, stripped). Cztery kolejne binaria SUID-root znajdowały się w /usr/bin pod losowymi nazwami, plus jedno o nazwie pkexem, jednoliterowa literówka względem legalnego pkexec, zaprojektowana tak, żeby prześlizgnąć się przy pobieżnym ls.
To był moment, w którym incydent zmienił kategorię. Utworzenie binarium SUID root wymaga roota. PHP uruchomiony jako użytkownik strony nie może tego zrobić, niezależnie od tego, jak puste jest disable_functions albo jak luźne jest open_basedir. Samo istnienie tych plików dowodziło, że przejęty został root, a nie tylko użytkownik WWW. Co oznacza, że czyszczenie webroota nigdy nie może być końcem pracy, bo persistence może ukrywać się w dowolnym miejscu hosta. Dla właściciela sklepu praktyczne tłumaczenie jest bezpośrednie: na tym etapie jedynym w pełni obronnym stanem końcowym jest postawienie serwera od nowa z czystego obrazu systemu, migracja zweryfikowanego, czystego kodu i danych, rotacja każdego sekretu oraz traktowanie pierwotnego hosta jako tymczasowego środowiska ograniczania szkód, utrzymywanego przy życiu tylko tak długo, jak trzeba, aby nadal obsługiwać klientów.
Rzeczywisty punkt wejścia: web shell w index.php
Wyczyściliśmy skimmer i usunęliśmy binaria-backdoory, ale nadal nie wiedzieliśmy, jak atakujący dostał się pierwszy raz. Logi dostępu pokazywały jeden adres IP wykonujący żądania POST / przez kilka tygodni, a rozmiary odpowiedzi były odciskiem palca:
| Rozmiar odpowiedzi | Co robił atakujący |
|---|---|
| ~46 KB | Pełna strona główna, sondowanie exploita z błędnymi parametrami |
| 1–2 KB | Wynik poleceń (ls, cat, whoami), rekonesans |
| ~695 B | Potwierdzenia zapisu plików, sadzenie payloadu |
| ~36 KB | Jedna większa odpowiedź, nadal dużo poniżej rozmiaru zrzutu bazy danych |
Ten rozkład powiedział nam dwie rzeczy. Po pierwsze, POST / samo w sobie było endpointem wykonywania kodu, zwykłe żądania strony głównej nie generują stałego strumienia 695-bajtowych potwierdzeń zapisu plików. Po drugie, pełny eksport tabeli klientów miałby co najmniej setki kilobajtów, a w logach nic nawet się do tego nie zbliżało; atakujący używał shella do zapisywania plików, nie do masowego wyciągania danych. Ta rozbieżność jest naprawdę uspokajającym dowodem, który można pokazać zaniepokojonemu właścicielowi, i jest dokładnie tym typem ustalenia, które logi dostępu dają za darmo, jeśli wiesz, że warto czytać rozmiar odpowiedzi, a nie tylko kod statusu.
Endpoint zaprowadził nas do index.php. Kopia z działającej strony została w którymś momencie wyczyszczona (prawdopodobnie podczas wcześniejszej próby odzyskiwania), ale witryna developerska w sąsiednim katalogu współdzielącym ten sam docroot nadal miała oryginał. Zaczynał się od zaciemnionego loadera: pojedynczy literał stringowy działał jak alfabet, a niebezpieczne nazwy funkcji (create_function, assert, base64_decode) były składane po jednym znaku przez indeksowanie tego stringa, omijając naiwny grep. Loader dekodował mały payload base64, budował z niego wykonywalne domknięcie PHP w runtime i je wywoływał. Zdekodowany payload był samym web shellem: każde żądanie, którego parametr product_id pasował do zahardkodowanego hasha MD5, trafiało do eval() z dowolnym PHP przekazanym w parametrze image_id. Nazwy parametrów celowo dobrano tak, aby w logach wyglądały jak zwykły ruch e-commerce.
Jeden szczegół zasługuje na osobne zdanie, bo to przypadkowy prezent wynikający z aktualizacji: loader zależy od create_function i ewaluacji stringów przez assert(), a oba mechanizmy PHP usunął w wersji 8.0. Na nowoczesnym hoście z PHP 8 ten backdoor sam zakończyłby się błędem fatalnym. Przeniesienie tego kodu na PHP 8 niechcący zepsułoby shella, jeszcze jeden powód, dla którego prawdziwa naprawa prowadzi do przodu, a nie w bok.
Oś czasu i jej konsekwencje
Nie mogliśmy ustalić daty pierwotnego posadzenia web shella, zdarzenie było starsze niż dostępna retencja logów, ale resztę dało się czysto odtworzyć ze znaczników czasu plików i logów dostępu:
| Dzień względem wykrycia | Zdarzenie | Dowód |
|---|---|---|
| −60 lub wcześniej | Web shell posadzony w index.php | Starsze niż retencja logów |
| −25 | Binaria SUID-root wrzucone do webroota | Znaczniki czasu utworzenia plików |
| −22 | Binaria SUID-root wrzucone do /usr/bin | Znaczniki czasu utworzenia plików |
| −10 do −5 | Rekonesans przez web shell | Odpowiedzi POST /, 695–1844 B |
| −2 | JS skimmera wdrożony do 74 plików motywu | Znaczniki czasu zmiany inode |
| −2 (≈30 min później) | PHP skimmera wdrożony do 13 plików konfiguracyjnych | Znaczniki czasu zmiany inode |
| 0 | Właściciel zgłasza podejrzany formularz | Inspekcja wizualna |
25-dniowa przerwa między przejęciem roota a wdrożeniem skimmera jest najbardziej wymownym wierszem w tej tabeli. Pasuje do scenariusza, w którym początkowy dostęp i monetyzacja są osobnymi etapami, dostęp pojawił się najpierw, a payload do skimmingu kart przyszedł tygodnie później. Na podstawie tych dowodów nie da się jednoznacznie stwierdzić, czy dostęp został sprzedany drugiej grupie, czy po prostu czekał w kolejce u tego samego operatora pracującego po liście celów, ale dla obrońcy lekcja jest ta sama: ciche tygodnie nie oznaczają bezpieczeństwa, tylko odliczanie. Sklep, który wykryje włamanie w tym okresie, nie traci nic; sklep, który czeka na objaw, traci każdą kartę przetworzoną po drodze.
Naprawa w kolejności, która ma znaczenie
Tę interwencję ukształtowały dwa ograniczenia, z którymi mierzy się większość prawdziwych sklepów. Strona działała i przyjmowała zamówienia, więc nie mogła zniknąć na wiele godzin. A aktualizacja z 1.7.x do 8.x/9.x to projekt na kilka tygodni, kompatybilność modułów, refaktoryzacja motywu, przebudowa płatności, nie zadanie na jeden dzień. Plan był więc taki: ograniczyć szkody i utwardzić sklep teraz, a równolegle przygotować czystą odbudowę. Pełna procedura krok po kroku (zabezpieczenie dowodów, rotacja danych dostępowych, szukanie backdoorów, blokowanie IP C2 firewallem, usuwanie wystawionych narzędzi administracyjnych, strojenie WAF) jest osobnym dokumentem, utrzymujemy ją jako checklistę utwardzania bezpieczeństwa PrestaShop, żeby była aktualna. To, co jest specyficzne dla tego incydentu i warte zapamiętania, to kolejność oraz kilka ruchów świadomych specyfiki PrestaShop:
- Zabezpiecz dowody, zanim czegokolwiek dotkniesz. Zrób snapshot dysku (albo przynajmniej spakuj webroot,
/etci/var/log), zarchiwizuj logi dostępu i błędów oraz policzsha256sumdla każdego podejrzanego pliku. Przydadzą się do post-mortem, do ewentualnego zapytania od operatora płatności i przy nieuniknionym drugim przebiegu, gdy znajdziesz backdoor, który wcześniej umknął. - Najpierw zatrzymaj wyciek widoczny dla klientów. Usuń wstrzyknięcie PHP z 13 plików konfiguracyjnych, przywróć
config/settings.inc.phpz czystej kopii dokładnie tego samego wydania PrestaShop (a rotację DB/cookie skup naapp/config/parameters.php, gdzie PS 1.7+ faktycznie trzyma tę konfigurację), zastąp całe drzewothemes/czystym źródłem i wyczyść każdy katalog cache oraz OPcache. W przeciwnym razie skompilowane bundle'e nadal będą serwować skimmer, jak opisano wyżej. - Potem szukaj persistence na całym hoście, nie tylko w webroocie. Ponieważ root został przejęty, niebezpieczne artefakty mogą znajdować się w
/etc/cron*, timerach i unitach systemd,~/.ssh/authorized_keyskażdego użytkownika,/etc/sudoers.d/, profilach shella, plikach vhostów oraz tabeli MySQLmysql.user, a także w nowych eventach lub triggerach. Wszystko zmodyfikowane w oknie kompromitacji jest podejrzane. - Uczyń pliki punktów wejścia niezmienialnymi.
chattr +inaindex.phpi na wszystkichconfig/*.php. Przyszły web shell nie może przepisać niezmienialnego pliku bez wcześniejszego uruchomieniachattr -i, a to wymaga roota. (Zastrzeżenie: działa to tylko na systemach plików honorujących atrybuty rozszerzone. Ext4/xfs/btrfs są w porządku; niektóre kontenerowe overlaye i konfiguracje ZFS nie są.)
Gdy root został dotknięty, czyszczenie na miejscu ogranicza szkody, ale nie leczy, a jeśli wektorem reinfekcji jest niezałatana podatność aplikacyjna, nie przeoczony plik, powyższa procedura cię nie uratuje. Ten problem warstwy aplikacji (wirtualne łatanie konkretnych CVE, zamknięcie originu za Cloudflare, poprawne ograniczenie środowiska uruchomieniowego PHP) jest dokładnie tym, po co istnieje artykuł Zaawansowane utwardzanie PrestaShop dla sklepów, których nie możesz jeszcze zaktualizować. A jeśli dane kart dotarły do C2. Zakładaj, że tak było, dopóki nie udowodnisz inaczej, obowiązki zgłoszeniowe wobec operatora płatności zaczynają się natychmiast; pełna sekwencja jest w naszym poradniku reagowania na wyciek danych.
Wskaźniki kompromitacji, czego szukać grepem
Uruchom wyszukiwanie z katalogu głównego sklepu i traktuj każde trafienie jako dowód do sprawdzenia, a nie automatyczne potwierdzenie samo w sobie:
rg -n "order_llx|processF|smenu|cc_owner|cc_cid|CURLOPT_SSL_VERIFYPEER|base64_decode" \
config themes modules
find config themes modules -type f \( -name '*.php' -o -name '*.js' \) -mtime -14 -ls
Jeśli prowadzisz sklep PrestaShop i chcesz sprawdzić tę konkretną rodzinę, oto zestaw sygnałów o wysokiej wartości. Duże rekurencyjne grepy uruchamiaj na snapshotcie albo w trybie tylko do odczytu, jeśli możesz. Ciężki skan na obciążonym webroocie może podbić I/O i uruchomić własne alerty.
| Gdzie szukać | Wskaźnik |
|---|---|
| Źródła PHP / JS | rg -n "order_llx|_0x5aa5|processF|smenu|__Pres_[il]dk" /var/www |
| Wstrzyknięcie w plikach konfiguracyjnych | Dosłowny fragment @deprecated 1.7$ar=[ dopisany na początku plików w config/ |
| Zaciemnione loadery | Łańcuchy eval(base64_decode(…)), assert(base64_decode(…)) albo create_function(…base64…) |
| Backdoory SUID | find / -perm -4000 -type f 2>/dev/null (uruchom jako root) |
| ELF udający JS | find /var/www -name '*.js' -exec file {} + | grep ELF |
| Persistence przez prepend | auto_prepend_file / auto_append_file w .htaccess albo .user.ini |
A w przeglądarce, na wyrenderowanej stronie finalizacji zamówienia: surowe pola <input> z ID cc_owner, cardNumber, cardExpiry albo cc_cid (prawdziwe formularze Stripe/Adyen/Braintree używają iframe'ów. Surowe pola kart są czerwoną flagą), ustawiane ciasteczka o nazwach __Pres_idk lub __Pres_ldk oraz index.php zaczynający się od zaciemnionej zmiennej w stylu <?php $e4d6=, czyli jedna litera plus hex tuż po tagu otwierającym. Tag kampanii Zhang (base64 Wmhhbmc=) pojawia się w payloadzie eksfiltracji, ale sam jest zbyt ogólny, żeby mu ufać, nadaj mu wagę tylko wtedy, gdy współwystępuje z innym wskaźnikiem powyżej. Do głębszych skanów skieruj php-malware-finder, publiczne reguły YARA Sucuri/ESET albo monitor integralności plików (AIDE, Wazuh, OSSEC) na webroot, ale pomiń skanery specyficzne dla Magento; ich sygnatury nie pasują do ataku ukształtowanego pod PrestaShop.
O co naprawdę chodziło w tym incydencie
Web shell był raną. Skimmer był widocznym objawem. Wyczyszczenie skimmera bez znalezienia shella zostawiłoby drzwi szeroko otwarte do ponownego wdrożenia tego samego payloadu, albo sprytniejszego, w ciągu kilku dni, i dokładnie tak doszło do reinfekcji opisanej na początku tego wpisu. Najważniejszy nawyk, którego uczy ten incydent, to ciągnięcie za nitkę: fałszywy formularz karty prowadzi do wstrzykniętych plików konfiguracyjnych, one prowadzą do pułapki skompilowanych bundle'i, ta prowadzi do binariów SUID-root, a te prowadzą do web shella w index.php. Zatrzymaj się na dowolnej warstwie powyżej ostatniej, a wyczyściłeś objaw, nie zamknąłeś ataku.
A jeśli dziś nadal działasz na PrestaShop 1.7.x, uczciwe ujęcie brzmi tak: jesteś celem, a exploity używane do posadzenia pierwotnego shella są stare, dobrze znane i spakowane w automatyczne skanery. Migracja do wspieranej, w pełni załatanej gałęzi PrestaShop, ze zaktualizowanymi wszystkimi modułami i motywami oraz odbudowanym i utwardzonym hostem, jest trwałą drogą. W międzyczasie, realistycznie przez tygodnie albo miesiące w nietrywialnym sklepie, hardening z checklisty utwardzania i środki warstwy aplikacji z zaawansowanego utwardzania dla sklepów, których nie możesz jeszcze zaktualizować nie czynią cię nietykalnym, ale razem podnoszą koszt ataku i, równie ważne, sprawiają, że włamanie staje się na tyle głośne, aby dać realną szansę wykrycia go w cichych tygodniach, zamiast dopiero po zniknięciu kart.
Oficjalne materiały PrestaShop
Oficjalne zalecenia znajdziesz w alercie bezpieczeństwa PrestaShop dotyczącym cyfrowych skimmerów, we wpisie projektu o poważnej podatności bezpieczeństwa na stronach PrestaShop, w notatce z 2025 roku o atakach SQL injection i wstrzykiwanym JavaScripcie oraz w najlepszych praktykach zabezpieczania sklepu od PrestaShop. Żadne z tych źródeł nie opisuje tego konkretnego incydentu, ale są przydatnym oficjalnym kontekstem dla kontroli skimmerów, rotacji danych dostępowych i obowiązków eskalacyjnych.
Najczęściej zadawane pytania
Jak sprawdzić, czy moja finalizacja zamówienia ma podobny skimmer?
Otwórz działającą finalizację zamówienia w przeglądarce, kliknij pola karty prawym przyciskiem i sprawdź je w inspektorze. Poprawnie zintegrowany formularz Stripe, Adyen albo Braintree umieszcza pola karty wewnątrz iframe'ów cross-origin. Strona nadrzędna nie może ich odczytać. Jeśli zamiast tego widzisz surowe, własne pola <input> z ID typu cardNumber, cc_owner albo cc_cid, siedzące bezpośrednio na stronie, to czerwona flaga. Następnie uruchom grepy z sekcji wskaźników powyżej na snapshotcie swoich plików. Pojedyncze trafienie jest czymś do sprawdzenia, nie automatycznym dowodem.
Usunąłem malware. Dlaczego fałszywy formularz wrócił?
Dwie pułapki specyficzne dla PrestaShop. Po pierwsze, pipeline CCC wypieka zainfekowane skrypty do skompilowanych bundle'i w themes/<theme>/assets/cache/, dopóki nie wyczyścisz tego cache (plus var/cache/, var/compile/ i ewentualnego Redis/Memcached), przeglądarka nadal dostaje skimmer z bundle'a, niezależnie od tego, co robisz ze źródłem. Po drugie, i gorzej: jeśli wektor wejścia (web shell, niezałatany moduł) nadal jest otwarty, atakujący po prostu wdraża go ponownie. Czyszczenie plików ogranicza szkody, ale nie leczy.
Znaleźliśmy binaria SUID-root. Jak źle to wygląda?
Utworzenie binarium SUID-root wymaga roota. PHP działający jako użytkownik strony nie może tego zrobić, niezależnie od tego, jak mocno zablokowane są disable_functions czy open_basedir. Ich istnienie dowodzi, że przejęty został root, nie tylko użytkownik WWW, więc persistence może ukrywać się w dowolnym miejscu hosta. Na tym etapie jedynym w pełni obronnym stanem końcowym jest postawienie serwera od nowa z czystego obrazu systemu, migracja zweryfikowanego, czystego kodu i danych oraz rotacja każdego sekretu, z traktowaniem pierwotnego hosta jako tymczasowego środowiska ograniczania szkód.
Czy dane kart klientów faktycznie zostały skradzione?
W tym incydencie wzorzec rozmiarów odpowiedzi w logach dostępu sugerował, że shell był używany do zapisywania plików, a nie masowego eksportu bazy danych, pełny zrzut tabeli klientów miałby setki kilobajtów i nic nawet się do tego nie zbliżało. Ale skimmer w przeglądarce działał prawie dwa dni, więc każdą kartę wpisaną w finalizacji zamówienia w tym oknie należy uznać za przechwyconą. Zakładaj ekspozycję, dopóki nie udowodnisz inaczej, i natychmiast rozpocznij zgłoszenie do operatora płatności. Sekwencja jest w naszym poradniku reagowania na wyciek danych.
Nadal jestem na 1.7.x i nie mogę zaktualizować sklepu przez kilka miesięcy. Co teraz?
Jesteś celem, a użyte tutaj exploity mają lata i są częścią automatycznych skanerów, ale w międzyczasie nie jesteś bezbronny. Wirtualnie załataj konkretne luki wskazane przez logi, zamknij origin za WAF-em, ogranicz środowisko uruchomieniowe PHP i dodaj monitoring integralności plików, żeby druga próba była głośna. Pełny awaryjny plan działania to zaawansowane utwardzanie dla sklepów, których nie możesz jeszcze zaktualizować, z bazą w checkliście utwardzania.
Czytaj dalej
- Zaawansowane utwardzanie PrestaShop dla sklepów, których nie możesz jeszcze zaktualizować, zamykanie wektora na poziomie aplikacji, który to czyszczenie ograniczyło, ale którego nigdy nie zamknęło.
- Utwardzanie bezpieczeństwa PrestaShop: kompletna checklista, pełna bazowa procedura, utrzymywana na bieżąco.
- Reakcja na wyciek danych: co zrobić, gdy twój sklep zostanie zhakowany, sekwencja pierwszych godzin, z obowiązkami RODO.