e-commerce

Card skimmer pe un magazin PrestaShop: cod activ doar la checkout

Client
Magazin online din România
Platformă
PrestaShop 1.7 (magazin online, mod catalog)
Infecție
Card skimmer în fișierele nucleu + sesiune de admin furată
Durată
o zi lucrătoare (curățare și securizare)
Downtime
zero (magazinul era deja căzut la preluare)

Un magazin online ne-a cerut upgrade-ul PrestaShop de la ramura 1.7.7 la 1.7.8. Înainte de orice upgrade facem o verificare de integritate: comparăm fiecare fișier al platformei cu sumele de control publicate oficial de producător. Nu e o formalitate — un upgrade aplicat peste o instalare compromisă duce codul malițios mai departe, în versiunea nouă, și îl face mult mai greu de găsit după aceea.

Verificarea a arătat că magazinul era deja compromis. Nu de câteva ore, ci de 18 zile.

„Fișierul a fost curățat și este acum sigur” — o afirmație falsă

Într-unul dintre fișierele de configurare ale platformei am găsit o notă lăsată de un instrument automat de curățare, care anunța că fișierul fusese dezinfectat. În același fișier, la câteva rânduri sub notă, se aflau încă două adrese de comandă-și-control codificate base64.

Instrumentul ștersese payload-ul — partea vizibilă, ușor de semnalat de un scanner — și lăsase pe loc mecanismul care îl încărca. Distincția asta decide totul: un skimmer are două componente, injectorul și payload-ul, iar ștergerea celui de-al doilea doar oprește temporar atacul. Injectorul rămâne funcțional, iar payload-ul poate fi rescris oricând, fără să fie nevoie de o nouă intrare pe server.

Exact asta era situația: raportul unui instrument automat spunea „curat”, iar magazinul era infectat.

Cum arăta skimmer-ul

În două fișiere centrale ale platformei — controlerele de bază ale front-office-ului — fusese adăugată o funcție care nu face parte din produsul original. Logica ei, în trei condiții:

  • vizitatorul are un coș activ;
  • vizitatorul nu este autentificat ca administrator;
  • adresa paginii conține unul dintre aproximativ 35 de cuvinte-cheie asociate procesului de comandă, în mai multe limbi — order, comanda, checkout, commande, bestellung, pedido și altele.

Dacă toate trei erau îndeplinite, funcția injecta în pagină conținutul decodificat al unui fișier cu extensie .png — un fișier care arăta ca o imagine, dar conținea cod.

Acesta este tiparul clasic al unui card skimmer. Fiecare condiție are un rost: se activează exclusiv în pașii de finalizare a comenzii, acolo unde se introduc datele de plată; rămâne invizibil pentru administratori, adică exact pentru oamenii care ar putea observa ceva; și își ține payload-ul separat, într-un fișier pe care atacatorul îl poate schimba oricând, fără să mai atingă codul platformei.

Data injectării, citită din marcajele de timp ale fișierelor: 31 iulie 2026, ora 22:41:52.

De ce nu s-a furat niciun card

Partea care schimbă evaluarea impactului: skimmer-ul nu a avut ce fura. Trei motive independente, în ordinea greutății lor.

  • Magazinul funcționa în mod catalog. Comenzile nu se puteau plasa deloc — pașii de finalizare pe care codul îi pândea nu existau operațional.
  • Nu se procesau plăți cu cardul. Singurele metode de plată instalate erau ramburs la livrare și ordin de plată bancar. Pe acest magazin nu se introduc date de card, indiferent de mod.
  • Payload-ul era gol. Ambele fișiere de payload aveau 0 octeți la momentul analizei — cel mai probabil neutralizate de scanerul de securitate al serverului.

Nuanța care ne împiedică să tratăm cazul ca pe unul minor: injectorul era intact și complet funcțional. Ar fi redevenit activ în clipa în care oricare dintre fișierele de payload ar fi fost rescris — iar rescrierea lor nu cerea o nouă intrare pe server, pentru că atacatorul avea deja una. Despre ea, mai jos.

Sesiunea de administrator care a trăit 17 zile

