⚠ Aviso de segurança de 23/10/2026 · rode o teste da seção Auditar hoje, em cada máquina
Grave · eMudhra + PHP2Shell · outubro de 2026

Guia v1.1.2 · atualizado em 23/10/2026

Atualize e audite seus sistemas hoje

O navegador já te protege. O seu servidor, o seu Python, o seu Node, os seus containers e o seu n8n talvez não. Aqui está como testar de verdade e como corrigir cada camada.

GRAVE: atualize seus sistemas agora. Chaves de certificados quebradas (uma era o número 4) + falha PHP2Shell
Em resumo

Este é um guia de segurança para quem tem servidor, site, automação ou agente de IA rodando. Em outubro de 2026 vieram duas notícias graves juntas: vazaram as chaves secretas de 8 certificados da certificadora eMudhra (uma delas era literalmente o número 4), e uma falha no PHP, chamada PHP2Shell, deixa invadir sites sem senha. O guia mostra como testar se cada ferramenta ainda está exposta, com um script que não instala nem altera nada (só executa os testes), e como atualizar cada camada. Precisa só de um terminal na máquina que você quer conferir.

O que houve

Duas falhas graves na mesma semana

Uma quebra a confiança do cadeado do HTTPS fora do navegador. A outra abre sites PHP para invasão sem login.

Auditoria de segurança: atualize, audite e teste a conexão real — sistema, Python, Node, Docker, n8n, PHP

1. eMudhra: chaves de certificado que cabiam num número pequeno

8

certificados intermediários

Da certificadora eMudhra, criados numa cerimônia em 30/04/2025: 6 para sites (TLS) e 2 para assinar programas. As cadeias passam pela raiz emSign ECC Root CA – G3 e pela cruzada AAA Certificate Services (Sectigo).

4

uma das chaves secretas

As chaves P-384 deveriam ser números gigantes. Saíram menores que 256: 4, 56, 66, 115, 155, 156, 230 e 237. Qualquer um acha em menos de um segundo. Descoberto em 17/10/2026 pelo pesquisador "Wayne".

?

por que é grave

Com a chave de uma certificadora confiável dá para emitir um HTTPS "válido" para qualquer site e ficar no meio da conexão. Até 21/10 não havia exploração confirmada, mas as chaves são públicas.

✅ Já protegidos

Navegadores: Chrome e Firefox (17/10), Microsoft (20/10), Apple (22/10). Firefox corrigido: 158.0.1 (NSS 3.131.1).

⚠ Precisam de você

Tudo o que não é navegador e tem lista própria de certificados: curl antigo, Python (certifi), Node, Go, Docker, n8n, agentes de IA, apps Electron, programas em Rust, aparelhos sem atualização.

2. PHP2Shell: invasão de site PHP sem senha

O que é

Duas falhas críticas no PHP (CVE-2026-109618 e CVE-2026-109619) que permitem executar comandos no servidor sem login. Aviso do time do PHP em 21/10/2026.

Quem está exposto

Toda instalação PHP que atende páginas: PHP-FPM, Apache com mod_php, php-cgi, FrankenPHP e LiteSpeed. Risco maior no mod_php. Inclui a maioria dos sites WordPress.

Versões corrigidas

8.5.12 · 8.4.27 · 8.3.36 · 8.2.35. Detalhes e o que fazer em PHP2Shell.

Auditar · teste a conexão real

Não basta ver a versão: teste se a ferramenta conecta

Os sites de teste abaixo usam certificados das cadeias comprometidas. Se a ferramenta conecta, ela ainda confia nas chaves quebradas. Se dá erro de certificado, está protegida.

Teste de 10 segundos (curl)

curl https://dv-g3b-emudhra.we-have-a-problem.com/
curl https://dv-g3b-aaa-emudhra.we-have-a-problem.com/
# "SSL certificate problem" = protegido
# mostrou a página        = EXPOSTO

Node e Python, do mesmo jeito

node -e "require('https').get('https://dv-g3b-emudhra.we-have-a-problem.com/',r=>{console.log('EXPOSTO');process.exit()}).on('error',e=>{console.log(/CERT|ISSUER|UNABLE_TO|SELF_SIGNED|DEPTH_ZERO|ERR_TLS/.test(e.code)?'protegido':'SEM TESTE',e.code);process.exit()})"

python3 -c "import ssl,urllib.request as u,urllib.error as E
try:
  import certifi; ctx=ssl.create_default_context(cafile=certifi.where())
except ImportError: ctx=ssl.create_default_context()
try: u.urlopen('https://dv-g3b-emudhra.we-have-a-problem.com/',context=ctx); print('EXPOSTO')
except E.HTTPError: print('EXPOSTO')
except Exception as e: print('protegido' if 'CERTIFICATE_VERIFY_FAILED' in str(e) else 'SEM TESTE', e)"

Erro de rede (ENOTFOUND, timeout) não é "protegido". Na dúvida, use o audita.sh.

Windows e macOS

O audita.sh é para Linux. No Windows (PowerShell) e no macOS (Terminal), o teste rápido é o mesmo curl: curl.exe https://dv-g3b-emudhra.we-have-a-problem.com/ (Windows) ou curl https://dv-g3b-emudhra.we-have-a-problem.com/ (macOS), e o mesmo para a cadeia dv-g3b-aaa-emudhra. Erro de certificado = protegido. Se você tem Node ou Python instalados, rode também os testes acima.

Teste as duas cadeias. Na nossa auditoria, várias ferramentas recusavam a cadeia AAA e aceitavam a G3 (ou o contrário). Um teste só pode dar falsa sensação de segurança. E erro de rede (DNS, timeout) não é "protegido": é "sem teste".

O script audita.sh: tudo de uma vez, sem alterar nada

Ele testa a conexão real com curl, wget, cada Node, Deno e Bun encontrado no disco (inclusive os embutidos em ferramentas), o Python do sistema, cada ambiente Python (venv ou conda) com o certifi dele e, por dentro, cada container Docker em execução. Confere a lista do Java (o arquivo cacerts), a versão do PHP no servidor e nos containers (PHP2Shell), o certifi desatualizado e os processos que precisam ser reiniciados. No fim, agrupa o que achou por tipo, com a correção de cada um. Não instala, não altera e não corrige nada, mas executa as ferramentas que testa (é assim que ele prova a conexão). É um levantamento: aponta problemas; não certifica que a máquina está segura.

1

Baixe o script e leia antes de rodar

Regra de ouro: nunca rode script da internet sem olhar o que ele faz. Este tem cerca de 650 linhas, comentadas.

curl -fsSLO https://raw.githubusercontent.com/inematds/auditoria-seguranca/main/audita.sh
less audita.sh            # leia
chmod +x audita.sh

Ou clone o repositório: git clone https://github.com/inematds/auditoria-seguranca.

2

Rode na máquina

