Privacidade Por Equipe DevBras

Capa do artigo: Skimmer na Convertr: o que fazer e como se proteger

Skimmer na Convertr: o que fazer e como se proteger

Skimmer no script do Plausible da Convertr capturou cartão e CPF de 24 a 27/10. Veja o que fazer se comprou e como proteger seu checkout com SRI e CSP.

Entre 24 e 27 de outubro de 2026, quem pagou com cartão no checkout de lojas que usam a Convertr pode ter entregado número do cartão, validade, CVV, nome e CPF a um grupo criminoso. A Convertr é uma plataforma brasileira que roda as lojas virtuais e o checkout de dezenas de marcas de moda. Ninguém invadiu as lojas uma por uma: os atacantes alteraram um único arquivo JavaScript, o script de estatísticas de visitas que todas as páginas de pagamento carregavam.

O caso foi descoberto pela empresa de segurança holandesa Sansec, que publicou a análise técnica completa em 26/10. Neste post explicamos o que aconteceu, o que fazer se você comprou numa dessas lojas e, principalmente, como conferir e proteger o seu próprio servidor e o seu checkout. Testamos aqui as verificações do Plausible em Docker e a proteção com SRI e CSP num Chrome headless, e mostramos os resultados reais.

O que aconteceu, em ordem

A Convertr usa o Plausible Community Edition, uma alternativa de código aberto ao Google Analytics, instalada num servidor próprio (analytics.convertr.app.br). Todo checkout das lojas da plataforma carregava o rastreador servido por esse servidor. Segundo a Sansec, o atacante acrescentou um bloco ofuscado ao fim do arquivo legítimo, e esse bloco era decodificado e executado com eval.

A linha do tempo abaixo usa os horários publicados pela Sansec (em UTC) convertidos para o horário de Brasília (BRT, UTC-3):

  • 15/05/2026: o Plausible corrige a falha CVE-2026-8467 na versão 3.2.1 do Community Edition.
  • 24/10, 02h03 BRT: última cópia limpa do rastreador da Convertr observada.
  • 24/10, 20h22 BRT: a Sansec detecta o skimmer.
  • 26/10: o skimmer é atualizado para ler os nomes exatos dos campos da API de pagamento da Convertr. Às 12h34 BRT o arquivo infectado ainda estava no ar, e a Sansec publica a análise.
  • 27/10, 09h54 BRT: a Convertr informa que removeu o skimmer e atualizou o Plausible.
  • 27/10, 10h08 BRT: a Sansec confirma que o rastreador está limpo.

A Convertr diz que as lojas da plataforma já venderam mais de R$ 2 bilhões. Nem a empresa nem a Sansec divulgaram a lista de lojas afetadas ou o número de compras atingidas, como registraram o Canaltech e o Zigg. Segundo a Sansec, a Convertr está notificando lojistas, provedores de pagamento e autoridades.

Comprei numa dessas lojas: o que fazer

O skimmer só agia em páginas cujo endereço continha checkout.convertr. Por isso, o jeito mais simples de saber se você foi exposto é olhar o histórico do navegador em que fez a compra (Ctrl+H no computador) e buscar por convertr.

  1. Achou uma página de checkout da Convertr entre 24 e 28 de outubro e pagou com cartão? Ligue para o banco e peça o bloqueio e a troca do cartão. A data vai até 28/10 porque, segundo a Sansec, o navegador pode manter a versão infectada do script em cache por até 24 horas depois da limpeza.
  2. Acompanhe o CPF. O CPF também foi capturado, e bancos usam o CPF para identificar clientes. O Registrato, do Banco Central, é gratuito e mostra contas, empréstimos e chaves Pix ligados ao seu CPF. Vale consultar agora e de novo nas próximas semanas.
  3. Desconfie de contato "do banco". Quem tem seu nome, CPF e os dados do cartão consegue montar um golpe por telefone bem convincente. Desligue e ligue você para o número que está no verso do cartão.
  4. Para as próximas compras, um cartão virtual (que muitos bancos geram no app) limita o estrago: dá para apagar ou trocar sem mexer no cartão físico.

A Sansec e a Convertr não dizem se quem pagou por Pix ou boleto também teve o CPF capturado.

Por que um script de estatísticas conseguiu ler o cartão

