Segurança

Segurança e Hardening do Magento 2: Guia Completo Anti-Magecart

Como blindar uma loja Magento 2 / Adobe Commerce contra skimmers de cartão (Magecart), explorações como CosmicSting e SessionReaper e ataques de cadeia de suprimentos — com comandos reais de composer, CLI, XML, SQL e nginx, não teoria.

Por Roger Takemiya · Atualizado em 14 de junho de 2026 · 28 min de leitura

Em 18 de junho de 2024, uma semana depois de a Adobe corrigir o CVE-2024-34102 (CosmicSting), a Sansec constatou que 75% das lojas Magento/Adobe Commerce ainda estavam sem o patch — e os atacantes comprometiam lojas a uma taxa de 5 a 30 por hora. Um ano depois a história se repetiu com o SessionReaper (CVE-2025-54236): segundo a Sansec, até 1º de novembro de 2025, 81% das lojas já tinham sido sondadas pelo exploit. Não é azar; é a mesma falha de processo se repetindo: patch existe, ninguém aplica a tempo.

Segurança em Magento 2 não é um plugin que você instala e esquece. É hardening em camadas e, principalmente, comandos que você executa: aplicar patch via composer e magento/quality-patches no dia do boletim, rotacionar a crypt key corretamente (ela é um array — apagar a chave antiga quebra a loja), forçar 2FA no admin, subir CSP para restrict, travar permissões de arquivo, colocar um WAF e rate limit na borda, rodar composer audit no CI e ter backup com restore testado. Neste guia eu troco prosa por comando: tudo que está aqui você consegue colar no terminal, auditar nas fontes oficiais e versionar no seu pipeline. São 14+ anos de Magento condensados nas decisões que evitam o telefonema das 3 da manhã.

Patches APSB: o fluxo real de aplicar via Composer e Quality Patches

A esmagadora maioria das lojas comprometidas cai por uma única razão: não aplicou um patch que já existia. A Adobe publica boletins no formato APSB (ex.: APSB25-88) listando as CVEs, versões afetadas e severidade. O feed que eu acompanho diariamente é a página de boletins de produto:

https://helpx.adobe.com/security/products/magento.html

Assine essa página e o programa de pre-release/PSIRT (parceiros recebem aviso antes da divulgação pública). Mas assinar não corrige nada — o que corrige é o fluxo abaixo.

1) Upgrade de versão via Composer (o fluxo canônico)

Quando o boletim pede subir de p-version (ex.: ir para 2.4.7-p3), o fluxo no Magento Open Source é:

# Open Source (community)
composer require magento/product-community-edition 2.4.7-p3 --no-update
composer update --with-dependencies

bin/magento setup:upgrade
bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f pt_BR en_US
bin/magento cache:flush

No Adobe Commerce (enterprise), troque o metapacote:

# Adobe Commerce (enterprise)
composer require magento/product-enterprise-edition 2.4.7-p3 --no-update
composer update --with-dependencies
bin/magento setup:upgrade && bin/magento setup:di:compile
bin/magento setup:static-content:deploy -f pt_BR en_US
bin/magento cache:flush

O --no-update só reescreve o composer.json; é o composer update --with-dependencies (ou -W) que resolve a árvore — fundamental porque magento/framework é arrastado por vários pacotes e dá conflito se você atualizar só o metapacote. Regra de ouro: rode isso em staging primeiro, com setup:di:compile e static-content:deploy no deploy (modo production), nunca direto em produção.

2) Quality Patches Tool (magento/quality-patches) — hotfix sem subir a versão

Nem todo boletim exige upgrade completo. Para hotfixes individuais (incluindo correções de segurança isoladas e patches de qualidade ACSD/MDVA), a ferramenta oficial é a Quality Patches Tool. Instale como dependência do projeto:

composer require magento/quality-patches

# ver o que está aplicado / disponível para sua versão
./vendor/bin/magento-patches status

# aplicar um patch específico pelo ID
./vendor/bin/magento-patches apply ACSD-12345

# reverter, se der regressão
./vendor/bin/magento-patches revert ACSD-12345
./vendor/bin/magento-patches revert --all

bin/magento cache:clean

Como achar o ID num boletim: no KB do APSB e nas release notes de security patch, cada correção isolada vem com um identificador ACSD-xxxxx (qualidade) ou um hotfix nomeado (ex.: o pacote VULN-32437-2-4-X-patch distribuído para o CVE-2025-54236). Você pesquisa esse ID na Quality Patches Tool online e aplica via CLI. Diferença prática: hotfix individual = correção pontual, baixo risco de regressão, ideal para resposta de emergência; upgrade de p-version = todas as correções acumuladas, mais regressão potencial, é o caminho definitivo.

3) Aplicar um patch .diff/.patch isolado

Quando a Adobe disponibiliza um isolated patch em arquivo .patch bruto (como aconteceu com o CVE-2025-24434), aplique-o manualmente. Eu valido antes com --check e versiono o arquivo no repositório, sob uma pasta m2-hotfixes/ aplicada no deploy:

# validar sem escrever nada
git apply --check VULN-XXXX_2.4.7-p3.patch

# aplicar (git apply ou patch -p1, conforme o cabeçalho do diff)
git apply VULN-XXXX_2.4.7-p3.patch
# ou, fora de um repo git:
patch -p1 < VULN-XXXX_2.4.7-p3.patch

bin/magento setup:upgrade && bin/magento cache:flush

Para automação, use o cweagans/composer-patches ou o vaimo/composer-patches, declarando o caminho do patch no composer.json — assim ele é reaplicado em todo composer install e não se perde num upgrade futuro.

4) Matriz de suporte x EOL: saber o que ainda recebe security-only

Cada linha 2.4.x tem janela de 3 anos a partir da data de GA. Não confunda "ainda recebe patch" com "recebe full release": linhas em fim de vida entram em extended security fixes, onde a Adobe libera arquivos de patch de segurança isolados, mas sem novas -p versions.

