Caso studio reale su un e-commerce PrestaShop bucato tramite ps_facetedsearch: analisi delle webshell ps_tools.php e ps_reader.php e guida alla bonifica.

Hacking PrestaShop: come ho individuato e rimosso una webshell nel Back Office

26-10-2026

All’inizio di ottobre un cliente mi contatta: puoi controllare il mio sito web? non riesco più ad accedere al back office.

Vado subito a controllare: il frontend era intatto, ma nel back office visualizzo una pagina bianca con questo JSON a schermo:

{"ok":0,"o":"","e":"dW5rbm93biBvcCA="}

Capisco subito che qualcosa non va e che non è un problema da sottovalutare, mi rimbocco le maniche e mi metto subito all’opera.

Un po' di contesto e importanza della manutenzione

Il sito web in questione è un e-commerce che vende prodotti fisici e che riceve svariati ordini ogni mese. Non è un sito web abbandonato, più o meno un paio di volte al mese entro nell’admin e aggiorno il core di PrestaShop e i plugin per tenere tutto in sicurezza, alla fine faccio qualche verifica generale di funzionamento.

Nonostante io sia costante e attento, è successo. Nessuna app esposta ad internet è mai al sicuro al 100%. La recente scoperta di php2shell e node2shell ne sono appunto la prova. Il sito in questione purtroppo è stato bucato e quasi sicuramente c’è stata una fuga di dati. Dopo il mio intervento, ho avvisato il cliente delle operazioni effettuate, fornendogli tutti i log estratti e un report forense completo: sulla base di questi dati, dovrà ora notificare il data breach agli utenti registrati e al Garante Privacy.

Voglio anche sottolineare un altro concetto importante. Non bisogna aspettare che un sito abbia centinaia di migliaia di visualizzazioni o vendite (nel caso di un e-commerce) per iniziare a pensare alla messa in sicurezza. Ogni sito, anche se poco visitato o che vende poco DEVE essere messo in sicurezza e controllato periodicamente.

In questo articolo ti farò vedere tutti i passaggi che ho effettuato per risalire a quando è avvenuto l’attacco e come, infine cosa ho fatto per rimettere in sicurezza il sito.

Il contenuto di questo articolo è puramente a scopo didattico, affinchè chiunque possa difendersi da attacchi di questo tipo. È assolutamente vietato utilizzare il seguente materiale per scopi differenti da testing o studio.

Analisi forense: da ps_facetedsearch alle backdoor ps_tools.php e ps_reader.php

Osservando la stringa dW5rbm93biBvcCA= intuisco che possa trattarsi di una stringa in base64 e con una semplice operazione di decodifica ottengo il significato unknown op. Probabilmente sarà un parametro GET o POST che l’attaccante avrà reso obbligatorio per blindare il back office.

Il cliente mi ha riferito che circa 3 giorni prima ha ricevuto un ordine che ha gestito quindi cerco i file modificati negli ultimi 3 giorni.

find /var/www/html/ -type f -name "*.php" -mtime -3
...
<cutted>
...
/var/www/html/modules/ps_tools/ps_tools.php
/var/www/html/modules/ps_tools/ps_reader.php
/var/www/html/modules/ps_facetedsearch/src/Filters/Block.php
/var/www/html/themes/classic/ps_reader.php

Ho tagliato l’output perchè oltre ai file che ho lasciato c’erano tutti i file di cache, probabilmente l’installazione di nuovi moduli ha triggerato lo svuotamento della cache.

Cerco anche qualcosa che contenga l’errore che ho decodificato: unknown op

grep -rnw "unknown op" /var/www/html/   
/var/www/html/modules/ps_tools/ps_tools.php:257:psb_out(false, '', 'unknown op ' . $op);

Non ho molti dubbi, l’attaccante ha sfruttato una vulnerabilità nota di ps_facetedsearch, che è un modulo ufficiale di PrestaShop.

I file totalmente estranei sono 3 (ps_tools.php e ps_reader.php replicato 2 volte in due posizioni diverse), un file già esistente è stato modificato. Cerco di capire cosa c’è in ps_tools.php e trovo conferma di quel che immaginavo.