Sem sudo ele audita o seu usuário (e os containers, se você está no grupo docker). Com sudo ele enxerga também as pastas de outros usuários. A busca no disco leva até 3 minutos; --rapido pula essa parte.

./audita.sh                 # completo
sudo ./audita.sh            # inclui containers Docker e outros usuários
./audita.sh --rapido        # sem procurar venvs, Node, Deno, Bun e JDKs no disco
./audita.sh --sem-docker    # não entra nos containers
./audita.sh --comandos      # mostra os comandos de correção (não executa nenhum)
./audita.sh --go            # também testa o Go de verdade (veja abaixo)
./audita.sh --raiz /opt     # busca no disco só nessa pasta (repita --raiz para várias)

Dois testes não são de conexão, e o script avisa isso na linha. Java: ele confere se o arquivo de confiança do Java (cacerts) ainda tem as duas raízes, porque testar a conexão exigiria compilar código Java. Go: o teste real compila um programa pequeno numa pasta temporária (cerca de 100 MB, apagada no fim). Como isso grava no disco, só roda com --go.

Com sudo, o Node instalado pelo nvm do seu usuário não fica no caminho padrão; ele ainda é achado pela busca no disco (não use --rapido nesse caso).

Com sudo, o script não roda como root o que outra pessoa pode trocar. Um programa achado no disco (Node, Deno, Bun, o Python de um venv, o keytool de um JDK) só é executado se o arquivo, cada link e cada pasta do caminho até a raiz forem do root ou do dono e mais ninguém puder gravar neles. O programa de um usuário roda como esse usuário (runuser), nunca como root. O que não passa vira SEM TESTE, com o motivo. Mesmo assim, rode como root só em máquina que você administra.
3

Leia o resultado: cinco estados

== meuservidor · Ubuntu 24.04.3 LTS · 23/10/2026 02:36 · audita.sh 1.1.2
-- versões
ca-certificates 20260816ubuntu0.24.04.1
-- conexão real (G3 = cadeia emSign ECC Root CA - G3 · AAA = cadeia cruzada AAA Certificate Services)
protegido  curl                               G3
protegido  curl                               AAA
EXPOSTO    node v24.13.0 /usr/bin/node        G3
protegido  node v24.13.0 /usr/bin/node        AAA
          versão: ATUALIZE para 24.22.1+
EXPOSTO    deno 2.7.14 ~/.deno/bin/deno       G3
EXPOSTO    venv ~/projetos/app/.venv           G3
protegido  venv ~/projetos/app/.venv           AAA
-- Java: lista de confiança (cacerts) — conferida pelo keytool, sem conexão
EXPOSTO    java /etc/ssl/certs/java/cacerts   G3   (lista do keystore, não conexão)
-- containers Docker em execução (teste de dentro de cada um)
   [n8n] n8nio/n8n:2.4.8
   EXPOSTO    node v20.19.0                      G3
   [painel-db] adminer:latest
   EXPOSTO    php 8.4.21                         PHP  PHP2Shell: atualize para 8.4.27+
-- processos a reiniciar (não reinicie no meio de job, upload ou live; escolha a hora)
REINICIE   pid 3361     meubot.service (sistema)           /usr/bin/node dist/index.js
           o binário foi atualizado depois que o processo começou
-- resumo meuservidor: 7 EXPOSTO · 0 SEM TESTE · 0 VERIFIQUE · 1 REINICIE
-- o que corrigir, por tipo (conta itens; o resumo acima conta linhas, 2 por item: G3 e AAA)
     1  Node
        ex.: /usr/bin/node (v24.13.0)
        → sudo apt update && sudo apt install --only-upgrade nodejs
     1  Deno
        ex.: ~/.deno/bin/deno (2.7.14)
        → ~/.deno/bin/deno upgrade
     1  containers de versão fixada (grupo C: subir a versão)
        ex.: n8n (n8nio/n8n:2.4.8)
        → versão fixada: a tag pode ter sido republicada com a base corrigida — salve o digest atual, puxe a mesma tag e recrie; se o digest não mudou ou seguir EXPOSTO, suba a versão
     1  processos a reiniciar (fora de job, upload ou live)
        ex.: meubot.service (sistema)
        → sudo systemctl restart meubot.service

Trecho de exemplo (a saída real tem mais linhas; o script escreve em português). G3 e AAA são os dois "caminhos de confiança" testados: cada certificado de site é assinado por um intermediário, que é assinado por uma raiz que o seu programa confia.

  • EXPOSTO: conectou (ou, no Java, a lista ainda tem as raízes; no PHP, a versão é vulnerável). Corrija essa camada (seção Atualizar).
  • protegido: recusou por erro de certificado.
  • SEM TESTE: o teste não terminou ou não prova nada. Rede, DNS, timeout, container sem shell ou sem ferramenta, saída cortada, SSH que falhou, busca no disco interrompida, programa que não é seguro executar, ou o wget do busybox com recusa ambígua (essa só conta como protegida se a lista de certificados do sistema não tem a raiz daquela cadeia). Nunca conta como protegido. Repita ou teste de outro jeito antes de concluir.
  • VERIFIQUE: PHP em versão de teste (RC, beta, dev: número maior não prova correção) ou abaixo da versão corrigida, mas com número da distribuição (pode ter a correção por backport). No Debian/Ubuntu o script já procura o CVE no changelog local do pacote; se não achar, confira com apt changelog <pacote> | grep CVE-2026-109618. Conferido e corrigido? Rode com --php-ok e o VERIFIQUE deixa de segurar a saída.
  • REINICIE: processo que começou antes da correção e ainda usa o programa ou a lista antiga. Veja quem reiniciar.

No fim vem o bloco o que corrigir, por tipo: quantos itens de cada tipo, um exemplo e o comando sugerido. O resumo conta linhas (cada ferramenta aparece duas vezes, G3 e AAA); o bloco conta itens. Com --comandos sai a lista completa, um comando por item, para você revisar e rodar um por um. O script nunca executa a correção.

No fim vem também o bloco escopo desta rodada: o que ficou de fora (--rapido, --sem-docker, --raiz, --php-ok, Go sem --go, e com qual usuário rodou).

Código de saída: 0 as verificações feitas passaram, dentro do escopo impresso; 1 há EXPOSTO; 2 há SEM TESTE; 3 nada conecta, mas falta reiniciar processos ou conferir o PHP. Saída 0 não quer dizer que a máquina inteira está segura. Em monitoramento, trate qualquer saída diferente de 0 como alerta e não libere deploy automático só pela saída 0.

4

Audite vários servidores por SSH

O script vai pelo próprio SSH e roda lá, sem copiar nada para o servidor. Use acesso por chave.

./audita.sh --ssh root@servidor1 --ssh deploy@servidor2

# ou uma lista (um destino por linha; não versione esse arquivo)
./audita.sh --hosts servidores.txt

