36 Pacchetti npm Malevoli Travestiti da Plugin Strapi: Ricostruzione Red Team dell'Attacco Supply Chain a Guardarian
Premessa: Perché Leggere dal Punto di Vista dell'Attaccante
Questo articolo è scritto in una forma diversa dal solito. Invece di descrivere un incidente dall'esterno, ricostruiamo la logica operativa dall'interno: ogni fase è narrata dal punto di vista di chi ha progettato l'attacco. Non è glorificazione. È reverse engineering del pensiero avversario, lo stesso metodo che i red team professionisti usano per anticipare le mosse di chi attacca. Se capite come ragiona l'attaccante, capite dove si rompe la catena difensiva. Ogni sezione "attaccante" è seguita da un box difensivo che riporta la prospettiva al lato blu.
Il caso è reale: tra il 14 e il 15 aprile 2026, 36 pacchetti npm malevoli mascherati da plugin per Strapi CMS sono stati pubblicati sul registry npm. Il bersaglio finale era Guardarian, piattaforma di scambio criptovalute. L'operazione ha attraversato otto varianti di malware in tredici ore, partendo da credential harvesting generico e arrivando a esecuzione fileless con persistenza crontab. Per le agenzie web e le startup italiane che usano Strapi quotidianamente, questo caso non è accademico: è un promemoria che la supply chain npm è un campo di battaglia attivo.
Fase 1 — Ricognizione: Mappare il Bersaglio Prima di Scrivere una Riga di Codice
*Sono seduto davanti al terminale e il mio obiettivo è chiaro: Guardarian. Una piattaforma crypto con infrastruttura basata su Strapi. Come lo so? Le offerte di lavoro su LinkedIn menzionano Strapi e Node.js. I loro repository GitHub hanno fork di plugin Strapi. Le risposte DNS e i certificati TLS rivelano hostname come "prod-strapi". I loro job posting cercano esperienza con PostgreSQL e Redis. Ogni pezzo di informazione pubblica è un tassello del puzzle infrastrutturale. Non ho bisogno di un exploit zero-day. Ho bisogno di capire come funziona la loro pipeline di deployment.*
*Cerco nei commenti delle issue GitHub, nelle discussioni su Stack Overflow, nei profili npm dei loro sviluppatori. Trovo riferimenti a Jenkins CI nelle convenzioni di naming dei branch. Trovo path come /opt/secrets/ e /var/www/nowguardarian-strapi/ in snippet di configurazione condivisi pubblicamente. Trovo nomi di database: guardarian, guardarian_payments, exchange, custody. Non ho ancora scritto una riga di codice malevolo, ma conosco già l'architettura del mio bersaglio meglio di molti dei loro sviluppatori junior.*
Difesa — Fase Ricognizione: Ogni organizzazione dovrebbe condurre un audit periodico della propria esposizione OSINT. Cercate il nome della vostra azienda combinato con nomi di tecnologie (Strapi, PostgreSQL, Redis, Jenkins) su GitHub, Stack Overflow, LinkedIn. Rimuovete o anonimizzate i riferimenti a path interni, nomi di database, hostname di produzione. Formate gli sviluppatori a non condividere snippet di configurazione con dati reali. Per le agenzie italiane che usano Strapi: verificate che i vostri repository pubblici non contengano riferimenti a infrastruttura di produzione dei clienti.
Fase 2 — Preparazione dell'Arsenale: Creare Identità e Pacchetti
*Il registry npm non verifica le identità. Creo quattro account in pochi minuti: umarbek1233, kekylf12, tikeqemif26, umar_bektembiev1. Quattro, non uno, perché la distribuzione su più account rende più difficile il takedown simultaneo e simula un ecosistema organico di contributor indipendenti. Se npm sospende un account, gli altri tre continuano a servire pacchetti.*
*La naming convention è il cuore dell'inganno. Tutti i pacchetti si chiamano strapi-plugin-qualcosa: strapi-plugin-cron, strapi-plugin-events, strapi-plugin-seed, strapi-plugin-api, strapi-plugin-database, strapi-plugin-server, e altri trenta. Nomi plausibili, generici, che uno sviluppatore potrebbe cercare quando ha bisogno di estendere Strapi. La versione di partenza è 3.6.8 — un numero che suona maturo, stabile, non una 0.1.0 che griderebbe "progetto nuovo". Aggiungo descrizioni credibili, keyword corrette, un README minimale. Chi cerca "strapi plugin cron" su npm troverà il mio pacchetto mescolato ai risultati legittimi.*
*Ma il vettore primario non è il typosquatting passivo. Il mio piano è più mirato: so che gli sviluppatori di Guardarian installano plugin Strapi. Se riesco a far apparire i miei pacchetti nelle dipendenze di un progetto interno — attraverso una pull request, una raccomandazione in una chat, o semplicemente sperando che qualcuno li trovi cercando — il danno è fatto prima che chiunque legga una riga di codice.*
Difesa — Fase Preparazione: Implementate una policy di allowlist per i pacchetti npm. Strumenti come npm audit signatures, Socket.dev, e Snyk possono rilevare pacchetti nuovi con naming sospetto. Configurate il vostro registry privato (Verdaccio, Artifactory, GitHub Packages) come proxy con whitelist: solo i pacchetti esplicitamente approvati possono essere installati. Per ogni nuovo pacchetto Strapi, verificate: data di pubblicazione, numero di download, autore, repository sorgente. Se un pacchetto strapi-plugin-* ha meno di 100 download e meno di una settimana di vita, trattatelo come sospetto fino a prova contraria.
Fase 3 — Il Vettore: postinstall Come Arma a Zero Click
*Ecco il dettaglio che rende npm un vettore così elegante: il campo scripts.postinstall nel package.json. Qualsiasi comando inserito in quel campo viene eseguito automaticamente quando qualcuno digita npm install. Non c'è prompt, non c'è conferma, non c'è sandbox. Il comando gira con i privilegi dell'utente che ha lanciato npm, che nella maggior parte degli ambienti di sviluppo e in troppi ambienti CI/CD è root o ha accesso a variabili d'ambiente sensibili. È un'esecuzione a zero click: l'atto stesso di installare il pacchetto è l'atto di eseguire il mio codice.*
*Nel mio package.json, il postinstall punta a uno script che sembra innocuo a un'occhiata superficiale. Ma il payload reale è offuscato, codificato in base64, concatenato da più stringhe, e ricostruito a runtime. Un reviewer distratto vede una riga di setup. Un reviewer attento vede una bomba.*
Il hook postinstall di npm è il singolo vettore più abusato negli attacchi supply chain JavaScript degli ultimi tre anni. Ogni organizzazione che usa npm dovrebbe: (1) impostare --ignore-scripts come default globale e abilitare gli script solo per pacchetti verificati; (2) utilizzare npm audit prima di ogni installazione; (3) verificare manualmente il package.json di ogni nuova dipendenza prima dell'installazione in CI/CD; (4) considerare l'adozione di Deno o Bun per progetti nuovi, che non eseguono script postinstall per default.
Fase 4 — Evoluzione Rapida: Otto Varianti in Tredici Ore
*L'operazione non è statica. Nell'arco di tredici ore pubblico otto varianti distinte del malware, ciascuna più raffinata della precedente. È sviluppo agile applicato al codice malevolo: ogni variante risponde a un feedback — reale o anticipato — sulla rilevabilità della precedente.*
Varianti 1-5: Raccolta Credenziali e Profilazione Ambiente
*Le prime cinque varianti sono esplorazioni. Raccolgono variabili d'ambiente — DATABASE_URL, REDIS_URL, AWS_SECRET_ACCESS_KEY, tutto quello che il processo Node.js può vedere — e le inviano al mio server C2 all'indirizzo 144.31.107.231 via HTTP in chiaro. Sì, in chiaro. Non è pigrizia: è pragmatismo. HTTPS richiede certificati, e un certificato TLS lascia tracce in Certificate Transparency logs che potrebbero attirare l'attenzione di ricercatori. HTTP puro su una porta alta è rumore di fondo nel traffico di un server di sviluppo. Nessuno monitora il traffico HTTP in uscita da un container di build npm.*
*Ogni variante aggiunge un livello di profilazione: hostname, utente corrente, path del filesystem, processi in esecuzione, contenuto di /etc/hostname. Sto costruendo una mappa dell'ambiente target prima di sferrare il colpo chirurgico.*
Variante 6 (strapi-plugin-seed): Il Colpo Chirurgico al Database
*Con la variante 6 cambio strategia. Non raccolgo più credenziali generiche: inserisco direttamente nel codice le credenziali PostgreSQL che ho già ottenuto dalle varianti precedenti o dalla ricognizione. Il malware tenta connessioni dirette a database con nomi specifici: guardarian, guardarian_payments, exchange, custody. Non sono nomi inventati — sono i nomi reali dei database di produzione di Guardarian. Se la connessione riesce, il malware esegue query di enumerazione: lista delle tabelle, schema, prime righe di ogni tabella. Il dump viene inviato al C2.*
*Questa variante è la prova che l'operazione non è opportunistica. È targetizzata. Un attacco opportunistico raccoglie tutto e filtra dopo. Io so già cosa cerco e dove cercarlo. I nomi dei database sono hardcodati nel mio codice perché li ho scoperti prima di iniziare.*
Difesa — Fase Accesso Database: Le credenziali di database di produzione non devono mai essere accessibili da ambienti di sviluppo o CI/CD. Usate vault dedicati (HashiCorp Vault, AWS Secrets Manager) con accesso granulare per ambiente. Monitorate le connessioni ai database di produzione: ogni connessione da un IP o un hostname non previsto deve generare un allarme. Se un processo npm tenta una connessione PostgreSQL, è un segnale di compromissione certo. Per le organizzazioni italiane: il GDPR impone misure di protezione proporzionate al rischio. Un database chiamato "payments" accessibile da un hook npm non supera nessun audit di conformità.
Variante 7 (strapi-plugin-api v3.6.8): Persistenza e Verifica dell'Hostname
*La variante 7 è dove l'operazione diventa sofisticata. Il malware ora controlla l'hostname del sistema: se contiene la stringa "prod-strapi", attiva il payload completo. Su qualsiasi altro hostname, si comporta come un plugin innocuo. Questo è targeting chirurgico: il codice è progettato per attivarsi solo sull'infrastruttura di produzione di Guardarian e dormire ovunque altrove. Se un ricercatore di sicurezza lo installa sul proprio laptop per analizzarlo, non vedrà nulla di malevolo.*
*Quando l'hostname corrisponde, il malware scrive un file chiamato .node_gc.js nella directory /tmp/. Il nome è scelto deliberatamente per sembrare un artefatto del garbage collector di Node.js — un file che un sysadmin vedrebbe e ignorerebbe. Poi aggiunge un entry al crontab dell'utente corrente: esecuzione ogni minuto. La persistenza è garantita: anche se il processo originale muore, il crontab lo rilancerà entro sessanta secondi. Il file .node_gc.js stabilisce una reverse shell verso il C2 a 144.31.107.231.*
Difesa — Fase Persistenza: Monitorate le modifiche al crontab con auditd o equivalente. Qualsiasi modifica al crontab da parte di un processo Node.js è un indicatore di compromissione critico. Monitorate la creazione di file in /tmp/ con nomi che imitano processi di sistema (.node_gc.js, .npm_cache.js, .v8_log.js). Implementate filesystem read-only per /tmp/ nei container di produzione, o usate tmpfs con noexec. L'hostname check è un reminder: i vostri hostname di produzione sono informazione sensibile. Usate naming convention che non rivelino la funzione del server a un osservatore esterno.
Variante 8 (v3.6.9): Esecuzione Fileless e Caccia ai Segreti
*L'ultima variante è la più pulita. Abbandono la scrittura di file su disco — troppo rilevabile, troppo persistente come artefatto forense. Passo all'esecuzione fileless: il payload viene lanciato direttamente con node -e, iniettando codice JavaScript come argomento della riga di comando. Non tocca il filesystem, non lascia file da analizzare. Esiste solo in memoria.*
*Il target è chirurgico: il malware cerca /opt/secrets/strapi-green.env e i contenuti della directory /var/www/nowguardarian-strapi/. Cerca file .env, chiavi private, dump Redis, Docker secrets, token di service account Kubernetes. Tutto viene inviato al C2 in chiaro. Un file alla volta. Se il file non esiste, passa al successivo senza errore. Se esiste, lo legge e lo invia. Silenzioso, rapido, monouso.*
*I path non sono casuali. /opt/secrets/strapi-green.env è un path specifico dell'infrastruttura Guardarian — probabilmente scoperto da un commento in un Dockerfile, da una variabile d'ambiente in un log di CI, o dalle varianti precedenti del malware. /var/www/nowguardarian-strapi/ contiene il deployment Strapi di produzione. Sto cercando le chiavi del regno in posti che conosco già.*
Difesa — Fase Esecuzione Fileless: L'esecuzione fileless tramite node -e è difficile da rilevare con antivirus tradizionali basati su firma di file. Le difese efficaci sono: (1) monitoraggio dei processi con argomenti della riga di comando sospetti — qualsiasi node -e con payload base64 o offuscato è un allarme; (2) monitoraggio dell'accesso a file sensibili con inotify/auditd; (3) principio del minimo privilegio: il processo Strapi non dovrebbe avere accesso in lettura a /opt/secrets/ né a Docker socket o token K8s; (4) segmentazione di rete: il traffico HTTP in uscita da un container Strapi verso IP sconosciuti dovrebbe essere bloccato per default.
Fase 5 — Esfiltrazione: Semplicità Come Virtù Operativa
*L'esfiltrazione è deliberatamente primitiva. HTTP in chiaro verso 144.31.107.231. Nessun protocollo custom, nessun tunnel DNS, nessuna steganografia. Perché? Perché la semplicità è una virtù operativa. Un protocollo custom è un'impronta digitale unica che un IDS può imparare a riconoscere. HTTP in chiaro su porta 80 è indistinguibile dal traffico legittimo di un'applicazione web che chiama un'API esterna. Nessun SOC al mondo genera alert per una richiesta HTTP GET in uscita da un server Node.js.*
*Il contenuto esfiltrato include: file .env completi con credenziali di database, API key, chiavi private di wallet; dump parziali di Redis con sessioni e token; Docker secrets montati nel filesystem; token di service account Kubernetes che permetterebbero movimento laterale nell'intero cluster. È il kit completo per un secondo stadio dell'attacco: con queste credenziali, potrei tornare in qualsiasi momento attraverso la porta principale, senza bisogno di npm o di supply chain. Il malware npm è il grimaldello per la porta sul retro; le credenziali esfiltrate sono la copia della chiave di casa.*
Difesa — Fase Esfiltrazione: Il traffico HTTP in uscita da container di produzione deve passare attraverso un proxy con allowlist di destinazioni. Qualsiasi richiesta verso un IP non in lista deve essere bloccata e loggata. Implementate Data Loss Prevention (DLP) a livello di rete: pattern matching per stringhe che assomigliano a credenziali, chiavi private, token JWT. Ruotate immediatamente tutte le credenziali se sospettate una compromissione — non dopo l'analisi forense, prima. Il costo di una rotazione preventiva è trascurabile rispetto al costo di credenziali esfiltrate e utilizzate.
La Timeline: Tredici Ore dal Banco di Lavoro dell'Attaccante
La compressione temporale dell'operazione merita una visualizzazione. In tredici ore, l'attaccante ha iterato otto volte sul proprio malware, pubblicandolo su 36 pacchetti attraverso 4 account. La velocità suggerisce preparazione meticolosa: il codice delle varianti successive era probabilmente già scritto e in attesa di deployment, non sviluppato in tempo reale.
- 1.Ora 0: Creazione dei 4 account npm e pubblicazione iniziale dei 36 pacchetti con varianti 1-2 (credential harvesting generico)
- 2.Ora 2-4: Varianti 3-5 pubblicate in aggiornamento — profilazione dell'ambiente, raccolta hostname, enumerazione processi
- 3.Ora 6: Variante 6 (strapi-plugin-seed) — credenziali PostgreSQL hardcodate, probing diretto dei database Guardarian
- 4.Ora 8-9: Variante 7 (strapi-plugin-api v3.6.8) — hostname check "prod-strapi", scrittura .node_gc.js, persistenza crontab
- 5.Ora 11-13: Variante 8 (v3.6.9) — esecuzione fileless via node -e, targeting di /opt/secrets/strapi-green.env e /var/www/nowguardarian-strapi/
- 6.Post-ora 13: Pacchetti individuati e rimossi dal registry npm dopo segnalazione della comunità di sicurezza
Indicatori di Compromissione (IOC)
Per i team SOC e i responsabili della sicurezza delle infrastrutture Strapi in Italia e all'estero, questi sono gli indicatori da cercare immediatamente.
- IP C2: 144.31.107.231 — verificare nei log di rete qualsiasi connessione in uscita verso questo indirizzo
- File su disco: /tmp/.node_gc.js — file di persistenza mascherato da artefatto Node.js garbage collector
- File su disco: /tmp/vps_shell.sh — script di shell per connessione reverse shell
- Crontab: entry che eseguono .node_gc.js o vps_shell.sh ogni minuto — verificare con crontab -l per tutti gli utenti di sistema
- Account npm: umarbek1233, kekylf12, tikeqemif26, umar_bektembiev1 — verificare che nessun pacchetto di questi autori sia presente nelle vostre dipendenze
- Pacchetti: qualsiasi pacchetto strapi-plugin-* con versione 3.6.8 o 3.6.9 non pubblicato dall'organizzazione ufficiale @strapi
- Processi: esecuzioni node -e con payload base64 o offuscato nei processi attivi
- Accesso a file: letture anomale di /opt/secrets/*.env, /var/www/*/.env, Docker secrets, token K8s
Perché Questo Caso Riguarda l'Italia
Strapi è il CMS headless più popolare nell'ecosistema Node.js e ha una presenza significativa nelle agenzie web e nelle startup italiane. Il registry npm è universale: non esiste un "npm italiano" con controlli locali. Qualsiasi sviluppatore che lavora su un progetto Strapi in Italia — dal freelance che costruisce il sito di un ristorante all'agenzia che gestisce l'e-commerce di un brand di moda — è esposto allo stesso vettore d'attacco. La differenza è che Guardarian era un bersaglio specifico con difese proporzionate al proprio profilo di rischio; una piccola agenzia web italiana probabilmente non ha un SOC, non monitora il traffico in uscita dai propri server di sviluppo, e non ha una policy di allowlist per i pacchetti npm.
Il messaggio non è "Strapi è insicuro". Strapi come progetto open source non ha responsabilità per pacchetti pubblicati da terzi sul registry npm. Il messaggio è: il modello di fiducia di npm — chiunque può pubblicare qualsiasi cosa con qualsiasi nome — è strutturalmente inadeguato per ambienti di produzione senza controlli aggiuntivi. E quei controlli aggiuntivi, in Italia, sono spesso assenti perché percepiti come "overhead" in team piccoli e veloci.
Per le agenzie web e le startup italiane: se usate Strapi, fate oggi stesso un audit delle vostre dipendenze npm. Eseguite npm ls per vedere l'albero completo delle dipendenze. Cercate pacchetti con naming strapi-plugin-* che non provengono dall'organizzazione ufficiale @strapi su npm. Se ne trovate, rimuoveteli immediatamente, ruotate tutte le credenziali presenti nei file .env dei server dove quei pacchetti sono stati installati, e verificate i log di rete per connessioni verso 144.31.107.231.
Lezioni Difensive: Cosa Ferma Questo Tipo di Attacco
Uscendo dalla prospettiva dell'attaccante e tornando a quella del difensore, questo caso illustra cinque principi che ogni organizzazione che dipende da npm dovrebbe adottare.
- 1.Registry privato con allowlist: non permettete l'installazione diretta da npmjs.com in ambienti CI/CD e di produzione. Usate un proxy (Verdaccio, Artifactory, GitHub Packages) che ammette solo pacchetti esplicitamente approvati.
- 2.Disabilitazione degli script postinstall: configurate npm con --ignore-scripts per default. Abilitate gli script solo per pacchetti verificati e necessari. Questo singolo controllo avrebbe neutralizzato tutte e otto le varianti.
- 3.Monitoraggio del traffico in uscita: i container di build e di produzione non dovrebbero poter contattare IP arbitrari. Un proxy in uscita con allowlist è la difesa più efficace contro l'esfiltrazione.
- 4.Segregazione delle credenziali per ambiente: le credenziali di produzione non devono essere accessibili da ambienti di sviluppo, build, o test. Mai. Se il vostro file .env di produzione è leggibile da un processo npm install, il vostro modello di sicurezza è rotto.
- 5.Monitoraggio del crontab e dei processi anomali: qualsiasi modifica al crontab da parte di un processo non autorizzato è un indicatore di compromissione. Qualsiasi processo node -e con payload offuscato è un indicatore di compromissione. Questi segnali devono generare alert immediati, non finire in un log che nessuno legge.
Nessuna di queste misure è nuova. Nessuna richiede tecnologia costosa o competenze rare. Sono tutte misure di igiene operativa che la maggior parte delle organizzazioni conosce ma non implementa perché "rallentano lo sviluppo". L'attaccante di questo caso contava esattamente su questo.
Nota Metodologica e Fonti
Fonti: analisi tecnica di Socket.dev (aprile 2026) sui 36 pacchetti npm malevoli; report Sonatype sulla campagna Guardarian; analisi della comunità npm security; dati OSINT sugli account npm coinvolti. Gli IOC sono stati verificati su fonti multiple. La ricostruzione della prospettiva attaccante è un esercizio analitico basato sugli artefatti tecnici documentati, non su informazioni dirette degli autori dell'attacco. Questo articolo non contiene codice eseguibile malevolo.