head -n 20 ps_tools.php
<?php
/**
 * ps maintenance bridge -- single-file prestashop-aware remote helper.
 * silently returns an empty page unless called with the right key.
 */
@error_reporting(0);
@ini_set('display_errors', '0');
@set_time_limit(0);

$PSB_KEY = 'ee88bbfbc1dd0a3a36a5';
$PSB_V   = 8;

function psb_b64($s) { return base64_encode((string)$s); }
function psb_out($ok, $o = '', $e = '', $m = null) {
    $r = array('ok' => $ok ? 1 : 0, 'o' => psb_b64($o));
    if ($e !== '') $r['e'] = psb_b64($e);
    if ($m !== null) $r['m'] = $m;
    echo json_encode($r);
    exit;
}

In chiaro nelle prime righe del file c’è la chiave che serve a “entrare” nell’admin ormai bloccato. Ero curioso di capire in dettaglio la funzionalità dei due moduli, quindi li ho scaricati e dati in pasto a Gemini e mi ha detto:

ps_tools.php (La backdoor C2 / Remote Helper). È uno script di controllo remoto progettato su misura per PrestaShop. Agisce in modalità “silenziosa”: se non viene chiamato con una chiave segreta specifica ($PSB_KEY), restituisce un errore mascherato in JSON. Una volta autenticato, risale automaticamente fino alla root del sito, legge le credenziali del database (anche da file protetti come parameters.php) e si connette al MySQL. Da quel momento consente all’attaccante di eseguire query SQL arbitrarie sul DB dello store, eseguire comandi shell sul server (se attivi) o codice PHP arbitrario tramite eval, e manipolare data e ora dei file sul disco (timestomping) per nascondere le tracce.

ps_reader.php (Il Web File Manager “Zero-Shell”). È un vero e proprio esploratore file autonomo dotato sia di interfaccia grafica via browser che di API JSON per essere controllato via script. La sua peculiarità è che non utilizza alcuna funzione di shell di sistema (exec, system, ecc.), ma opera esclusivamente tramite le funzioni native di filesystem di PHP (file_get_contents, file_put_contents, scandir). Questo gli permette di funzionare anche su server con protezioni rigide sul php.ini. Include funzionalità per navigare le cartelle, scaricare sorgenti, creare o cancellare file e, soprattutto, un editor riga-per-riga che consente all’attaccante di modificare file legittimi al volo senza doverli riscrivere per intero.

La combinazione dei due plugin è un kit d’intrusione a due livelli: ps_reader per manipolare i file con tanto di GUI, ps_tools serve per operare nel database ed eseguire istruzioni remote tramite API.

ps_reader malware gui file manager
ps_reader: il file manager malefico (click per ingrandire)

Rimozione del Super Admin malevolo e dell’errore reset_password_token

Non posso limitarmi semplicemente a cancellare i due plugin e dormire sereno, è necessaria un’analisi approfondita. Mi riapproprio del back office e controllo immediatamente se ci sono utenti sospetti e in effetti noto un nuovo Super Admin che ha un’identità non nota. Provo subito a disabilitarlo da interfaccia grafica ma incontro questo errore: La proprietà Employee->reset_password_token non è valida [PrestaShopException 0]

Errore PrestaShop Employee reset_password_token non valida durante rimozione admin malware
Errore del back office quando disabilito l'utente (click per ingrandire)

Devo agire direttamente sul database, individuo l’id dell’utente malefico, lo “riparo” e poi lo cancello.

require "/var/www/html/config/config.inc.php";
$rows = Db::getInstance()->executeS("SELECT id_employee, firstname, lastname, email, active, reset_password_token FROM "._DB_PREFIX_."employee");
print_r($rows);                                                                                                                          
Array
(
    <cutted>
    [3] => Array
        (
            [id_employee] => 4
            [firstname] => Marco
            [lastname] => 
            [email] => marco@<nome-dominio>.it
            [active] => 1
            [reset_password_token] => c6cd75c2e72cc8c1580763fbab18ff69
        )
)