Analiza jurnalelor de acces ale serverului a scos la iveală un tipar care nu poate fi explicat prin activitate legitimă. Un singur client, cu următorul comportament:

  • activ între 31 iulie și 16 august, continuu;
  • 38.695 de cereri către directorul de administrare — o cerere la aproximativ 40 de secunde, 24 de ore din 24, 17 zile consecutiv;
  • user-agent Firefox/99.0 — o versiune lansată în 2022, depășită cu circa patru ani;
  • zero pagini de administrare accesate efectiv — exclusiv punctul de intrare;
  • zero resurse solicitate: niciun fișier CSS, niciun JavaScript, nicio imagine.

Am verificat experimental ce răspuns primește o cerere fără sesiune validă: 302, cu corp gol. Cererile din jurnal primeau 200, cu corpuri de 86 și 54 de octeți — răspunsuri JSON specifice unei interfețe de administrare autentificate. Sesiunea era, deci, validă.

Un om care folosește un browser real încarcă pagini, stiluri, imagini, navighează prin meniuri. Acest client nu a făcut nimic din toate acestea: a interogat, la interval fix, un singur punct de acces. Este tiparul unui program automat care menținea vie și verifica periodic o sesiune de administrare furată — un mecanism de persistență, nu o activitate de lucru.

Pentru comparație, sesiunile legitime ale angajaților apar în același jurnal cu un browser în versiune curentă și totalizează 96 de cereri. Nouăzeci și șase, față de treizeci și opt de mii.

Factorul care a făcut posibilă durata: durata de viață a sesiunilor de administrare era configurată la 480 de ore — 20 de zile. De aceea o sesiune a putut supraviețui 17 zile fără nicio reautentificare.

De unde venea parola

Rămânea întrebarea de la care pornește orice investigație serioasă: cum a intrat atacatorul? Răspunsul se afla în rădăcina publică a site-ului.

Un export complet al bazei de date — un fișier .sql de 74 MB, datat 2022 — stătea direct în directorul public, cu drepturi de citire pentru toată lumea și fără nicio regulă care să blocheze accesul. Era descărcabil de oricine cunoștea sau ghicea adresa.

Conținea, printre altele, tabelul conturilor de administrare, cu hash-urile parolelor.

Am verificat apoi data ultimei schimbări de parolă pentru cele trei conturi de administrare existente. Toate trei datau din 2021 și nu fuseseră schimbate niciodată de atunci. Prin urmare, hash-urile aflate în exportul public din 2022 erau exact aceleași care protejau conturile și în ziua intervenției.

De aici rezultă o explicație coerentă a întregului incident:

Export de bază de date descărcabil public → obținerea hash-urilor de parolă ale administratorilor → spargerea unei parole → autentificare perfect legitimă în zona de administrare → injectarea codului skimmer → menținerea sesiunii ca mecanism de persistență.

Ținem să fim expliciți: această înlănțuire este o ipoteză, nu un fapt demonstrat. Jurnalele de acces nu se păstrează pe server mai mult de aproximativ trei săptămâni, deci nu putem dovedi descărcarea efectivă a fișierului. Este însă singura ipoteză care explică simultan toate observațiile, iar fiecare verigă a ei este confirmată individual.

Un upgrade abandonat la jumătate

Peste toate acestea, chiar în ziua intervenției, o încercare de upgrade fusese pornită și abandonată la jumătate, cu 17 minute înainte. Ce am găsit:

  • panoul de administrare returna eroare fatală la fiecare accesare — memoria cache a framework-ului indica un container de servicii inexistent;
  • magazinul fusese pus în mentenanță, fără nicio adresă IP în lista albă — inaccesibil pentru absolut toată lumea, inclusiv pentru proprietari;
  • modulul de upgrade rămăsese într-o stare coruptă: peste o versiune recentă fusese dezarhivată una mult mai veche, rezultând un amestec nefuncțional din două generații de cod;
  • procedura fusese configurată cu opțiunea de backup dezactivată — urma să ruleze fără nicio posibilitate de revenire.

Cu alte cuvinte, magazinul era căzut înainte să ajungem noi la el. Prima parte a intervenției a fost să-l repunem în funcțiune.

Cronologia