# chave SSH específica (vale para todos os destinos)
./audita.sh --chave ~/.ssh/minha_chave --ssh root@servidor1

Se o SSH não conecta ou cai no meio (tempo esgotado, bash ausente lá), a linha diz SEM TESTE com o código e a saída não é 0: um servidor que não respondeu nunca passa como limpo. Exemplo de servidores.txt: uma linha root@203.0.113.10, outra deploy@vps2 # loja. Lá, o script roda com o usuário do SSH: para ver os containers e as pastas de outros usuários, entre como root (ou com um usuário do grupo docker).

5

Corrija, reinicie e rode de novo

A auditoria só termina quando o resumo mostra 0 EXPOSTO · 0 SEM TESTE · 0 VERIFIQUE · 0 REINICIE (saída 0) em todas as máquinas, numa passada completa (sem --rapido, e com o bloco de escopo sem nada importante de fora). Processo que já estava rodando guarda a lista antiga na memória: atualizar sem reiniciar não resolve.

Como atualizar · camada por camada

Atualizar o sistema não basta

Cada ferramenta pode ter a própria lista de certificados confiáveis. O ca-certificates do sistema protege o curl e quem usa a lista do sistema. Node, Python com certifi, Go, Electron, Rust e cada container têm a sua.

⚠ Antes de atualizar qualquer coisa: backup e saúde. Depois: teste e valide.

Antes. Faça backup do que não volta: banco, volumes, a imagem antiga com uma tag de reserva e a configuração. E confira que o sistema está saudável agora, para saber comparar depois.

docker exec meu-postgres pg_dumpall -U postgres > backup-$(date +%F).sql   # banco
docker tag meuapp:latest meuapp:pre-update                              # imagem antiga de reserva
docker inspect meuapp > meuapp.inspect.json                             # portas, env, volumes
sudo tar czf config-$(date +%F).tgz /etc/nginx /caminho/do/app/.env       # configuração
curl -fsS https://meusite/health && echo OK                             # saúde antes
docker exec meu-postgres psql -U postgres -d app -c "select count(*) from clientes"   # anote os números

Depois. Muita coisa que rodava pode ficar incompatível com a versão nova. Nós vimos uma dependência sem trava quebrar um app, um venv ressincronizar e desfazer a correção, e um proxy guardar o endereço antigo e responder 502. Então teste e valide: o serviço subiu, responde, faz o que fazia e os dados estão lá (mesmas contagens). Só dê por encerrado quando estiver ok e no ar.

CamadaVersão corrigidaOnde a lista mora
Ubuntu20260816ubuntu0.<versão do Ubuntu> (ex.: 20260816ubuntu0.24.04.1)sistema
Debian20250419~deb12u2 (Debian 12) · 20250419+deb13u1 (Debian 13)sistema
Alpineca-certificates 20261020-r0sistema / containers
Pythoncertifi 2026.10.19 · pip 26.2.2 · uv 0.13.5 · o próprio Python: 3.15.1 · 3.14.9 · 3.13.17em cada venv
Node22.23.4 · 24.22.1 · 26.12.1 (Node 20: sem correção)dentro do binário
Go1.27.3 · 1.26.10 + recompilarno Linux, a do sistema (ver passo 4)
Java (OpenJDK)as duas raízes removidas do cacerts (keytool -delete) ou JDK atualizadoarquivo cacerts de cada JDK
Deno · Bunrecusaram as duas cadeias no nosso teste: Deno 2.9.8 · Bun 1.4.4dentro do binário
Firefox / NSS158.0.1 · NSS 3.131.1navegador
Electron44.9.1 · 43.8.1 · 42.11.16dentro de cada app
Rustrustls-webpki 0.103.16 · webpki-roots 1.0.10dentro de cada programa
FreeBSDca_root_nss 1.131.1 (ports) · base: 15.1-RELEASE-p6 · 14.5-RELEASE-p3 · 14.4-RELEASE-p12 (FreeBSD-SA-26:73)sistema
1

Sistema (ca-certificates)

# Ubuntu / Debian
sudo apt update && sudo apt install --only-upgrade ca-certificates
dpkg-query -W ca-certificates          # confira a versão
# Alpine
apk upgrade --no-cache ca-certificates
# Fedora / RHEL
sudo dnf upgrade ca-certificates

Deixe a atualização automática ligada (unattended-upgrades no Ubuntu/Debian). No nosso caso foi ela que corrigiu o sistema sozinha.

2

Python: o certifi de cada ambiente

O requests e muitos pacotes usam o certifi, não a lista do sistema. Cada venv tem o seu. O audita.sh lista os desatualizados.

/caminho/do/venv/bin/python -m pip install -U certifi pip
python3 -c "import certifi; print(certifi.__version__, certifi.where())"  # 2026.10.19 ou mais
# ferramentas instaladas com pipx / uv tool
pipx upgrade-all        # ou: pipx runpip <app> install -U certifi
uv self update          # uv 0.13.5 ou mais
uv tool upgrade --all
# ambientes conda (miniconda/anaconda)
conda update -n <ambiente> certifi

Atenção ao Python "do sistema": se aparecer EXPOSTO no modo certifi, existe um certifi instalado com pip --user na sua pasta (~/.local/lib/python3.X/). O caminho mostrado pelo comando acima diz qual é. No Ubuntu 24.04 e no Debian 12+ o pip recusa mexer ali ("externally-managed-environment"); como a mudança fica só na sua pasta de usuário, use:

python3 -m pip install --user --break-system-packages -U certifi

O certifi do pacote do sistema (/usr/lib/python3/dist-packages) não precisa disso: no Debian/Ubuntu ele aponta para a lista do ca-certificates.

Venv sem pip (criado pelo uv): o comando -m pip falha. Use o uv apontando para o Python do venv:

uv pip install --python /caminho/do/venv/bin/python -U certifi
Armadilha do uv run: projeto que sobe com uv run ressincroniza o venv pelo uv.lock a cada início e desfaz o certifi novo. Suba o certifi no próprio lock: uv lock --upgrade-package certifi && uv sync. O audita.sh avisa quando o venv tem um uv.lock ao lado.
3

Node: troque de versão (Node 20 não tem correção)

O Node traz a lista de certificados dentro do próprio programa. Atualizar o sistema não muda nada para ele.

# com nvm
nvm install 22 && nvm alias default 22     # 22.23.4 ou mais
# com o repositório NodeSource (apt): ele é separado por versão.
# Se está no 20, troque o repositório para o 22 antes de instalar:
curl -fsSLo nodesource_setup.sh https://deb.nodesource.com/setup_22.x
less nodesource_setup.sh                  # leia
sudo bash nodesource_setup.sh && sudo apt install -y nodejs
# já está no 22 ou 24? basta atualizar
sudo apt update && sudo apt install --only-upgrade nodejs
node -v