LinhaFim de suporte regularObservação
2.4.412/abr/2025Encerrada — migre já
2.4.512/ago/2025Encerrada
2.4.611/ago/2026Em suporte (com extensão de 1 ano, até 30/ago/2027, para clientes Adobe Commerce)
2.4.731/mai/2027Linha mais segura para ficar hoje

Successful exploitation of these vulnerabilities could lead to arbitrary code execution, security feature bypass, and privilege escalation.

Fonte: Adobe Experience League (KB ka-27149)

Quem ainda roda Magento 1 (suporte encerrado em 30/jun/2020) está sem qualquer patch há anos: a migração para Magento 2 deixou de ser roadmap e virou prioridade de segurança absoluta.

2FA obrigatório: provedores, comandos CLI e recuperação total

O painel admin é o alvo nº 1 de força bruta e credential stuffing. Desde o 2.4.0, o 2FA é obrigatório e habilitado por padrão no Admin (UI e Web API) via módulo Magento_TwoFactorAuth, independente de IP.

Provedores: o que de fato usar em 2.4.6+

O módulo nativo lista quatro provedores, com os códigos exatos usados na CLI:

ProvedorCódigo CLIMinha recomendação
Google AuthenticatorgooglePadrão prático (TOTP, grátis, universal)
Duo SecurityduoMelhor para times (push, políticas centrais)
AuthyauthyEvitar em projetos novos
U2F / WebAuthnu2fkeyForte (YubiKey), mas com ressalvas

Caveat honesto de campo: o caminho que a Adobe vem reforçando é Google Authenticator e Duo. Authy e U2F continuam no código, mas eu não os adoto em projeto novo — o Authy descontinuou o app desktop e o U2F depende da lib WebAuthn (atualizada junto das dependências de terceiros na linha 2.4.7+). Em 2.4.6+, padronizo google para a maioria e duo quando o cliente já tem Duo corporativo. Se quiser forçar globalmente quais provedores são permitidos, use o config path:

bin/magento config:set twofactorauth/general/force_providers google
bin/magento cache:flush

Comandos de operação do dia a dia

Tecnicamente dá para desligar tudo com module:disable Magento_TwoFactorAuth — e eu recomendo fortemente não fazer isso. Já vi loja desabilitar 2FA "pra facilitar" e ser invadida semanas depois. Quando um colaborador perde o autenticador, você reseta só aquele usuário:

# listar provedores disponíveis
bin/magento security:tfa:providers

# resetar o 2FA de um usuário (ele refaz o onboarding no próximo login)
bin/magento security:tfa:reset admin google
bin/magento security:tfa:reset admin u2fkey

# definir o segredo TOTP do Google para um usuário (provisioning automatizado)
bin/magento security:tfa:google:set-secret admin <encoded-secret>

Cenário de pânico: todos os admins perderam o 2FA

Acontece (migração de servidor, app trocado, segredo perdido). Em vez de desabilitar o módulo, o caminho cirúrgico é forçar provider e limpar o estado, ou resetar usuário a usuário. Eu prefiro restringir o provider via config e resetar os admins críticos:

# garante que o provider forçado é o google e limpa cache
bin/magento config:set twofactorauth/general/force_providers google
bin/magento security:tfa:reset admin google
bin/magento cache:flush

Só desabilite o módulo como medida temporária de emergência absoluta — e reabilite (module:enable Magento_TwoFactorAuth + setup:upgrade) assim que recuperar o acesso.

Segundo a documentação de administração da Adobe, o 2FA é exigido para confirmar a identidade do usuário no Admin por meio de uma senha de uso único gerada por um app ou dispositivo, e o administrador é solicitado a configurá-lo já no primeiro acesso. (Resumo da fonte, não citação literal.)

Fonte: Adobe Experience League

reCAPTCHA no login admin