È l’utente con id 4, vediamo quando si è connesso.

require "/var/www/html/config/config.inc.php";
$sql = "SELECT s.id_employee_session, s.id_employee, e.firstname, e.email, s.date_add, s.date_upd
        FROM " . _DB_PREFIX_ . "employee_session s
        LEFT JOIN " . _DB_PREFIX_ . "employee e ON s.id_employee = e.id_employee
        ORDER BY s.date_upd DESC";
$rows = Db::getInstance()->executeS($sql);

foreach ($rows as $r) {
  printf("Session ID: %d | Employee: [%d] %s (%s) | Created: %s | Updated: %s\n",
    $r["id_employee_session"],
    $r["id_employee"],
    $r["firstname"],
    $r["email"],
    $r["date_add"],
    $r["date_upd"]
  );
}'
Session ID: 285 | Employee: [4] Marco (marco@<nome-dominio>.it) | Created: 2026-10-06 06:50:04 | Updated: 2026-10-06 06:50:05

Session ID: 284 | Employee: [4] Marco (marco@<nome-dominio>.it) | Created: 2026-10-06 06:38:34 | Updated: 2026-10-06 06:38:35

Session ID: 283 | Employee: [4] Marco (marco@<nome-dominio>.it) | Created: 2026-10-06 05:52:51 | Updated: 2026-10-06 05:52:51

Session ID: 282 | Employee: [4] Marco (marco@<nome-dominio>.it) | Created: 2026-10-06 05:24:35 | Updated: 2026-10-06 05:24:35

Session ID: 281 | Employee: [4] Marco (marco@<nome-dominio>.it) | Created: 2026-10-06 05:22:08 | Updated: 2026-10-06 05:22:09

Session ID: 280 | Employee: [4] Marco (marco@<nome-dominio>.it) | Created: 2026-10-06 04:31:14 | Updated: 2026-10-06 04:31:14 

Gli orari delle sessioni di questo utente coincidono con il giorno e ora in cui è avvenuta anche l’installazione dei plugin malefici. Pulisco tutte le sessioni attive.

require "/var/www/html/config/config.inc.php";
Db::getInstance()->execute("TRUNCATE TABLE " . _DB_PREFIX_ . "employee_session");

Infine riparo e disattivo l’utente malefico.

require "/var/www/html/config/config.inc.php";
Db::getInstance()->execute("UPDATE "._DB_PREFIX_."employee SET reset_password_token = NULL, reset_password_validity = NULL, active = 0 WHERE id_employee = 4");

Ho preferito rimuovere l’utente direttamente dal back office per verificarne il corretto funzionamento.

Bonifica del malware, diff dei backup e messa in sicurezza

Lo so, questo articolo è molto lungo, tecnico e dettagliato. Ci siamo quasi però, resisti ancora un po' 😉

Le ultime cose importanti da fare sono:

  1. controllare se hanno alterato le credenziali di pagamento per fregarsi i soldi degli ordini successivi
  2. verificare che nel database non siano stati iniettati hook malevoli o altri payload
  3. ripristinare il plugin infetto e aggiornarlo per patchare la vulnerabilità che ha permesso l’intrusione
  4. reset delle password e delle api key

Per queste operazioni non esiste una pratica veloce e facile, però il sito è sotto backup. Ho scaricato un backup precedente all’attacco e l’ho confrontato file per file escludendo solo la cartella di cache, ho effettuato anche il diff con il dump del database nel momento in cui ho effettuato l’analisi.

La parte più tediosa è stata rigenerare e aggiornare le api key dei servizi utilizzati dal sito.

Per fortuna non hanno alterato altro. L’idea che mi sono fatto è che tutto questo è servito solo per rubare le email degli utenti per inviare spam o phishing.

Analisi del malware PrestaShop su GitHub

Puoi trovare il codice dei plugin spia su GitHub. Al suo interno c’è un docker compose che ti permette di verificare il comportamento dei plugin in locale. Con un po' di reverse engineering ho creato un file che “testa” le API che espone ps_tools.php.