Paliativo para quem não consegue trocar hoje (testado por nós): mandar o Node usar a lista do sistema já atualizada. Só funciona se o ca-certificates estiver corrigido. --use-system-ca não serve: ele soma as duas listas e continuou EXPOSTO no nosso teste.

export NODE_OPTIONS=--use-openssl-ca   # no serviço/ambiente do app; depois reinicie

Node baixado do nvm ou do nodejs.org pode não achar a lista do sistema sozinho: se o teste der erro em tudo, acrescente SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt e confirme com o audita.sh. NODE_OPTIONS é a variável de ambiente que passa opções para todo Node iniciado ali (no systemd: Environment=NODE_OPTIONS=--use-openssl-ca; no Docker: environment: do compose).

Procure também Node embutido: o Playwright, agentes de IA e apps trazem um Node próprio. O audita.sh acha esses binários no disco; atualize a ferramenta que os trouxe. No caso do Playwright dentro de um venv Python, o Node fica em …/site-packages/playwright/driver/node: atualize o pacote (pip install -U playwright; no npm, npm i playwright@latest) ou use o paliativo NODE_OPTIONS=--use-openssl-ca no serviço que o usa.

4

Go: atualize e recompile

Instale Go 1.27.3 ou 1.26.10 e gere o binário de novo (go build). Programas Go de terceiros: atualize para a versão que eles lançarem depois de 21/10.

O que vimos no nosso teste, no Linux: um programa feito com Go 1.26.3 recusou as duas cadeias com a lista do sistema corrigida e conectou quando apontamos para uma lista antiga (SSL_CERT_FILE). Ou seja, no Linux o Go segue a lista do sistema, ou a do container onde roda. Corrigir o ca-certificates já protege boa parte dos programas Go; quem embute a própria lista (pacote x509roots/fallback) ou roda em container com imagem antiga continua exposto. Por isso recompile mesmo assim. Para o teste real: ./audita.sh --go.

5

Java: o arquivo cacerts fica para trás

O Java não lê a lista do sistema direto: ele usa um arquivo próprio, o cacerts. No Debian/Ubuntu esse arquivo é gerado a partir do ca-certificates, mas pode não ser refeito na atualização. No nosso caso, o sistema já estava corrigido e o cacerts do OpenJDK 8 ainda era de 2025, com as duas raízes dentro.

# onde está o cacerts do Java que você usa
readlink -f $(dirname $(readlink -f $(which java)))/../lib/security/cacerts
ls /usr/lib/jvm/                        # outros JDKs instalados
# ainda tem as raízes?
keytool -list -keystore /etc/ssl/certs/java/cacerts -storepass changeit | grep -qiE "emsign_ecc_root_ca_-_g3|comodo_aaa" && echo EXPOSTO || echo limpo
# Debian/Ubuntu: guarde uma cópia e tire as duas raízes do cacerts
sudo cp -a /etc/ssl/certs/java/cacerts /etc/ssl/certs/java/cacerts.bak
sudo keytool -delete -alias debian:emsign_ecc_root_ca_-_g3.pem -keystore /etc/ssl/certs/java/cacerts -storepass changeit
sudo keytool -delete -alias debian:comodo_aaa_services_root.pem -keystore /etc/ssl/certs/java/cacerts -storepass changeit
Só update-ca-certificates -f não basta. No nosso teste ele regravou o arquivo, mas as duas raízes continuaram lá, mesmo com o sistema já corrigido. Por isso a remoção é feita à mão, com o keytool -delete acima. Os nomes (debian:…) são os do Debian/Ubuntu; em outro JDK, use o nome que o keytool -list mostrar. Depois rode o teste de novo: tem que dar limpo. O sistema corrigido já tirou essas duas raízes da lista dele (confira: grep -iE "emSign_ECC_Root_CA_-_G3|Comodo_AAA" /etc/ca-certificates.conf mostra as duas com ! na frente, que quer dizer "desconfiar"); o cacerts só ficou para trás. Só remova do Java depois de ver isso. Se algum site parar de abrir num programa Java depois disso, atualize o JDK e o ca-certificates-java em vez de devolver a raiz. Lembre: no Java o audita.sh confere a lista, não faz conexão.

JDK com cacerts próprio (baixado à parte, dentro de uma IDE, num /opt): atualize o JDK. changeit é a senha padrão do arquivo, pública; ela só protege contra gravação acidental. Depois, reinicie os programas Java.

6

Deno e Bun: a lista vem dentro do programa

Como o Node, os dois trazem a lista embutida. No nosso teste, Deno 2.7.12 e Bun 1.3.13 conectaram; depois de atualizar, Deno 2.9.8 e Bun 1.4.4 recusaram as duas cadeias.

deno upgrade
bun upgrade
# teste de uma linha (erro de certificado = protegido)
deno eval "await fetch('https://dv-g3b-emudhra.we-have-a-problem.com/')"
bun -e "await fetch('https://dv-g3b-emudhra.we-have-a-problem.com/')"

Os dois ficam na pasta do usuário (~/.deno, ~/.bun): rode a atualização com o usuário dono, não com sudo.

7

Docker: cada container é uma máquina

O container não usa o ca-certificates do servidor. Imagem antiga = lista antiga. A correção depende de como a imagem chegou aí, e o audita.sh já separa os containers expostos nesses grupos (pelo docker inspect, sem mexer em nada).

A · Tag flutuante

redis:alpine, nginx:alpine, postgres:16-alpine, …:latest, …:main. O fornecedor troca a imagem por trás da mesma tag: basta puxar e recriar.

B · Construída por você

Imagem feita com docker build a partir de um Dockerfile seu. Reconstruir do zero pode quebrar o app; o caminho seguro é a camada por cima.

C · Versão fixada

kong:2.8.1, gotrue:v2.197.0. A tag muda pouco, mas não é imutável: o fornecedor pode republicar a mesma tag com a base corrigida (o que nunca muda é o digest, sha256:…). Puxe de novo e compare o digest; se não mudou ou continua EXPOSTO, suba a versão, e às vezes espere o fornecedor.

Container sem curl, wget, Node ou Python (ex.: postgres:16 Debian) aparece como SEM TESTE "nenhuma ferramenta de teste" (conta como teste que não rodou, e a saída não é 0): o script não tem como testar a conexão. Confira a lista pelo pacote (docker exec <c> dpkg-query -W ca-certificates ou apk list -I ca-certificates-bundle) e compare com a tabela de versões.

Grupo A: puxe e recrie

docker compose up -d --pull always redis      # um serviço; repita para os outros
docker compose pull && docker compose up -d   # todos de uma vez (veja o aviso sobre bancos abaixo)
# prova: o pacote da lista dentro do container (Alpine)
docker compose exec redis apk list -I ca-certificates-bundle   # 20261020-r0 ou mais