Combine 2FA com Google reCAPTCHA no formulário de login. Eu uso v3 (score) ou Invisible para não atritar a equipe — o checkbox v2 incomoda quem loga várias vezes ao dia. O recurso vem do módulo Magento_ReCaptchaAdminUi e a configuração fica em Stores > Configuration > Security > Google reCAPTCHA Admin Panel (chaves armazenadas sob os paths recaptcha_admin/*). Com 2FA + reCAPTCHA + bloqueio por tentativas (próxima seção), você fecha praticamente todos os vetores diretos ao painel.

Magecart, CosmicSting e SessionReaper: rotação de crypt key sem quebrar a loja

Magecart é o guarda-chuva para grupos de web skimming: injetam JS no checkout para copiar dados do cartão em paralelo à transação. É invisível para cliente e lojista. A Sansec adiciona cerca de 30 assinaturas de malware por dia ao eComscan — isso é uma indústria.

As duas vulnerabilidades que definiram a era atual

FalhaCVE / BoletimCVSSPatchVetor
CosmicStingCVE-2024-341029.811/jun/2024XXE não autenticada → rouba crypt key → gera JWT de API
SessionReaperCVE-2025-54236 (APSB25-88)9.109/set/2025RCE não autenticada / takeover via Web API REST

No CosmicSting, o atacante lê a crypt/key de app/etc/env.php e forja um JWT que dá acesso total à API, usado para plantar JS em blocos CMS. No SessionReaper, segundo a Sansec, após a análise pública do bug os ataques em massa começaram em 22/out/2025 e, em 1º/nov/2025, 81% das lojas já tinham sido sondadas — a Adobe elevou a prioridade para nível 1 (patch em 72h).

CosmicSting (aka CVE-2024-34102) is the worst bug to hit Magento and Adobe Commerce stores in two years. [...] stores are getting hacked at a rate of 5 to 30 per hour.

Fonte: Sansec

Por que aplicar o patch NÃO basta

Aqui está a lição mais cara do CosmicSting: se a crypt key já vazou (e a Sansec recomenda tratar como vazada qualquer loja que não corrigiu a tempo), o invasor continua forjando JWTs válidos mesmo depois do patch. Você precisa rotacionar a crypt key. E é exatamente aqui que muitos guias quebram a loja.

A crypt key é um ARRAY — nunca apague a chave antiga

No app/etc/env.php, a crypt/key é um conjunto de chaves separadas por quebra de linha. A chave nova entra como elemento adicional; a antiga permanece para descriptografar dados já gravados (cartões salvos, credenciais de integração, dados de config). Apagar a chave antiga = perder acesso a todos esses dados. O formato:

// app/etc/env.php (trecho)
'crypt' => [
    'key' => '0123...antiga' . "\n" . 'abcd...nova',
],

O jeito certo de rotacionar. O Magento tem o comando nativo, que adiciona uma nova chave e re-criptografa segredos (a tela de troca no Admin foi removida no 2.4.8 — agora é só CLI):

# gera e adiciona uma nova crypt key, re-encripta segredos
bin/magento encryption:key:change

O problema, apontado pela Sansec, é que o procedimento nativo adiciona a chave nova mas não invalida a antiga referenciada no env.php — então JWTs forjados com a chave velha podem continuar válidos. Para o caso CosmicSting eu uso uma das ferramentas dedicadas que tratam isso corretamente:

# opção A: gene/module-encryption-key-manager
composer require gene/module-encryption-key-manager
bin/magento setup:upgrade
bin/magento gene:encryption-key-manager:generate
bin/magento gene:encryption-key-manager:invalidate   # marca a chave antiga como inutilizável (não a apaga)

# opção B: wubinworks/magento2-encryption-key-manager-cli (aftercare CosmicSting)

O gene move a chave antiga para uma seção crypt/invalidated_key no env.php (preservando o dado, impedindo o uso) e força o JWT factory a usar só a chave mais recente — exatamente o que falta no comando nativo.

Pós-rotação: invalidar sessões, tokens e form keys

Rotacionar a chave por si só não derruba JWTs já emitidos nem sessões. Faça a faxina:

# derruba sessões e cache
bin/magento cache:flush

# expira tokens de integração / OAuth (force re-login das integrações)
mysql -e "TRUNCATE TABLE oauth_token;" magento_db
# limpa sessões persistentes (lembrar-me)
mysql -e "TRUNCATE TABLE persistent_session;" magento_db

# se usar sessão em arquivo, limpe var/session; se Redis, FLUSHDB no DB de sessão

Depois disso, todos os admins e integrações precisam logar de novo — é o sintoma esperado, não um bug.

Confirmar se você já foi comprometido

Antes de declarar a loja limpa, cace os sinais clássicos de comprometimento pós-CosmicSting:

  • Usuários admin desconhecidos: SELECT username,email,is_active,created FROM admin_user ORDER BY created DESC; — qualquer conta recente que ninguém criou é bandeira vermelha.
  • Blocos CMS com JS suspeito: SELECT block_id,title FROM cms_block WHERE content LIKE '%<script%'; e o mesmo em cms_page.
  • Config injetada: revise core_config_data em paths de design/head e miscellaneous (HTML head/footer são esconderijos comuns de skimmer).
  • Logs de exploração: procure POST /rest/V1/... ou POST /rest/all/V1/... com payload XML/XXE nos access logs do webserver.

A Sansec mantém um verificador público de CosmicSting e o módulo open source sansecio/magento2-module-cosmic-sting-jwt para detectar JWTs forjados. Se achar qualquer indício, trate como incidente completo (veja a seção de backups/incidentes) — não confie em "só apliquei o patch".

Hardening do admin: URL customizada, allowlist de IP, senhas e Action Logs

Além do 2FA, há um conjunto de configurações de admin que eu travo via config:set (versionável no deploy) em todo projeto. Aqui troco a tabela genérica por valores reais.

Endurecer senha e sessão por CLI

Os defaults do Magento são fracos (senha mínima de 7 caracteres é o piso, não uma recomendação). Eu subo assim:

# senha
bin/magento config:set admin/security/password_min_length 12
bin/magento config:set admin/security/password_lifetime 90        # dias; vazio = sem expiração
bin/magento config:set admin/security/lockout_failures 5          # tentativas até bloquear
bin/magento config:set admin/security/lockout_threshold 30        # minutos de bloqueio

# sessão (em SEGUNDOS — default 900 = 15 min)
bin/magento config:set admin/security/session_lifetime 900        # 900 a 3600 é razoável
bin/magento config:set admin/security/use_case_sensitive_login 1
bin/magento cache:flush

Correção importante: o session_lifetime é em segundos, default 900, com mínimo aceito de 60. Já vi guia recomendar usar o mínimo de 60s — isso deslogaria todo mundo a cada minuto. Fique entre 900 e 3600.

URL de admin customizada

Tire o admin de /admin. Dá para fazer no env.php (preferido, fora do banco) ou via config:

// app/etc/env.php
'backend' => [
    'frontName' => 'painel_x7k2'
],
# alternativa via banco
bin/magento config:set admin/url/use_custom_path 1
bin/magento config:set admin/url/custom_path painel_x7k2
bin/magento cache:flush

Mantenha Add Secret Key to URLs = Yes (admin/security/use_form_key): ele insere um token anti-CSRF nas URLs do admin. Desligar isso (alguns fazem para "consertar" links) reabre CSRF — não desligue.

Allowlist de IP no Nginx (bloco real)

Onde a equipe acessa de IPs fixos, restrinja o path do admin no webserver. Para a URL customizada acima:

location ~* ^/painel_x7k2 {
    allow 200.200.200.10;      # IP do escritório
    allow 203.0.113.0/24;      # VPN da empresa
    deny all;

    try_files $uri $uri/ /index.php$is_args$args;
}

Combinada com URL não-default e secret key, essa camada torna o admin praticamente invisível para varreduras automatizadas.

ACL e menor privilégio

Em System > Permissions > User Roles, cada usuário recebe só o que precisa. Gotcha de auto-escalada: ao restringir um role, negue o acesso à própria ferramenta Permissions — senão o usuário edita o próprio role e vira admin. Use o escopo Custom para limitar websites/stores em operações multiloja.

Segundo a documentação da Adobe sobre user roles, ao atribuir recursos a um role com acesso limitado é preciso desabilitar o acesso à ferramenta Permissions; caso contrário, os usuários conseguem modificar as próprias permissões. (Resumo da fonte, não citação literal.)

Fonte: Adobe Experience League

Admin Action Logs: quem fez o quê

Auditoria é o que separa "acho que mexeram" de "sei exatamente quem, quando e o quê". No Adobe Commerce (e B2B) existe o Action Logs nativo: Reports > Action Logs > Report registra login/logout, criação e alteração de produtos, pedidos, config, usuários, etc., com retenção configurável em Stores > Configuration > Advanced > Admin > Admin Actions Log Archiving. Revise periodicamente — admins novos e mudanças de config fora de horário são o primeiro sinal de invasão.

No Open Source não existe esse recurso nativo. As alternativas que eu uso: (1) módulos de audit log da comunidade (ex.: Amasty_Logging e similares), ou (2) auditoria via logs do webserver + a tabela admin_user_session e os access logs de POST no path do admin. Não é tão rico quanto o Action Logs, mas cobre o essencial: quem logou e de onde.

Permissões de arquivo: o que é read-only em produção e bloqueio de PHP

Permissões mal configuradas são convite para webshell. Skimmers fazem upload de PHP malicioso em diretório gravável para persistir. O objetivo é claro: o webserver escreve só onde precisa, e nada executável roda onde não deve.

Em produção, vendor e app/etc são READ-ONLY

Correção de um erro comum: em deploy mode production, vendor, app/etc e generated não devem ser graváveis pelo webserver — eles são escritos só no deploy. Em runtime, apenas três precisam de escrita:

  • var: cache, logs, sessões (se em arquivo).
  • pub/media: uploads e imagens de produto.
  • pub/static: assets — e mesmo este pode ser read-only se você roda o setup:static-content:deploy no deploy (SCD on deploy), regenerando tudo fora do runtime.

Listar vendor como "gravável pelo webserver em produção" contradiz o próprio hardening — é assim que um RCE vira persistência.

Os comandos find canônicos da Adobe

Para o modelo de dois usuários (deploy + webserver no mesmo grupo), este é o conjunto que a Adobe documenta:

find var generated vendor pub/static pub/media app/etc -type f -exec chmod g+w {} +
find var generated vendor pub/static pub/media app/etc -type d -exec chmod g+ws {} +
chown -R :www-data .            # grupo do seu webserver (apache, nginx, www-data)
chmod u+x bin/magento

O g+ws nos diretórios fixa o setgid, garantindo que arquivos novos herdem o grupo correto. Em hospedagem de um único usuário, prefira umask 022 (755/644) via arquivo magento_umask:

echo 022 > magento_umask

Travar arquivos sensíveis: 640 e nunca 777

# env.php contém crypt key e credenciais de banco — read-only e restrito
chmod 640 app/etc/env.php
chmod 640 app/etc/config.php

# tira qualquer permissão de "outros" em arquivos sensíveis
chmod o-rwx app/etc/env.php app/etc/config.php

777 nunca. Se você precisou de 777 para "funcionar", o problema é dono/grupo errado, não a permissão.

Bloquear execução de PHP em pub/media

Medida mais importante e mais negligenciada: pub/media recebe upload, então é o esconderijo favorito de webshell. No Nginx:

location ~* ^/media/.*\.(php|php5|phtml|pl|py|cgi|sh|asp|aspx)$ {
    deny all;
}

No Apache, coloque um .htaccess dentro de pub/media que mata qualquer handler de script — em prosa não adianta, eis o bloco real:

# pub/media/.htaccess
<IfModule mod_php.c>
    php_flag engine off
</IfModule>
RemoveHandler .php .phtml .php3 .php4 .php5 .php7 .pl .py .cgi
<FilesMatch "\.(php|phtml|php3|php4|php5|php7|pl|py|cgi)$">
    SetHandler none
    Require all denied
</FilesMatch>

Segundo o guia de instalação da Adobe, os diretórios que recebem permissão de escrita são definidos via comandos find sobre var, generated, vendor, pub/static, pub/media e app/etc, distinguindo o modelo de um único usuário do modelo de dois usuários que compartilham um grupo. (Resumo da fonte, não citação literal.)

Fonte: Adobe Experience League

Esconder a superfície de exposição

Negue acesso público a artefatos que vazam estrutura e segredos. No Nginx, dentro do server block:

location ~ /\.(git|env|svn|htaccess) { deny all; }
location ~* ^/(composer\.(json|lock)|auth\.json|app/etc/env\.php)$ { deny all; }
location ^~ /setup/ { internal; }   # nunca exponha o setup wizard em produção

O Magento 2 já serve só a partir de pub/ em produção, mas confirme que app/, var/ e vendor/ não estão acessíveis pela web e que o instalador/setup está fechado.

HTTPS, HSTS, SameSite e hardening de TLS

Sem HTTPS de ponta a ponta, todo o resto desmorona. A Adobe recomenda servir todas as páginas via HTTPS, não só o checkout.

Secure base URL e HSTS por CLI

bin/magento config:set web/secure/base_url https://www.sualoja.com.br/
bin/magento config:set web/secure/use_in_frontend 1
bin/magento config:set web/secure/use_in_adminhtml 1
bin/magento config:set web/secure/enable_hsts 1
bin/magento config:set web/secure/enable_upgrade_insecure 1
bin/magento config:set web/cookie/cookie_httponly 1
bin/magento cache:flush

Com use_in_frontend e use_in_adminhtml em 1, a flag Secure do cookie passa a ser aplicada (o cookie só transita por HTTPS) — não existe um toggle "secure cookie" isolado; ele deriva do uso de URLs seguras + HSTS. O HttpOnly impede que JS leia o cookie de sessão (defesa direta contra roubo via XSS).

O header HSTS literal — e o aviso sobre preload

O Magento emite o header quando HSTS está ligado; se você gerencia no webserver, o valor recomendado:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Gotcha sério: includeSubDomains e, principalmente, preload (inscrição na lista de preload dos navegadores) são quase irreversíveis — remover da lista leva meses e, no intervalo, qualquer subdomínio sem HTTPS fica inacessível. Comece com max-age baixo (ex.: 300), valide tudo, e só então suba para um ano e adicione preload.

SameSite: o gotcha do retorno do PSP

O Magento usa SameSite=Lax por padrão nos cookies. Lax é o equilíbrio certo para storefront. Você pode endurecer para Strict, mas atenção: Strict pode quebrar o retorno de gateways de pagamento (PagSeguro, Cielo, Stripe, etc.) — quando o cliente volta do PSP via redirect cross-site, o navegador não envia o cookie de sessão em modo Strict e ele "perde" o carrinho/login. Por isso eu mantenho Lax no storefront e, no máximo, considero Strict só no admin. Se for ajustar via config:

bin/magento config:set web/cookie/cookie_samesite Lax
bin/magento cache:flush

Hardening de TLS no webserver

Patch e config no Magento não cobrem o transporte — isso é no Nginx/Apache:

# nginx: desabilita TLS antigo, mantém só 1.2 e 1.3
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;
ssl_stapling on;            # OCSP stapling
ssl_stapling_verify on;

Desabilite TLS 1.0/1.1 (exigência de PCI DSS), use TLS 1.2 como mínimo, ative OCSP stapling e habilite HTTP/2 (listen 443 ssl; http2 on;) — ganho de performance sem custo de segurança.

Segundo a documentação da Adobe, é fortemente recomendado transmitir todas as páginas de um site de produção — incluindo páginas de conteúdo e de produto — usando um protocolo seguro. (Resumo da fonte, não citação literal.)

Fonte: Adobe Experience League

Headers de segurança adicionais

Complemente com headers que fecham clickjacking e MIME-sniffing. No Nginx (storefront):

add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

Observação: o módulo Magento_Csp também pode emitir a diretiva frame-ancestors (defesa de clickjacking moderna que substitui o X-Frame-Options) via whitelist — útil quando você precisa permitir framing de domínios específicos.

CSP no 2.4: whitelist correta, leitura de relatórios e SRI

Se patches e 2FA protegem a entrada, o CSP é a última linha contra Magecart: o navegador só executa scripts de origens autorizadas. A Sansec é enfática sobre a eficácia dessa camada.

The majority of all digital skimming attacks to date would have been stopped by a proper CSP implementation.

Fonte: Sansec

Modos e o XML correto do config.xml

Suporte desde a 2.3.5 (módulo Magento_Csp). A partir do 2.4.7, o CSP fica em restrict-mode por padrão nas páginas de pagamento (storefront e admin) e report-only nas demais. O detalhe que muitos erram: o nó já se chama report_only, então o valor é booleano1 liga o report-only (não bloqueia), 0 é restrict (bloqueia). O XML literal, em etc/config.xml do seu módulo:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Store:etc/config.xsd">
  <default>
    <csp>
      <mode>
        <storefront>
          <report_only>1</report_only>   <!-- 1 = report-only -->
        </storefront>
        <admin>
          <report_only>0</report_only>   <!-- 0 = restrict -->
        </admin>
      </mode>
    </csp>
  </default>
</config>

O csp_whitelist.xml completo (caminho exato e schema)

O arquivo fica em app/code/Vendor/Module/etc/csp_whitelist.xmlnão em uma subpasta de área. Estrutura completa com xmlns e o schema correto:

<?xml version="1.0"?>
<csp_whitelist xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Csp:etc/csp_whitelist.xsd">
  <policies>
    <policy id="script-src">
      <values>
        <value id="google-analytics" type="host">https://www.google-analytics.com</value>
        <value id="inline-snippet" type="hash" algorithm="sha256">BASE64_HASH_AQUI=</value>
      </values>
    </policy>
    <policy id="connect-src">
      <values>
        <value id="psp" type="host">https://api.pagseguro.com</value>
      </values>
    </policy>
  </policies>
</csp_whitelist>

Os type aceitos são host (domínio), hash (com algorithm="sha256" para inline scripts específicos) e nonce (via nonce provider, a partir do 2.4.7). Use hash em vez de liberar unsafe-inline sempre que possível.

Como LER as violações (sem isso a whitelist é chute)

Você não monta a whitelist no escuro — você lê o que o report-only reporta. Três fontes:

  • Console do navegador: em report-only, o Chrome/Firefox loga cada violação com a diretiva e a origem bloqueada — a forma mais rápida de mapear o storefront real.
  • report-uri / report-to: o CSP envia um JSON de violação para o endpoint configurado; aponte para um coletor (serviço externo ou um endpoint próprio) e agregue por origem.
  • Logs: capture os POSTs de violação no webserver e filtre por blocked-uri para listar todos os domínios de terceiros que precisam entrar.

Rode report-only por alguns dias cobrindo o tráfego real (incluindo checkout e retorno de PSP), cadastre as origens legítimas, e só então vire para restrict nas páginas de pagamento.

SRI e o trade-off do unsafe-eval

A partir do 2.4.7, o Magento gera SRI (Subresource Integrity) automaticamente para os assets JS locais (hashes sha256 no atributo integrity=), de modo que um arquivo adulterado é recusado pelo navegador; por padrão o recurso é aplicado nas páginas de pagamento (storefront e admin) e pode ser estendido às demais. Para assets remotos você ainda precisa fixar o hash manualmente. Sobre o unsafe-eval: ele costuma permanecer permitido porque Knockout.js e RequireJS (base do front do Magento) usam eval() internamente — removê-lo quebra o storefront sem um refactor grande. É o trade-off honesto: dá para endurecer script-src ao ponto de matar inline e host não-confiável, mas unsafe-eval sai por último. Projetos sobre Mage-OS e algumas extensões de CSP ajudam a popular a whitelist e a reduzir essa dependência.

CSP e SRI atendem diretamente ao PCI DSS 6.4.3 e 11.6.1 (detalhados na seção de PCI). Como o CSP roda no navegador, convive bem com cache de borda e não pesa no render — o que conversa com Performance e Core Web Vitals, já que scripts de terceiros são, ao mesmo tempo, o maior risco de skimming e a maior causa de degradação de métricas.

WAF, OWASP CRS, bot mitigation e rate limiting (Cloud e on-prem)

Um WAF filtra tráfego malicioso antes da aplicação. No Adobe Commerce on cloud infrastructure, o WAF é powered by Fastly e existe só em produção.

Fastly WAF e o OWASP CRS

O WAF Fastly detecta, registra e bloqueia injection, XSS, exfiltração de dados, violação de protocolo HTTP e o OWASP Top Ten, com base em regras ModSecurity da Trustwave SpiderLabs somadas a regras específicas do Commerce. Por baixo, isso se conecta ao OWASP Core Rule Set (CRS) — o ponto que merece atenção é o paranoia level: subir o PL pega mais ataques, mas gera mais falsos positivos; o tuning real é identificar regras que barram tráfego legítimo (ex.: payloads grandes de import) e criar exceções. No Cloud, você adiciona regras WAF customizadas e listas via o painel e edge dictionaries do Fastly. Para bot mitigation, use o Fastly para denylist de user-agents abusivos, allowlist de parceiros e regras de edge — não deixe como "palavra no slide".

Segundo a documentação da Adobe, o WAF do Adobe Commerce on cloud infrastructure é powered by Fastly, disponível apenas em produção, e detecta/registra/bloqueia tráfego malicioso com regras ModSecurity da Trustwave SpiderLabs e cobertura do OWASP Top Ten, com latência estimada de 1,5 ms a 20 ms por requisição não-cacheada. (Resumo da fonte, não citação literal.)

Fonte: Adobe Experience League

Rate limiting de Web API (backpressure)

Rate limiting não é recurso embutido do WAF padrão. O Magento tem o backpressure de Web API, introduzido no 2.4.7, desabilitado por padrão, com backend em Redis (ou Valkey nas versões recentes). Valores que eu uso como ponto de partida:

bin/magento config:set sales/backpressure/enabled 1
bin/magento config:set sales/backpressure/guest_limit 50    # req por IP (convidado) na janela
bin/magento config:set sales/backpressure/limit 100         # req por customer ID na janela
bin/magento config:set sales/backpressure/period 60         # janela: aceita só 60, 3600 ou 86400
bin/magento cache:flush

O period aceita apenas 60, 3600 ou 86400 segundos. A REST API já limita listas a 20 entidades e paginação a 300 itens por padrão, o que ajuda contra scraping.

On-prem (a maioria das lojas BR): faça na borda você mesmo

"Use Cloudflare/AWS WAF" é genérico demais. Para quem está fora da cloud da Adobe, o mínimo viável é rate limit no Nginx:

# http { } — define as zonas
limit_req_zone $binary_remote_addr zone=login:10m rate=5r/m;
limit_req_zone $binary_remote_addr zone=general:10m rate=30r/s;
limit_conn_zone $binary_remote_addr zone=conn:10m;

# server { } — aplica nos pontos certos
location ~* /(customer/account/loginPost|painel_x7k2) {
    limit_req zone=login burst=10 nodelay;
}
location / {
    limit_req zone=general burst=50 nodelay;
    limit_conn conn 20;
}

Para WAF de aplicação on-prem, ative o ModSecurity com OWASP CRS ou o managed ruleset do Cloudflare e adicione uma rate limiting rule no path de login e no checkout. Garanta que as allowlists do WAF/rate limit contemplem os IPs e endpoints das integrações com ERP e NF-e, para não bloquear chamadas legítimas de retaguarda.

Cadeia de suprimentos: composer audit, auditoria de admins e FIM

Hardening sem monitoramento é incompleto. A Adobe oferece o Security Scan Tool gratuito, no dashboard da conta Commerce/Magento, rodando 21.000+ testes (malware, skimmers, patches desatualizados, misconfigurations), agendável semanal/diário/on-demand. Limitação real: ele opera de fora, via domínio público — não enxerga tudo que está no servidor e é complementar, não substituto de varredura local.

Segundo a documentação da Adobe, o Security Scan Tool está disponível gratuitamente no dashboard da conta Commerce/Magento e executa mais de 21.000 testes de segurança, com agendamento semanal, diário ou on-demand. (Resumo da fonte, não citação literal.)

Fonte: Adobe Experience League

Integridade do composer.lock no CI

A defesa de supply chain mais barata e mais ignorada: travar dependências vulneráveis no pipeline. Três comandos concretos:

# 1) audita o que está travado no lock, sem instalar (roda no CI a cada PR)
composer audit --locked

# 2) bloqueia, no install, qualquer pacote com advisory conhecido
composer require --dev roave/security-advisories:dev-latest

# 3) em produção, nunca instale dependências de dev
composer install --no-dev --prefer-dist --no-progress

O composer.lock deve estar commitado e o deploy de produção deve usar composer install (determinístico), nunca composer update. Gotcha do roave/security-advisories: ele só é checado em require/update — por isso o composer audit --locked no CI é o complemento que pega o lock já existente.

Auditoria de usuários admin via SQL

Conta admin desconhecida é o sinal nº 1 de comprometimento. Cole isto no banco periodicamente (e sempre após um incidente):

-- todos os admins, mais recentes primeiro
SELECT user_id, username, email, is_active, created, modified, logdate
FROM admin_user ORDER BY created DESC;

-- tokens de integração/OAuth ativos (procure o que não reconhece)
SELECT entity_id, type, created_at FROM oauth_token WHERE type='access' ORDER BY created_at DESC;

-- contas inativas que voltaram a logar (suspeito)
SELECT username, is_active, logdate FROM admin_user WHERE is_active=0 AND logdate IS NOT NULL;

Desative na hora qualquer conta órfã (UPDATE admin_user SET is_active=0 WHERE username='...';), invalide os tokens com TRUNCATE TABLE oauth_token; e force re-login geral.

Vetting de extensão com método (não truísmo)

"Revise o código" sem método não ajuda. O que eu de fato checo antes de subir uma extensão:

  • Permissões do composer.json: dependências estranhas, scripts de post-install que baixam algo, requisitos de PHP que abrem funções perigosas.
  • Chamadas perigosas: grep -rnE 'eval\(|base64_decode\(|system\(|exec\(|file_put_contents\(|curl_exec\(' vendor/fornecedor/modulo — eval e gravação de arquivo em extensão de catálogo é bandeira vermelha.
  • Versão + CVE: cruze a versão exata com advisories conhecidos antes de instalar.
  • Origem: só Marketplace oficial ou integrador confiável — o Marketplace faz malware scan (antivírus + Yara com regras do Commerce) e rejeita submissões reprovadas.

FIM e varredura agendada (com cron real)

Monitoramento de integridade de arquivos (FIM) pega o skimmer nas primeiras horas, não depois de meses. Opções concretas, da mais simples à mais robusta:

  • git diff no deploy: se o código é versionado, qualquer arquivo PHP/JS alterado fora do deploy aparece num git status/git diff agendado.
  • Checksum do core: compare o hash de vendor/magento contra o release oficial da sua versão.
  • AIDE / Tripwire: baseline de integridade do sistema de arquivos com alerta em mudança.
  • eComscan (Sansec): examina arquivos, banco e componentes de terceiros — a Sansec analisa 200–300 hacks/semana.

Agende a varredura via cron (exemplo com eComscan diário às 3h):

# crontab -e
0 3 * * * /usr/local/bin/ecomscan --no-tty /var/www/html | mail -s "eComscan diario" [email protected]

Backup self-hosted, runbook forense e PCI DSS 4.0.1 / LGPD

Mesmo com tudo acima, prepare-se para o pior. E aqui a maioria dos guias falha: foca só na cloud Pro da Adobe, irrelevante para a loja brasileira on-prem ou Starter. Vamos ao backup que você controla.

Backup próprio com mysqldump (excluindo lixo de log)

Não despeje o banco inteiro — tabelas de log e cache incham o dump e não precisam de backup. Use --single-transaction (não trava InnoDB) e ignore o que é descartável:

# estrutura de TODAS as tabelas (inclusive as ignoradas no dump de dados)
mysqldump --single-transaction --no-data magento_db > schema.sql

# dados, pulando log/relatório/cache
mysqldump --single-transaction --quick \
  --ignore-table=magento_db.report_event \
  --ignore-table=magento_db.report_viewed_product_index \
  --ignore-table=magento_db.customer_log \
  --ignore-table=magento_db.customer_visitor \
  magento_db | gzip > db-$(date +%F).sql.gz

O Magento também tem bin/magento setup:backup, mas para banco grande o mysqldump dá mais controle. Faça backup também de pub/media (imagens) e de app/etc/env.php (config + crypt key — guarde criptografado e separado):

tar czf media-$(date +%F).tar.gz pub/media
gpg -c app/etc/env.php   # backup criptografado do env.php

Regra 3-2-1 e teste de restore

Backup que nunca foi restaurado é teoria. A regra: 3 cópias, 2 mídias diferentes, 1 off-site. Envie o dump cifrado para outro local (S3 com SSE, outro datacenter) e teste o restore em staging periodicamente — descubra que o backup está corrompido no exercício, não no incidente. Cron de backup + envio off-site:

# crontab: backup diário às 2h + sync off-site
0 2 * * * /usr/local/bin/m2-backup.sh && aws s3 cp /backups/ s3://meu-bucket/m2/ --recursive --sse AES256

Runbook forense: a ORDEM importa

O erro clássico é "limpar e voltar no ar". Skimmer fica dormente por meses — restaurar um backup recente pode reinstalar o malware. A ordem correta:

  1. Preserve antes de tocar: tire snapshot/imagem do disco e copie os logs ANTES de qualquer limpeza — é a evidência forense.
  2. Isole: tire a loja do ar ou bloqueie o tráfego suspeito.
  3. NÃO restaure um backup recente às cegas: ele pode conter o skimmer dormente. Identifique a data da invasão primeiro.
  4. Investigue: admin_user (contas novas), oauth_token (tokens forjados), cms_block/cms_page (JS injetado), core_config_data (head/footer).
  5. Erradique: aplique patch, rotacione a crypt key corretamente (seção CosmicSting), troque TODAS as credenciais (banco, integrações, admin), invalide tokens e sessões.
  6. Valide: rode eComscan + Security Scan e só então reabra.
  7. Notifique: adquirentes (PCI) e, no Brasil, a ANPD conforme a LGPD.

Segundo a documentação de segurança da Adobe, no plano Pro os backups são feitos a cada hora durante as últimas 24 horas e depois retidos via AWS EBS Snapshot, com volumes criptografados em AES-256 e atividades registradas no CloudTrail, dentro de uma VPC isolada. (Resumo da fonte, não citação literal.)

Fonte: Adobe Experience League

PCI DSS 4.0.1: os dois requisitos anti-Magecart

O Adobe Commerce é PCI DSS Level 1 Service Provider, mas o modelo é de responsabilidade compartilhada: a Adobe cobre a infra; o lojista responde pelo código customizado, processos e ASV scans. Dois requisitos, obrigatórios desde 31/03/2025, atacam o skimming direto:

RequisitoExigênciaMitigação Magento
6.4.3Inventário, autorização e integridade de todos os scripts na página de pagamentoCSP + SRI
11.6.1Detecção/alerta de modificação não autorizada em headers de segurança HTTP e scripts da página de pagamentoCSP report-only + FIM

SAQ, ASV e LGPD para o público BR

  • SAQ aplicável: a maioria das lojas Magento que renderizam o formulário de pagamento (mesmo via iframe/redirect do PSP) cai no SAQ A-EP, não no SAQ A — e é justamente o A-EP que o Magecart ataca, porque seu servidor entrega a página de pagamento.
  • ASV scan: varredura por Approved Scanning Vendor com cadência trimestral (e após mudanças significativas) é obrigação do lojista.
  • LGPD/ANPD: vazamento de dado pessoal de cliente em incidente de skimming aciona o dever de comunicação à ANPD e aos titulares, em prazo razoável — isso vale para o Brasil além do PCI, não em vez dele.

Perguntas frequentes

Como aplico um patch de segurança do Magento na prática, via Composer?

Para subir de p-version no Open Source: composer require magento/product-community-edition 2.4.7-p3 --no-update, depois composer update --with-dependencies, e então bin/magento setup:upgrade, setup:di:compile, setup:static-content:deploy -f e cache:flush (no Adobe Commerce troque o metapacote por magento/product-enterprise-edition). Para hotfixes pontuais sem subir a versão, use a Quality Patches Tool: composer require magento/quality-patches e ./vendor/bin/magento-patches apply ACSD-xxxxx. Sempre valide em staging primeiro.

O que é a Quality Patches Tool e quando usar em vez de fazer upgrade?

É a ferramenta oficial (magento/quality-patches) que aplica correções isoladas — de segurança e de qualidade, identificadas por IDs ACSD/MDVA — sem subir a versão inteira. Comandos: ./vendor/bin/magento-patches status (ver o que está aplicado), apply ID e revert ID. Use hotfix individual para resposta de emergência ou correção pontual de baixo risco; use upgrade de p-version quando quiser todas as correções acumuladas de forma definitiva. Você acha o ID do patch no KB do boletim APSB ou na ferramenta online de Quality Patches.

Como rotaciono a crypt key após o CosmicSting sem quebrar a loja?

A crypt/key no app/etc/env.php é um ARRAY: a chave nova entra como elemento adicional e a antiga PERMANECE para descriptografar dados já gravados (cartões, credenciais). Nunca apague a antiga. O comando nativo bin/magento encryption:key:change adiciona a chave nova, mas não invalida a velha referenciada no env.php — por isso, no caso CosmicSting, uso ferramentas como gene/module-encryption-key-manager (generate + invalidate). Depois, invalide o que ficou: cache:flush, TRUNCATE oauth_token e persistent_session, e force re-login de admins e integrações.

Devo desabilitar o 2FA para facilitar o acesso da equipe? Como reseto um usuário?

Não. O 2FA é obrigatório desde o 2.4.0 e é uma das defesas mais eficazes contra invasão do painel; já vi lojas serem invadidas semanas após desligá-lo. Se alguém perdeu o autenticador, resete só aquele usuário: bin/magento security:tfa:reset admin google. Para listar provedores use security:tfa:providers, e para forçar globalmente um provedor use config:set twofactorauth/general/force_providers google. Padronize google ou duo em projetos novos.

Como protejo a cadeia de suprimentos e audito usuários admin via linha de comando?

No CI rode composer audit --locked (audita o composer.lock sem instalar) e adicione roave/security-advisories como dev-dependency (composer require --dev roave/security-advisories:dev-latest); em produção use composer install --no-dev. Para auditar admins, consulte o banco: SELECT username,email,is_active,created FROM admin_user ORDER BY created DESC e verifique oauth_token — qualquer conta ou token que ninguém reconhece é sinal de comprometimento. Desative a conta órfã e invalide os tokens (TRUNCATE TABLE oauth_token).

Como faço backup de uma loja Magento on-prem e o que o PCI DSS 4.0.1 exige?

Use mysqldump --single-transaction ignorando tabelas de log/relatório (--ignore-table), mais backup de pub/media e do app/etc/env.php (cifrado, guardado separado). Siga a regra 3-2-1 (3 cópias, 2 mídias, 1 off-site) e teste o restore em staging periodicamente. Sobre PCI: desde 31/03/2025 são obrigatórios os requisitos 6.4.3 (inventário e integridade de scripts na página de pagamento, mitigado com CSP+SRI) e 11.6.1 (detecção de alteração em scripts/headers, mitigado com CSP report-only + FIM). A maioria das lojas Magento cai no SAQ A-EP e precisa de ASV scan trimestral; no Brasil, incidentes com dados pessoais também acionam a LGPD/ANPD.

O que foi o SessionReaper e por que ele importa mesmo se já apliquei o CosmicSting?

SessionReaper é o CVE-2025-54236 (boletim APSB25-88, CVSS 9.1), uma falha crítica corrigida pela Adobe em 9 de setembro de 2025 que permite, sob certas condições, takeover de conta e execução remota não autenticada via Web API REST. Segundo a Sansec, após a análise pública do bug os ataques em massa começaram em 22 de outubro de 2025 e, em 1º de novembro, 81% das lojas já tinham sido sondadas. É uma vulnerabilidade distinta do CosmicSting: aplicar o patch de 2024 não protege contra ela. Aplique o hotfix da Adobe (VULN-32437-2-4-X-patch) imediatamente e monitore os access logs por POSTs suspeitos em /rest/.

Referências oficiais

  1. Quality Patches Tool — Usage (status/apply/revert) — Adobe Experience League
  2. Quality Patches Tool — busca de patches (IDs ACSD/MDVA) — Adobe Experience League
  3. Adobe Security Bulletins — Magento / Adobe Commerce — Adobe
  4. Software lifecycle policy (datas de fim de suporte 2.4.x) — Adobe Experience League
  5. Security update available for Adobe Commerce — APSB25-08 (KB ka-27149) — Adobe Experience League
  6. Action Required: Critical Security Update — APSB25-88 (SessionReaper, KB ka-27397) — Adobe Experience League
  7. SessionReaper, unauthenticated RCE in Magento & Adobe Commerce (CVE-2025-54236) — Sansec
  8. CosmicSting attack & defense overview (rotação de crypt key) — Sansec
  9. module-encryption-key-manager (rotação correta da crypt key / invalidar JWT) — Gene Commerce (GitHub)
  10. Configure file system ownership and permissions (find/chmod canônicos) — Adobe Experience League
  11. Configure Admin security (sessão, senha, lockout) — Adobe Experience League
  12. User roles (ACL / permissions) — Adobe Experience League
  13. Content Security Policies (csp_whitelist.xml / SRI) — Adobe Commerce Developer Documentation
  14. CSP & SRI against digital skimming — Sansec
  15. Web Application Firewall (WAF) powered by Fastly — Adobe Experience League
  16. Security scan (Security Scan Tool) — Adobe Experience League
  17. roave/security-advisories — bloqueio de pacotes vulneráveis — Roave (GitHub)
  18. What is Magecart? — Sansec
Precisa de um orçamento? Ficarei feliz em ajudar. Clique Aqui