Ecco il link al repository GitHub con l'analisi del malware PrestaShop.

Questo è l’output del codice Python che testa le potenzialità della backdoor:

python3 test_malware.py 
============================================================
  PRESTASHOP MALWARE LOCAL INSPECTOR (PYTHON)
============================================================
Target URL:    http://localhost:8080
Backdoor Key:  ee88bbfbc1dd0a3a36a5

============================================================
[1] ANALISI GATE DI PROTEZIONE STEALTH (ps_tools.php)
============================================================
[*] Chiamata GET senza parametri:
    Status: 200
    Body:   '{"ok":0,"o":"","e":"dW5rbm93biBvcCA="}' (silenzioso o errore generico)
[*] Chiamata con chiave errata ('invalid_key_123'):
    Risposta: ok=0, error='k' (accesso negato)

============================================================
[2] HANDSHAKE E PROTOCOLLO BACKDOOR (op=ping)
============================================================
[+] Handshake riuscito!
    Stato:         OK (Attivo)
    Payload decodificato: 'pong'
    Versione backdoor (v): 8
    Versione PHP host:     8.1.33

============================================================
[3] RICOGNIZIONE SERVER E FURTO CREDENZIALI (op=env)
============================================================
[+] Informazioni ambiente estratte dal malware:
    Shop Root:        /var/www/html
    File script:      /var/www/html/modules/ps_tools/ps_tools.php
    Utente web:       www-data
    disable_functions:pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,
    Funzioni shell:   system, exec, shell_exec, passthru, popen, proc_open

[!] Configurazione Database trafugata da PrestaShop:
    Host:     db
    Database: prestashop
    User:     prestashop
    Password: [presente, lunghezza: 13c]
    File:     /var/www/html/app/config/parameters.php

============================================================
[4] ESECUZIONE COMANDI ARBITRARI (op=exec)
============================================================
[*] Comando inviato: 'id; uname -a'
[+] Risposta dal server (stdout):
----------------------------------------
uid=33(www-data) gid=33(www-data) groups=33(www-data)
Linux 317cb8c95765 7.0.14-linuxkit #1 SMP PREEMPT_DYNAMIC Thu Sep 24 15:21:18 UTC 2026 x86_64 GNU/Linux
----------------------------------------

============================================================
[5] INTERROGAZIONE DIRETTA DATABASE (op=sql)
============================================================
[+] Query eseguita con successo: 'SELECT id_employee, firstname, lastname, email FROM ps_employee LIMIT 5'
    Record estratti (1):
    - ID: 1 | Nome: John Doe | Email: admin@prestashop.local

============================================================
[6] WEBSHELL GRAFICA / FILE MANAGER (ps_reader.php)
============================================================
[+] Modulo  (modules/ps_tools):
    URL:    http://localhost:8080/modules/ps_tools/ps_reader.php
    Stato:  ATTIVO (Web GUI accessibile senza auth)
[+] Tema    (themes/classic):
    URL:    http://localhost:8080/themes/classic/ps_reader.php
    Stato:  HTTP 403

============================================================
[✓] Analisi completata.
============================================================

Alla fine tutto è ritornato alla normalità: dopo aver debellato ogni traccia dell’intrusione e patchato la vulnerabilità, il sito è tornato online in sicurezza.

Consulenza, Sicurezza & Incident Response

Gestisci un e-commerce o una piattaforma web e hai il dubbio che possa essere stata compromessa?

Offro supporto tecnico e consulenza specialistica:

  • Analisi forense, identificazione di webshell e indicatori di compromissione (IoC).
  • Bonifica da malware, ripristino dell’integrità del database e chiusura delle falle di sicurezza.
  • Messa in sicurezza e hardening di stack CMS (PrestaShop, WordPress) e infrastrutture web.

Contattami per un audit di sicurezza o per richiedere un intervento d’urgenza.

Antonio Porcelli @progressify

Antonio Porcelli

@progressify

Commenta l'articolo