É aqui que mora a lição para quem desenvolve. Um <script src="..."> comum roda com os mesmos poderes de qualquer código da página: lê todos os campos do formulário, escuta cada tecla e pode mandar dados para qualquer lugar. Não importa se o script "só conta visitas". Se ele é carregado na página de pagamento e alguém troca o conteúdo dele, o código novo enxerga o número do cartão.

No trecho publicado pela Sansec, o skimmer junta os dados num texto separado por |, codifica em Base64 e envia com fetch no modo no-cors para o domínio 1x0.is. O modo no-cors não deixa o script ler a resposta, mas o envio acontece do mesmo jeito. Ou seja, a proteção de CORS do navegador não ajuda nada aqui.

A Sansec acompanha esse grupo desde julho de 2024. Ele já infectou dezenas de lojas Magento, OpenCart e WooCommerce, quase todas no Brasil, e tem skimmers para e.Rede, PagSeguro, Pagar.me e Mercado Pago, além de uma variante que troca o QR Code do Pix pelo do atacante. A Convertr foi o primeiro ataque à cadeia de fornecimento que a Sansec viu desse grupo: em vez de invadir uma loja, invadiu o fornecedor de todas. Se você tem loja Magento, o nosso post sobre a falha do Magento que entrou na lista da CISA também é leitura obrigatória desta semana.

A porta de entrada suspeita: CVE-2026-8467 no Plausible CE

Segundo a Sansec, a Convertr instalou o Plausible CE 3.0 em 2025 e nunca atualizou. As versões de 3.0.0 até 3.2.0 expõem o endereço /storybook sem login. Ele vem da biblioteca phoenix_storybook, usada para visualizar componentes de interface. A descrição da CVE-2026-8467 no NVD explica que valores enviados por WebSocket para esse endpoint eram colocados sem escape num template e executados no servidor, o que dá execução remota de código sem autenticação. A biblioteca é afetada da 0.5.0 até antes da 1.1.0.

O Plausible corrigiu o problema na versão 3.2.1, de 15/05/2026, que remove o /storybook por completo. O aviso de segurança do projeto classifica a falha como crítica (nota 9.5) e recomenda bloquear o /storybook no proxy reverso enquanto a atualização não sai. A Sansec deixa claro que não viu os logs da Convertr e por isso não consegue confirmar que essa foi mesmo a porta de entrada.

Conferimos isso nas imagens oficiais. Puxamos ghcr.io/plausible/community-edition nas tags v3.2.0 e v3.2.1 e listamos as bibliotecas dentro de cada uma:

docker run --rm --entrypoint sh ghcr.io/plausible/community-edition:v3.2.0 \
  -c 'ls /app/lib | grep -i storybook'
# saída: phoenix_storybook-0.9.3

docker run --rm --entrypoint sh ghcr.io/plausible/community-edition:v3.2.1 \
  -c 'ls /app/lib | grep -i storybook'
# saída: (nada)

A 3.2.0 traz a phoenix_storybook 0.9.3, dentro da faixa vulnerável. A 3.2.1 não traz a biblioteca.

Roda Plausible CE? Confira em 4 passos

Todos os comandos abaixo foram executados aqui, no Docker 26.1, com as imagens oficiais. Troque plausible pelo nome do seu container (veja com docker ps).

1. Veja a versão que está rodando

A imagem oficial traz a versão num rótulo padrão OCI, que o container herda:

docker inspect --format '{{.Config.Image}} {{index .Config.Labels "org.opencontainers.image.version"}}' plausible
# no nosso teste: ghcr.io/plausible/community-edition:v3.2.0 v3.2.0

Qualquer coisa de v3.0.0 até v3.2.0 (inclusive as versões rc) precisa subir para a v3.2.1 ou mais nova. Cuidado com tags genéricas como v3 ou v3.2: a lista de tags afetadas na release inclui as duas, então confira o rótulo, não só a tag do compose.

2. Procure acessos ao /storybook nos logs

O próprio aviso do Plausible manda procurar requisições para /storybook. No teste com nginx, uma requisição para /%73torybook (o "s" codificado) aparece assim mesmo no log, então vale buscar as duas formas e sem diferenciar maiúsculas:

grep -iE '/(storybook|%73torybook)' /var/log/nginx/access.log*
zgrep -iE '/(storybook|%73torybook)' /var/log/nginx/access.log*.gz