2021
Se instalează platforma. Parolele celor trei conturi de administrare sunt stabilite atunci — și nu vor mai fi schimbate niciodată.
2022
Un export complet al bazei de date apare în rădăcina publică a site-ului. Rămâne acolo, descărcabil de oricine, aproximativ patru ani.
decembrie 2025
Jurnalul de erori înregistrează o tentativă de exploatare prin mecanismul de cache al motorului de șabloane. Rămân în urmă opt fișiere-sondă goale, împrăștiate prin mai multe directoare.
31 iulie 2026, 00:12
Începe activitatea sesiunii de administrare neautorizate. Posibil și mai devreme — jurnalele nu merg mai în urmă.
31 iulie 2026, 22:41:52
Codul skimmer este injectat în patru fișiere nucleu, în aceeași secundă.
1–16 august 2026
Sesiunea neautorizată continuă, cu aproximativ 2.000 de cereri pe zi.
16 august 2026, 12:39
Ultima activitate a sesiunii. Nu putem stabili dacă a expirat de la sine sau dacă a fost abandonată.
18 august 2026, 13:01
Încercarea de upgrade pornește și se oprește la jumătate. Magazinul rămâne nefuncțional.
18 august 2026, 13:30
Începe intervenția noastră: backup, curățare, securizare, verificare.

Curățarea, pas cu pas

Înainte de orice modificare — și înainte chiar de a repara panoul de administrare — am făcut copii de siguranță complete, verificate:

  • un export al bazei de date, confirmat prin numărul de tabele și prin prezența marcajului de finalizare;
  • o arhivă completă a fișierelor, aproximativ 1 GB, testată prin citire integrală;
  • o copie a configurației de server originale.

Apoi, în ordine:

  • am șters memoria cache coruptă a framework-ului (704 MB) — panoul de administrare a redevenit funcțional;
  • am restaurat patru fișiere nucleu din sursele oficiale ale versiunii instalate, verificând fiecare prin sumă de control MD5 după scriere, față de manifestul oficial de integritate — nu am „curățat” codul injectat, l-am înlocuit cu originalul;
  • am șters cele două fișiere de payload și cele opt fișiere-sondă rămase din tentativa din decembrie;
  • am mutat exportul bazei de date în afara zonei publice, cu drepturi restrânse la administratorul serverului;
  • am păstrat toate fișierele infectate originale, separat, ca probă pentru eventuale analize ulterioare.

Al patrulea fișier restaurat merită o mențiune. Singura lui diferență față de original era o linie goală rămasă în interiorul unei funcții — exact locul din care instrumentul automat de curățare tăiase codul injectat. Fusese modificat în aceeași secundă cu celelalte trei. Fără comparația cu manifestul oficial, acea linie goală ar fi trecut complet neobservată.

Securizarea

  • 128 de directoare și 132 de fișiere aveau permisiuni 0777 — scriere permisă oricărui utilizator de pe server. Pe o găzduire partajată, asta înseamnă că orice alt cont de pe aceeași mașină putea modifica fișierele magazinului. Le-am readus la 755, respectiv 644.
  • Am adăugat o regulă de server care blochează accesul public la fișiere de tip .sql, .log, .zip, .tar, .bak și altele similare. Detaliu important: regula a fost plasată în afara marcajelor gestionate automat de platformă, ca să supraviețuiască regenerării fișierului de configurare. O regulă corectă, dar plasată greșit, dispare la prima salvare din panoul de administrare.

Verificările de la final

O curățare e completă doar dacă poate fi demonstrată. Verificările noastre, toate trecute:

  • cele 22.090 de fișiere nucleu, comparate integral cu manifestul oficial al versiunii: 2 modificate, 0 lipsă
  • cele 2 modificate — personalizări proprii, intenționate și documentate, fără legătură cu infecția
  • 0 apariții ale semnăturilor codului malițios (numele funcției injectate și ale celor două fișiere de payload)
  • 0 apariții ale tiparelor generice de webshell
  • 0 fișiere-sondă rămase
  • 0 căi cu permisiuni de scriere publică
  • 0 exporturi de bază de date în zona publică
  • baza de date curată — 3 conturi de administrare, toate legitime; niciun cont de acces prin webservice; nicio configurare cu cod injectat
  • panou de administrare funcțional
  • magazin public testat integral — pagina principală, categorie, produs, contact, autentificare, căutare

Testarea magazinului public s-a făcut autorizând temporar exclusiv adresa IP a serverului, apoi revocând autorizarea. Configurația magazinului a rămas exact așa cum am găsit-o.

Ce i-am recomandat clientului