Testando à mão com o wget do busybox (Alpine), a recusa aparece como ssl_client: SSL_connect ou Connection reset by peer, que parece erro de rede. Para ter certeza, teste um site bom com o mesmo wget (wget -O /dev/null https://www.google.com/): se esse abre, a falha do site de teste foi de certificado. O audita.sh já faz essa conferência.

Banco com tag latest ou sem versão principal (postgres:latest, mysql:latest, mongo): o pull pode trazer uma versão principal nova (ex.: Postgres 16 → 18), que não abre o diretório de dados antigo. Antes do pull, fixe a versão principal que você usa hoje (postgres:16-alpine) e faça o pg_dumpall.
  • Container sem compose (criado com docker run): salve a receita antes, docker inspect redis > redis.inspect.json, e olhe .Mounts. Sem volume, os dados morrem com o container: no Redis, redis-cli save e docker cp redis:/data/dump.rdb . antes; depois suba o novo com as mesmas portas e variáveis e devolva o arquivo.
  • Imagem nova já no disco, container antigo rodando: acontece quando outro projeto puxou a mesma tag. Não precisa baixar nada, só recriar (docker compose up -d <serviço>). O audita.sh mostra esses como "só recriar".
  • "latest" ainda antiga: o fornecedor pode não ter publicado a imagem corrigida. No nosso caso, uma imagem :main de 16/10 continuou EXPOSTA depois do pull. Aí use a camada por cima (abaixo) até sair a oficial.

Grupo B: a camada por cima da imagem antiga

Reconstruir com docker compose build --pull baixa a base nova e todas as dependências sem trava de versão. No nosso caso, um app em Node subiu plugins novos e parou de funcionar. A técnica que funcionou: manter a imagem que já roda e só trocar a lista e o Node por cima.

# 1) guarde a imagem atual com outro nome (é o seu caminho de volta)
docker tag meuapp:latest meuapp:pre-emudhra

# 2) Dockerfile.emudhra
FROM meuapp:pre-emudhra
USER root                                                                       # instalar exige root
COPY --from=node:22-bookworm-slim /usr/local/bin/node /usr/local/bin/node   # só se a imagem tem Node 22; mesma versão principal e mesma distro da base
RUN apt-get update && apt-get install -y --only-upgrade ca-certificates && rm -rf /var/lib/apt/lists/*
# Alpine: RUN apk add -u ca-certificates ca-certificates-bundle
RUN python3 -m pip install -U certifi                                           # se o app tem Python com pip próprio
USER app                                                                        # volte ao usuário original (veja: docker inspect -f '{{.Config.User}}' meuapp:pre-emudhra)

# 3) construa por cima e recrie
docker build -f Dockerfile.emudhra -t meuapp:latest .
docker compose up -d meuapp
# deu errado? volte: docker tag meuapp:pre-emudhra meuapp:latest && docker compose up -d meuapp
  • O Node copiado tem que ter a mesma versão principal do que já está na imagem (22 por 22): módulos nativos (.node) feitos para o Node 20 não carregam no 22. Imagem em Node 20 não tem como ser corrigida assim; ela precisa ser reconstruída com base 22.
  • Se o pip recusar com externally-managed-environment, o Python é o do sistema da imagem (apt): o ca-certificates atualizado já cobre esse Python. Tire a linha do pip; não use || true, que esconde o erro.
  • O node:22-… do COPY tem que ser da mesma família da base (bookworm com Debian 12, alpine com Alpine), senão o binário não roda.
  • App que sobe com uv run: o uv ressincroniza o venv pelo uv.lock e desfaz o certifi novo. Suba o certifi no lock, ou troque só o arquivo cacert.pem do certifi travado pelo da lista nova.
  • Tem um nginx na frente (proxy)? Depois de recriar o app, reinicie o nginx também: ele guarda o endereço interno antigo e passa a responder 502.
  • docker-compose antigo (1.29) com KeyError: 'ContainerConfig': docker-compose rm -f -s <serviço> antes do up.
  • Com calma, depois: troque a base node:20 por node:22 (ou 24) no Dockerfile, trave as versões das dependências e reconstrua de verdade (docker compose build --pull).

Grupo C: versão fixada (e o Supabase local)

Suba a versão no compose ou na configuração para uma lançada depois de 21/10/2026 e teste de novo. Se o fornecedor ainda não reconstruiu a imagem, não há o que puxar: a espera é por ele.

Supabase local (o da CLI supabase start) é o caso típico: a CLI fixa cada imagem. O caminho é este, nesta ordem:

# 1) backup do banco (com tudo rodando)
docker exec supabase_db_<projeto> pg_dumpall -U postgres > supabase-backup.sql
# 2) CLI nova + parar e subir (na pasta do projeto; sem --no-backup, que apagaria os dados)
npx -y supabase@latest stop && npx -y supabase@latest start
# 3) teste de novo
./audita.sh                       # completo: o --rapido não acha venvs nem quem reiniciar
  • Nunca remova os containers do Supabase à mão (docker rm): use a CLI.
  • No nosso teste, a CLI nova trouxe imagens novas, mas 10 de 12 containers continuaram EXPOSTOS: só algumas imagens já vinham corrigidas. Enquanto o fornecedor não reconstruir, não há o que fazer além de esperar e testar de novo.
  • Não troque a imagem oficial por uma camada local com a mesma tag: o próximo supabase start descarta a sua.
  • O start pode subir mais serviços do que antes (no nosso caso, de 4 para 12 containers). Confira a memória da máquina.
  • Risco: com tudo só em localhost, a exposição é baixa. Quem faz conexão HTTPS de saída são poucos serviços (autenticação, painel, logs).
8

n8n: backup, depois atualize

O n8n roda em Node. Versão antiga = Node antigo dentro do container.

# 1) ache o nome do volume (o compose costuma pôr o nome da pasta na frente)
docker volume ls | grep n8n                 # ex.: meuprojeto_n8n_data
# 2) pare o n8n e faça o backup (com ele parado o banco não sai pela metade)
docker compose stop n8n
docker run --rm -v meuprojeto_n8n_data:/data -v "$PWD":/bk alpine tar czf /bk/n8n-backup.tgz -C /data .
# 3) se o compose fixa a versão (image: n8nio/n8n:2.4.8), troque pela atual antes:
#    image: n8nio/n8n:2.44.6   (ou mais nova; confira as notas de versão)
# 4) atualize
docker compose pull n8n && docker compose up -d n8n
docker compose exec n8n node -v                     # confirme o Node novo

Puxar a mesma tag pode trazer uma imagem republicada. Confira se o digest mudou (docker image inspect -f '{{.RepoDigests}}' n8nio/n8n:2.4.8, antes e depois): se não mudou, subir a versão é o caminho.

Armadilha do docker-compose antigo (1.29): ao subir a imagem nova ele pode falhar com KeyError: 'ContainerConfig' e deixar o n8n parado. Remova o container antigo antes (os dados ficam no volume): docker-compose rm -f -s n8n && docker-compose up -d n8n. Melhor ainda: migre para o docker compose v2.

Outros jeitos de guardar os dados: se o n8n usa uma pasta montada (ex.: ./n8n_data ou ~/.n8n), copie a pasta com ele parado; se usa Postgres, faça o pg_dump do banco. Em todos os casos guarde junto a chave de criptografia (N8N_ENCRYPTION_KEY ou o arquivo config da pasta do n8n): sem ela as credenciais salvas não voltam.

Depois de atualizar, olhe o log (docker compose logs n8n). Saltos grandes de versão podem trazer avisos novos, como o do executor de Python do nó Code.

9

Windows, macOS, celular e apps

  • Windows: Windows Update (correção da Microsoft de 20/10).
  • macOS / iPhone: atualização do sistema (Apple, 22/10).
  • Navegadores: reinicie o navegador para aplicar; Firefox 158.0.1 ou mais.
  • Apps Electron (VS Code, Slack, Discord e outros): atualize cada app. Quem desenvolve: Electron 44.9.1 / 43.8.1 / 42.11.16.
  • Rust: cargo update -p rustls-webpki -p webpki-roots e recompile.
10

Reinicie os serviços (e ache quem reiniciar)

Programa que já está rodando segurou a lista antiga na memória: atualizar sem reiniciar não resolve. Bots, APIs, workers, agentes, o n8n e qualquer servidor Python com venv (uvicorn, gunicorn) precisam reiniciar depois da correção. O audita.sh lista na seção processos a reiniciar quem ainda roda com o programa antigo (o Node foi trocado depois que o processo começou) ou começou antes do certifi novo do venv dele.

# à mão: processos Python de venv e Node, com o tempo de execução em segundos
ps -eo pid,etimes,args | grep -E "venv/bin/python|/node " | grep -v grep
# serviços que estão rodando
systemctl list-units --type=service --state=running
systemctl --user list-units --type=service --state=running
pm2 list
# reinicie
sudo systemctl restart <servico>     # serviços do sistema
systemctl --user restart <servico>   # serviços do seu usuário
pm2 restart all                       # apps Node gerenciados pelo pm2 (pm2 update reinicia o próprio pm2)
docker compose up -d                  # containers: recria com a imagem nova

Na lista do script, cada processo vem com o que reiniciar: (sistema) é sudo systemctl restart, (--user) é systemctl --user restart, e processo avulso é algo que você abriu no terminal, no tmux ou no pm2: reinicie só aquele programa.

Nunca reinicie user@1000.service (ou outro número): é a sessão inteira do usuário. Derruba terminais, tmux, jobs e uploads de uma vez.
Escolha a hora. Não reinicie no meio de um trabalho longo: render, upload, transmissão ao vivo, migração de banco, fila de jobs. Espere terminar ou avise quem usa. Reiniciar o processo errado pode custar mais do que alguns minutos de exposição.

Atenção: docker compose restart só reinicia o mesmo container, com a imagem velha. Depois de pull ou build, o certo é docker compose up -d.

E rode o audita.sh de novo.

⚠ Antes de dar por encerrado: valide

Atualizou? Confira, serviço por serviço: subiu (docker compose ps, systemctl status), responde (curl -fsS https://meusite/health), faz o que fazia (abra, faça login, rode um fluxo) e os dados estão lá (as mesmas contagens de antes). Deu errado: volte com a imagem de reserva (docker tag meuapp:pre-update meuapp:latest && docker compose up -d meuapp). Atenção: se a versão nova já migrou o banco (n8n, Postgres, apps com migração), a imagem antiga não abre o banco migrado: aí a volta é restaurar o backup do volume ou o dump junto com a imagem antiga. Em salto grande de versão, teste antes numa cópia do volume. E rode o audita.sh de novo. Só termina quando estiver ok e no ar.

Caso real

Auditamos nossos servidores. O sistema estava certo. O resto não.

Na madrugada de 23/10/2026 auditamos 6 servidores com este método. Sem nomes nem endereços: o que importa é o padrão.

O que já estava certo

O ca-certificates dos 6 servidores já estava corrigido pela atualização automática. O curl recusava as cadeias ruins.

O que continuava exposto

Node, ambientes Python (certifi) e containers, inclusive o n8n, seguiam confiando nas chaves quebradas.

O que corrigimos

  • Node 20 → 22
  • certifi dos ambientes Python
  • Node embutido de um agente
  • n8n 2.4.8 → 2.44.6 (com backup)
  • 4 containers atualizados (imagem nova e recriação)

Duas armadilhas de infraestrutura

1. docker-compose antigo

O n8n não subiu depois do pull: o docker-compose 1.29 deu KeyError 'ContainerConfig' com a imagem nova e o serviço ficou parado por uns minutos. Resolveu remover o container antigo antes do up.

2. Arquivo montado a partir do /tmp

Um nginx em container montava a configuração de um arquivo em /tmp. A limpeza do /tmp transformou o caminho numa pasta vazia, e ao recriar o container o nginx não subiu. Configuração montada tem que morar numa pasta permanente.

A máquina de desenvolvimento: de 134 para quase zero

No mesmo dia rodamos o audita.sh na máquina de trabalho, a que tem dezenas de projetos, ambientes Python e containers locais. O sistema e o curl estavam protegidos. O resto não: 134 linhas EXPOSTO. Corrigindo camada por camada, caiu para 70 e depois sobrou só o que dependia de fornecedor. O que aprendemos, na ordem em que apareceu:

1. Os testes que faltavam

  • Java: o cacerts do OpenJDK 8 era de 2025 e ainda tinha as duas raízes, com o sistema já corrigido. update-ca-certificates -f regravou o arquivo, mas as raízes ficaram: foi preciso tirar as duas com keytool -delete.
  • Deno e Bun conectavam (2.7.12 e 1.3.13). deno upgrade e bun upgrade resolveram.
  • Go 1.26.3 recusou no teste real: ele segue a lista do sistema.

2. Python: onde o certifi se esconde

  • Um certifi antigo instalado com pip --user na pasta do usuário passava na frente do certifi do sistema e deixava o "python3 do sistema" EXPOSTO.
  • Venvs criados pelo uv não têm pip: uv pip install --python ….
  • Projetos com uv run desfaziam a correção a cada início, pelo uv.lock.

3. Containers: três grupos, três correções

  • Tag flutuante: puxar e recriar resolveu a maioria (bancos, cache, proxy).
  • Imagem própria: reconstruir do zero quebrou um app (dependências sem trava). A camada por cima da imagem antiga funcionou, com a antiga guardada como :pre-emudhra.
  • Supabase local: CLI nova + stop/start trouxe imagens novas, mas a maioria seguiu EXPOSTA. É espera pelo fornecedor.

4. O que não se vê na lista

  • Um painel de banco (adminer) em container rodava PHP 8.4.21, vulnerável ao PHP2Shell, aberto numa porta. Imagem nova: 8.4.27.
  • O Node embutido do Playwright, dentro de um venv, seguia EXPOSTO até atualizar o pacote.
  • Bots e serviços continuaram usando o Node antigo até serem reiniciados.
Num servidor já corrigido, o script ainda apontou um Node antigo numa pasta de backup (.bak) esquecida depois da troca de versão: não rodava, mas é o tipo de sobra que alguém executa por engano. Apague cópias antigas que não usa. O padrão de tudo isso é um só: quem não é o sistema fica para trás.
PHP2Shell

Seu site PHP pode ser invadido sem senha

Aviso do time do PHP de 21/10/2026: duas falhas críticas permitem executar código no servidor sem login. A falha está no PHP, antes do WordPress ou de qualquer aplicação abrir a página. Plugin de segurança não segura.

As duas falhas

  • CVE-2026-109618: o tratamento de falta de memória interrompe o PHP com dados da requisição pela metade, e a limpeza trata esses dados como válidos.
  • CVE-2026-109619: um estado interno da requisição sobrevive depois de a memória ser liberada, e a requisição seguinte do mesmo processo usa esse estado velho.

Quem está exposto

  • Toda instalação PHP que atende páginas: PHP-FPM, Apache com mod_php, php-cgi, FrankenPHP, LiteSpeed.
  • Risco maior no mod_php: há ataque demonstrado que funciona antes de qualquer código da aplicação rodar.
  • PHP roda por baixo de cerca de 7 em cada 10 sites, inclusive a maioria dos WordPress.

Seu caso, em três situações

1. Plataforma pronta

Shopify, Wix, Squarespace: o PHP é responsabilidade da empresa. Confira o aviso de segurança da plataforma; se não achar, pergunte ao suporte.

2. WordPress em hospedagem

Teste o site (passo abaixo) e veja a versão do PHP no painel da hospedagem. Abaixo da versão corrigida: peça ao provedor para atualizar hoje.

3. Servidor próprio (VPS)

É com você e é urgente: ataques começaram antes mesmo de a correção sair. Atualize, reinicie e procure sinais de invasão.

1

Teste: o verificador da OpenAI

A OpenAI publicou o php2shell-checker (licença Apache 2.0). Ele lê a versão que o site anuncia nos cabeçalhos; não manda ataque. Precisa de git e Node instalados no seu computador (não no servidor).

git clone https://github.com/openai/php2shell-checker && cd php2shell-checker
node phpcheck.js "https://seusite.com.br"
# OK (0) · POTENTIALLY VULNERABLE (1) · UNKNOWN (3)

Ou peça ao seu agente (Claude Code, Codex): "rode o php2shell-checker só contra o meu site e me explique o resultado". UNKNOWN não é sinal verde: o site esconde a versão; confira no painel ou no servidor.

2

No servidor: veja a versão de verdade

php -v
php-fpm8.3 -v                       # ajuste o número da versão
apt changelog php8.3-fpm | head -30  # Debian/Ubuntu: procure CVE-2026-109618
# PHP dentro de containers (painéis, WordPress, adminer, phpMyAdmin)
for c in $(docker ps --format '{{.Names}}'); do echo "$c: $(docker exec $c php -v 2>/dev/null | head -1)"; done

Não esqueça os containers: imagem com PHP antigo é tão vulnerável quanto o servidor, e painéis de banco costumam ficar expostos numa porta. O audita.sh já mostra a versão do PHP de cada container e do servidor e compara com as corrigidas (EXPOSTO abaixo delas; VERIFIQUE quando o número é de pacote da distribuição). Para corrigir: puxe a imagem nova e recrie o container (grupo A da seção Docker).

Cuidado com o número. Distribuições Linux e fornecedores de suporte longo (LTS) receberam a correção antes e podem aplicá-la mantendo o número antigo (backport). Por isso o cabeçalho e o verificador não enxergam backport: "versão abaixo da corrigida" quer dizer "investigue no changelog do seu fornecedor", não necessariamente "vulnerável".
3

Corrija: atualize e reinicie todos os processos

Antes: backup do site (sudo tar czf site-$(date +%F).tgz /var/www) e do banco (mysqldump --all-databases > wp-$(date +%F).sql), e anote se a home e o /wp-admin abrem. Depois: confira os dois de novo e o php -v. Veja a chamada antes e depois de atualizar.

  • Versões corrigidas: 8.5.12, 8.4.27, 8.3.36, 8.2.35 (ou o pacote corrigido da sua distribuição).
  • PHP 8.1 e anteriores não recebem mais atualização do projeto PHP. Algumas distribuições e fornecedores LTS têm a correção: pergunte a eles ou suba para uma versão suportada. Pela gravidade, o time do PHP publicou correções mínimas em código-fonte para 7.4, 8.0 e 8.1, para quem compila.
sudo apt update
apt list --upgradable 2>/dev/null | grep -i php   # veja o que vai mudar
sudo apt install --only-upgrade $(dpkg -l 'php*' | awk '/^ii/{print $2}')   # só os pacotes PHP instalados
sudo systemctl restart php8.3-fpm    # ou apache2, se usa mod_php

Usa o repositório PPA ondrej/php (comum para ter várias versões)? Ele também precisa estar com a versão corrigida; confira com php -v depois. mod_php é o PHP rodando dentro do Apache; PHP-FPM é o PHP como serviço separado, usado com nginx ou Apache.

4

Não dá para atualizar agora? Reduza a exposição

O próprio time do PHP avisa: estas medidas reduzem caminhos de ataque conhecidos, não tornam a instalação segura. Só a atualização resolve. Aplique no php.ini, no pool do FPM ou na configuração do servidor (não em ini_set() nem .user.ini) e reinicie todos os workers.

# no pool do FPM (ex.: /etc/php/8.3/fpm/pool.d/www.conf), que a aplicação não consegue mudar:
php_admin_value[memory_limit] = -1
php_admin_value[post_max_size] = 8M          # 32M se o site precisa de upload
php_admin_value[upload_max_filesize] = 6M    # sempre abaixo do post_max_size
php_admin_flag[session.upload_progress.enabled] = off
# só no PHP 8.5:
php_admin_flag[fatal_error_backtraces] = off
  • memory_limit = -1 (contra a CVE-2026-109618) tira o limite de memória do PHP. Isso remove a falha de memória que o atacante controla, mas não fecha o caminho por completo: uma falha real de alocação (a memória do servidor acabando) ainda pode chegar ao ponto vulnerável. E um script com defeito pode consumir toda a RAM do servidor. Por isso o time do PHP recomenda pôr um limite fora do PHP: o limite de memória do container (Docker) ou do serviço (cgroup, no systemd MemoryMax=), de preferência derrubando só o processo afetado.
  • Não use RLIMIT_AS/RLIMIT_DATA (limites de memória do ulimit) no lugar disso: eles podem provocar justamente a falha de memória vulnerável.
  • session.upload_progress.enabled e fatal_error_backtraces desligados (contra a CVE-2026-109619) bloqueiam só os caminhos confirmados, não outras variantes.
Aviso novo do PHP (23/10/2026, CVE-2026-115560): mantenha session.upload_progress.enabled = 0 mesmo depois de atualizar, até a próxima versão regular do PHP. O risco só existe numa combinação rara: o app define session.serialize_handler como php_binary ou php_serialize na configuração e troca para php com ini_set() antes do session_start(), com o upload progress ligado (o padrão). App que nunca troca o serialize_handler em execução não é afetado por esse caminho e pode deixar o recurso ligado. Quem já desligou em 21/10 não precisa fazer mais nada. O audita.sh avisa (linha info) quando o recurso está ligado no PHP de linha de comando; o que vale é a configuração do FPM ou do Apache.
5

Ficou exposto? Procure sinais de invasão

# arquivos .php criados ou mudados nos últimos 7 dias dentro do site
find /var/www -name '*.php' -mtime -7 -ls
# .php dentro da pasta de uploads do WordPress (lá não deveria haver nenhum)
find /var/www -path '*wp-content/uploads*' -name '*.php' -ls
# tarefas agendadas que você não criou
crontab -l; sudo crontab -l -u www-data; ls -la /etc/cron.*
# acessos recentes e usuários novos
last -20; tail -5 /etc/passwd
wp user list --role=administrator --path=/var/www/seusite   # WP-CLI; como root, acrescente --allow-root

Achou algo que você não criou: tire o site do ar, troque senhas e chaves, restaure de um backup anterior à invasão.

Este guia em vídeo

Vídeos do INEMA sobre o PHP2Shell

Rotina · para a próxima não pegar de surpresa

O que mais vale fazer

A inteligência artificial está achando falhas antigas cada vez mais rápido. A defesa é rotina, não susto.

🤖 Agentes de IA e curl | bash

  • Instalador do tipo curl ... | bash roda código da internet com o seu usuário. Baixe primeiro, leia, e só então rode.
  • Confira o checksum (sha256sum arquivo) ou a assinatura publicada pelo projeto.
  • Fixe versões (package-lock.json, requirements.txt com versão, imagem Docker com tag) e atualize de propósito, não por acaso.
  • Agente com acesso ao terminal: rode em pasta isolada ou container, sem segredos à vista.

📅 Checklist mensal (30 minutos)

  • Atualizações do sistema aplicadas e atualização automática ligada.
  • Node, Deno, Bun, Python (certifi), Go, Java e imagens Docker nas versões suportadas.
  • ./audita.sh em todas as máquinas: resumo zerado.
  • Backup testado: restaurou de verdade, não só "existe".
  • Linha de versão ainda suportada (Node 20 e PHP 8.1 já não são).

🔎 Vigie os certificados do seu domínio

Todo certificado emitido fica num registro público (Certificate Transparency). Procure seu domínio no crt.sh e veja se apareceu certificado que você não pediu. Um registro CAA no DNS diz quais certificadoras podem emitir para o seu domínio.

dig CAA seudominio.com.br +short

⚙️ CI/CD e segredos

  • Reconstrua as imagens no pipeline com --pull: imagem velha em cache traz lista velha.
  • Rode o audita.sh no pipeline como alarme: qualquer saída diferente de 0 barra o deploy (1 EXPOSTO, 2 SEM TESTE, 3 pede reinício). A saída 0 sozinha não libera nada: ela vale só dentro do escopo impresso no fim, e o script é um levantamento, não um certificado.
  • Nada de chave no código ou no git (.env fora do repositório); se uma chave vazou, troque, não só apague.
  • Para sites, o método blinda-site do INEMA olha cabeçalhos, dependências, segredos e banco com um agente de IA, sem tocar no site no ar.
Glossário

As palavras deste guia

Certificadora (CA)

Empresa que assina os certificados HTTPS. É o "cartório" que diz: este site é mesmo quem diz ser.

Raiz, intermediário, cadeia

A raiz é a assinatura-mãe que o seu programa confia. Ela assina intermediários, que assinam os sites. A sequência é a cadeia. As chaves vazadas são de intermediários.

P-384

Tipo de chave por curva elíptica. Uma chave P-384 boa é um número com mais de 100 dígitos; as da eMudhra eram menores que 256.

ca-certificates

Pacote do Linux com a lista de certificadoras confiáveis do sistema. Usado pelo curl, wget e muitos programas.

certifi

Pacote Python com uma lista própria de certificadoras. O requests usa ele, não a do sistema.

venv

Ambiente Python isolado de um projeto, com os próprios pacotes (e o próprio certifi).

Container (Docker)

Uma "máquina" leve dentro do servidor, com sistema e lista de certificados próprios. Imagem antiga = lista antiga.

pm2 · systemd

Gerenciadores que mantêm programas rodando no servidor e os reiniciam.

cacerts · keystore

Arquivo onde o Java guarda as certificadoras em que confia. Cada JDK pode ter o seu; o keytool mostra o que tem dentro.

Tag flutuante · fixada

O nome depois dos dois pontos na imagem Docker. Flutuante (latest, alpine) muda de conteúdo com o tempo; fixada (2.8.1) muda pouco, mas não é imutável: o fornecedor pode republicar a mesma tag. O que nunca muda é o digest (sha256:…).

Cadeia cruzada

Quando uma certificadora também é assinada por outra raiz mais antiga, para funcionar em aparelhos velhos. Aqui: a cadeia da eMudhra também passa pela raiz AAA Certificate Services.

busybox

Conjunto mínimo de comandos (sh, wget…) usado em imagens pequenas como Alpine. O wget dele mostra erros diferentes do wget comum.

CVE

Número público de uma falha de segurança, usado para procurar a correção (ex.: CVE-2026-109618).

Fontes

Onde conferir

  1. Página de teste, detalhes do caso eMudhra e versões corrigidas por sistema e ferramenta (inclui uv 0.13.5 e FreeBSD): emudhra.we-have-a-problem.com
  2. Mozilla, discussão do incidente: bugzilla.mozilla.org/show_bug.cgi?id=2083449
  3. Google, proteção do Chrome e Android: blog.google/security/…emudhra…
  4. SOCRadar, riscos e correções: socradar.io/blog/emudhra-ca-key-compromise…
  5. PHP, aviso de segurança de 21/10/2026: php.net/archive/2026.php
  6. PHP, aviso informativo de 23/10/2026 (CVE-2026-115560, upload progress): php.net/archive/2026.php
  7. OpenAI, verificador PHP2Shell: github.com/openai/php2shell-checker