Se aparecer alguma coisa vinda de fora, o aviso do Plausible e a Sansec recomendam tratar o container como comprometido: recriar o servidor, trocar todos os segredos que ele enxerga (variáveis como SECRET_KEY_BASE, senhas do Postgres e do ClickHouse, credenciais de SMTP) e comparar os arquivos do rastreador com a versão oficial.

3. Bloqueie o /storybook no proxy até atualizar

Testamos dois jeitos de bloquear no nginx. O primeiro, com location ^~ /storybook, devolveu 404 para /storybook e para /%73torybook, mas deixou passar /Storybook com 200, porque esse tipo de location diferencia maiúsculas. O modificador ~* resolve:

location ~* ^/storybook {
    return 404;
}

Com essa regra, /storybook, /Storybook, /STORYBOOK/x e /%73torybook deram 404, e o resto do site continuou respondendo 200. Isso é só um curativo: a correção é atualizar para a 3.2.1, que remove o endpoint.

4. Confira se o rastreador foi alterado

Para saber se o ataque funciona contra a imagem oficial, simulamos a adulteração: subimos um container da v3.2.1, acrescentamos uma linha com eval ao fim do plausible.js, como fez o skimmer, e testamos três jeitos de perceber a mudança.

O mais rápido é o docker diff, que lista o que mudou no container em relação à imagem:

docker diff plausible | grep tracker/js
# C /app/lib/plausible-0.0.1/priv/tracker/js
# C /app/lib/plausible-0.0.1/priv/tracker/js/plausible.js

O segundo compara os hashes SHA-256 dos 1.026 arquivos do rastreador com os de um container novo da mesma imagem. Só o arquivo alterado apareceu no diff:

IMG=ghcr.io/plausible/community-edition:v3.2.1
D=/app/lib/plausible-0.0.1/priv/tracker/js
docker run --rm --entrypoint sh $IMG -c "cd $D && sha256sum *.js" > oficial.txt
docker exec plausible sh -c "cd $D && sha256sum *.js" > atual.txt
diff oficial.txt atual.txt