Prioritatea întâi, de făcut imediat:

  • Invalidarea tuturor sesiunilor active, prin rotirea cheilor de criptare a cookie-urilor. Efect imediat: orice sesiune existentă, inclusiv una eventual furată, devine invalidă. Nu presupune schimbarea niciunei parole și durează câteva minute.
  • Schimbarea parolelor celor trei conturi de administrare. Parolele datează din 2021, iar hash-urile lor au fost public descărcabile. Algoritmul folosit este solid, dar asta protejează împotriva spargerii rapide — nu împotriva a patru ani de timp disponibil.
  • Reducerea duratei de viață a sesiunilor de la 480 de ore la 8–12 ore.

Pe termen scurt: finalizarea upgrade-ului platformei — care închide și vulnerabilitatea de execuție de cod prin cache-ul motorului de șabloane, CVE-2022-31181, cea exploatată în decembrie 2025; actualizarea temei comerciale la o versiune compatibilă; schimbarea parolelor de găzduire, FTP și bază de date; configurarea unui sistem automat de backup, cu copii păstrate în afara serverului.

Pe termen mediu: migrarea către o ramură majoră care încă primește actualizări de securitate — versiunea intermediară e un pas obligatoriu, nu o destinație. Și, la fel de important pentru orice investigație viitoare: transmiterea adresei IP reale a vizitatorilor de la CDN către server. În configurația actuală, toate jurnalele înregistrează adresele CDN-ului, nu ale vizitatorilor — motivul pentru care nu am putut identifica adresa IP reală a atacatorului. Împreună cu o perioadă mai lungă de păstrare a jurnalelor, e diferența dintre a ști ce s-a întâmplat și a deduce.

Ce merită reținut

Trei lecții, niciuna specifică acestui magazin:

  • Un raport automat de curățare nu este o curățare verificată. Instrumentul care declarase fișierul „sigur” lăsase în urmă atât injectorul, cât și adresele de comandă-și-control.
  • Un fișier uitat în rădăcina publică nu îmbătrânește frumos. Un export de bază de date făcut „temporar”, în 2022, a rămas descărcabil patru ani — și conținea exact acele hash-uri de parole care erau încă valabile.
  • O parolă nerotată transformă o breșă veche într-una actuală. Dacă parolele ar fi fost schimbate oricând între 2022 și 2026, expunerea ar fi rămas un incident istoric, fără consecințe.

Un caz cu un mecanism înrudit — execuție de cod prin cache-ul motorului de șabloane, pe un alt magazin — e descris în studiul de caz despre webshell-ul găsit după o alertă falsă. Ce facem, concret, într-o intervenție de acest tip: curățare malware PrestaShop.

Anexă tehnică — indicatori de compromitere

Pentru administratorii și colegii din industrie care vor să-și verifice propriile magazine:

Funcție injectată
jschecks($html, $p), adăugată în classes/controller/FrontController.php și classes/controller/Controller.php
Fișiere de payload
img/eCrwG.png și js/FH5ot.js — extensii de imagine și script, conținut codificat; găsite goale (0 octeți)
Adrese comandă-și-control
8.159.150.250 și 8.159.139.203, codificate base64 în config/alias.php, alături de o notă falsă de „fișier curățat”
Condiție de activare
coș activ + vizitator neautentificat ca admin + URL conținând unul dintre ~35 de cuvinte-cheie de checkout multilingve
Fișiere-sondă
fișiere PHP de dimensiune 0, cu nume de tip hash MD5 pe 32 de caractere, în rădăcină și în pdf/, js/, modules/, themes/, webservice/, upload/
Sesiune neautorizată
user-agent Mozilla/5.0 (Windows NT 10.0; rv:99.0) Gecko/20100101 Firefox/99.0; cereri exclusiv către punctul de intrare al zonei de administrare, la ~40 de secunde, fără resurse statice
Unealtă de atac observată
python-requests/2.32.4, în fereastra injectării
Verificare recomandată
compararea integrală a fișierelor nucleu cu manifestul oficial de integritate al versiunii instalate — singura metodă care a scos la iveală și fișierul modificat doar cu o linie goală
Verificare recomandată (expunere)
căutarea fișierelor .sql, .zip, .tar.gz și .bak în zona publică, plus a directoarelor și fișierelor cu permisiuni 0777

Site-ul tău trece prin ceva similar?

Îl curățăm și îl securizăm într-o singură intervenție, de la 250 lei, cu garanție 30 de zile. Diagnosticul e gratuit, fără obligații.

Cere diagnostic gratuit

Alte studii de caz

Citește și