O terceiro busca as marcas do skimmer que a Sansec listou nos indicadores de comprometimento (fromCharCode seguido de eval). Nos arquivos oficiais da v3.2.1, nenhum dos 1.026 contém eval( nem fromCharCode, então qualquer resultado aqui merece atenção:

docker exec plausible sh -c "grep -lE 'eval\(|fromCharCode|checkout\.convertr' $D/*.js"

Uma observação do teste: na imagem oficial, a pasta e os arquivos do rastreador pertencem ao root e não têm permissão de escrita, e o Plausible roda como o usuário 999. Ou seja, numa instalação padrão em Docker, quem só consegue executar código como o usuário do aplicativo não deveria conseguir editar esses arquivos diretamente. A Sansec não detalhou como era a instalação da Convertr, então não dá para dizer como o arquivo foi alterado lá. Só não use isso como desculpa para adiar a atualização: execução remota de código continua sendo o pior cenário possível.

Tem loja? Trave os scripts do checkout com SRI e CSP

O padrão PCI DSS já exige isso de quem processa cartão: o requisito 6.4.3 pede inventário, autorização e garantia de integridade de cada script carregado na página de pagamento, como lembra o PCI Security Standards Council. Duas ferramentas do próprio navegador cobrem boa parte disso sem custo, e testamos as duas.

SRI: o navegador recusa o script se ele mudar

Com Subresource Integrity, você informa o hash do arquivo na própria tag. Se o conteúdo servido não bater com o hash, o navegador não executa o script. Para gerar o hash:

openssl dgst -sha384 -binary tracker.js | openssl base64 -A
<script src="https://analytics.SEU-DOMINIO/js/script.js"
        integrity="sha384-HASH-GERADO-ACIMA"
        crossorigin="anonymous"></script>

Montamos uma página de checkout de teste servida num endereço e um "servidor de analytics" em outro, com o cabeçalho Access-Control-Allow-Origin (o SRI em arquivo de outra origem exige CORS, e é por isso que a tag leva crossorigin="anonymous"). Com o arquivo original, o Chrome executou o script normalmente. Depois acrescentamos uma linha ao arquivo, simulando o skimmer, e o Chrome se recusou a executar: a variável que o script define nunca apareceu.

O custo do SRI é que toda atualização legítima muda o hash. Se você atualizar o Plausible, por exemplo, precisa gerar o hash de novo e publicar a página. Para script que muda sem aviso, como alguns SDKs de terceiros, o SRI quebra a página, e aí a resposta melhor costuma ser não carregar esse script no checkout.

CSP connect-src: mesmo que o script rode, o dado não sai

A segunda barreira é o cabeçalho Content-Security-Policy com a diretiva connect-src, que limita para quais endereços o JavaScript da página pode mandar requisições com fetch, XHR, WebSocket e sendBeacon. Na mesma página de teste, o checkout tentou mandar um fetch no modo no-cors (igual ao do skimmer) para um terceiro endereço:

  • Sem CSP: o Chrome enviou a requisição, e o "servidor do atacante" recebeu.
  • Com connect-src 'self' mais o endereço do analytics: o Chrome bloqueou o envio, o fetch falhou com "Failed to fetch" e o evento securitypolicyviolation registrou a diretiva connect-src e o endereço bloqueado.

Um ponto de partida para a página de pagamento é algo assim, trocando pelos domínios do seu gateway:

Content-Security-Policy: default-src 'self';
  script-src 'self' https://analytics.SEU-DOMINIO https://SDK-DO-GATEWAY;
  connect-src 'self' https://analytics.SEU-DOMINIO https://API-DO-GATEWAY;
  frame-src https://SDK-DO-GATEWAY;
  img-src 'self' data:

Duas ressalvas honestas. Primeira: CSP não é bala de prata, porque um skimmer pode tentar outros canais de saída, e uma política cheia de 'unsafe-inline' e domínios amplos protege pouco. Por isso o default-src 'self' e as listas curtas importam. Segunda: comece com Content-Security-Policy-Report-Only por alguns dias para ver o que quebraria antes de bloquear de verdade.

A defesa mais barata: menos script no checkout

A página de pagamento não precisa de chat, mapa de calor, pixel de cinco redes sociais nem, sendo sincero, de analytics completo. Cada script ali é um fornecedor que pode ser invadido, exatamente como aconteceu com a Convertr. Também ajuda usar o formulário de cartão do próprio gateway, em iframe ou campos hospedados, para que o número do cartão nunca passe pelo DOM da sua página. Explicamos as opções em o número do cartão não precisa passar pelo seu servidor e no exemplo prático do checkout transparente do Mercado Pago.

E se o seu time usa GitHub Actions para publicar o front, lembre que ataque à cadeia de fornecimento também começa por lá: veja como checar se os secrets do seu repositório vazaram no caso Gheist.

O que testamos e o que não testamos

Testamos em 27/10/2026 neste ambiente: as imagens oficiais do Plausible CE v3.2.0 e v3.2.1 (presença da phoenix_storybook, rótulo de versão, permissões e conteúdo dos 1.026 arquivos do rastreador), a detecção de adulteração com docker diff, sha256sum e grep, o bloqueio do /storybook no nginx (imagem nginx:alpine) e o comportamento de SRI e CSP no Google Chrome em modo headless, com servidores locais.

Não reproduzimos a exploração da CVE-2026-8467 nem subimos um Plausible completo com Postgres e ClickHouse. Também não temos acesso a nada da Convertr: tudo sobre o ataque em si vem da Sansec e das reportagens citadas.

Resumo

  • O caso: um skimmer no script do Plausible da Convertr capturou cartão e CPF em checkouts de lojas de moda de 24 a 27/10/2026. Já foi removido.
  • Se você comprou: busque "convertr" no histórico; se pagou com cartão entre 24 e 28/10, peça bloqueio e troca e acompanhe o CPF no Registrato.
  • Se você roda Plausible CE: atualize para a v3.2.1 ou mais nova, procure /storybook nos logs, bloqueie com location ~* ^/storybook até atualizar e confira o rastreador com docker diff.
  • Se você tem loja: tire do checkout o script que não é essencial, use SRI nos que ficarem e uma CSP com connect-src curta.

Fontes: análise da Sansec, release v3.2.1 do Plausible, aviso de segurança GHSA-mhcv-h7gf-57cf, GHSA-55hg-8qxv-qj4p e CVE-2026-8467 no NVD.

Avalie este artigo