Imagina usar PostgreSQL por mais de duas décadas e descobrir que, o tempo todo, existia um bug escondido no core do banco que permitia a qualquer usuário com acesso básico executar código arbitrário no servidor. Pois é, não precisa imaginar. Aconteceu hoje.
O pesquisador de segurança Mehmet Ince, conhecido como @mdisec, publicou a terceira parte de uma série devastadora chamada “Systemic Risks in the Managed PostgreSQL Industry”. Nesse capítulo, ele detalha como transformou uma primitiva de type confusion com 22 anos de idade em execução remota de código (RCE) completa, registrada como CVE-2026-6460. O CVSS? 8.8 de 10. A correção? Saiu hoje, 12 de novembro de 2026.
Se você usa PostgreSQL, seja local, na AWS Aurora, no Google AlloyDB, no Supabase, no Neon ou em qualquer outro managed provider: pare o que está fazendo e atualize. Sério.
O que é Type Confusion e por que isso é grave
Antes de entrar no exploit em si, vale entender o conceito. Type confusion acontece quando um sistema trata um valor como se fosse de um tipo, mas na verdade ele é de outro. É como se o banco de dados olhasse para um número inteiro e decidisse que aquilo é um ponteiro de memória. Spoiler: isso não termina bem.
No PostgreSQL, o container genérico de valores se chama Datum. Ele tem 8 bytes e, sozinho, não sabe de que tipo é. A informação de tipo vem de um OID (Object Identifier) que aponta para o catálogo pg_type. Então, um Datum criado como UUID pode ser reinterpretado por funções que esperam bytea, desde que alguém consiga trocar a definição de tipo entre a preparação e a execução de uma query.
E é exatamente isso que o Mehmet fez.
A mecânica do ataque
O ataque explora uma falha fundamental: o PostgreSQL não valida se as premissas de uma prepared statement ainda são verdadeiras no momento da execução. A sequência é esta:
-- 1. Cria um tipo composto com campo UUID
CREATE TYPE evil_type AS (campo UUID);
-- 2. Prepara uma query que faz cast para esse tipo
PREPARE stmt AS SELECT ROW('550e8400-e29b-41d4-a716-446655440000')::evil_type;
-- 3. Altera o tipo, trocando UUID por bytea
ALTER TYPE evil_type ALTER ATTRIBUTE campo SET DATA TYPE bytea;
-- 4. Executa a prepared statement
EXECUTE stmt;
O que acontece na etapa 4 é o caos. A prepared statement ainda acha que o campo é UUID (16 bytes fixos). Mas as funções de execução agora esperam bytea (tamanho variável com header). Os primeiros 16 bytes do UUID são reinterpretados como estrutura de bytea, corrompendo a memória de forma controlada.
A partir daí, o atacante consegue manipular ponteiros e alcançar execução de código arbitrário com os privilégios do processo PostgreSQL.
22 anos escondido
O detalhe que assusta é a idade dessa falha. A primitiva de type confusion existe desde que tipos compostos foram introduzidos no PostgreSQL. Estamos falando de código que entrou no core do banco há mais de duas décadas e nunca foi questionado.
Por quê? Porque ninguém pensou que tipos compostos pudessem ser alterados entre a preparação e a execução de uma query. É um daqueles bugs que parece óbvio depois que alguém aponta, mas que exige um conhecimento profundo do funcionamento interno do banco para sequer ser concebido.
O Mehmet conta no blog dele que a ideia surgiu durante uma conversa casual sobre SQL injection e prepared statements. Ele já estava investigando a segurança do ecossistema managed PostgreSQL há meses, quebrando guardrails de extensões de segurança em providers como Supabase, Aurora e AlloyDB. A type confusion foi a peça que faltava.
O impacto real: não é só o seu servidor
Aqui é onde a coisa fica realmente séria. O CVE-2026-6460 não afeta só quem roda PostgreSQL on-premise. Ele afeta todo o ecossistema de bancos de dados gerenciados que usam PostgreSQL como base.
| Serviço | Base PostgreSQL | Potencialmente afetado? | |
|---|---|---|---|
| ——— | —————- | ———————— | |
| AWS Aurora PostgreSQL | Sim | Sim | |
| Google AlloyDB | Sim | Sim | |
| Supabase | Sim | Sim | |
| Neon | Sim | Sim | |
| Azure Database for PostgreSQL | Sim | Sim | |
| DigitalOcean Managed Databases | Sim | Sim | |
| Render | Sim | Sim | |
| Railway | Sim | Sim |
O vetor de ataque exige apenas CREATE TYPE e PREPARE, que são permissões comuns para usuários de banco de dados. Em muitos managed providers, qualquer usuário da aplicação tem essas permissões por padrão.
Isso significa que, se sua aplicação conecta no banco com um usuário que pode criar tipos (a maioria pode), ela é potencialmente vulnerável. O atacante não precisa de credenciais de superusuário. Basta acesso SQL básico.
O vetor CVSS desmontado
O score CVSS 8.8 (High) vem deste vetor:
AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
Traduzindo para humanos:
- AV:N (Network): explorável remotamente, pela rede
- AC:L (Low Complexity): não precisa de condições especiais
- PR:L (Low Privileges): qualquer usuário com acesso SQL básico
- UI:N (No User Interaction): não precisa que alguém clique em nada
- C:H / I:H / A:H: compromete completamente confidencialidade, integridade e disponibilidade
Em outras palavras: qualquer pessoa com uma connection string pode, potencialmente, executar código no servidor que roda o banco. Sem fireworks, sem phishing, sem nada. Só SQL.
A pesquisa por trás da descoberta
O Mehmet Ince não tropeçou nessa vulnerabilidade por acaso. Ele está conduzindo uma pesquisa sistemática sobre riscos no ecossistema de PostgreSQL gerenciado, com uma série de 6 partes publicadas no blog dele.
Na parte 2, ele mostrou como quebrar os guardrails de extensões de segurança. Esses guardrails são usados por providers como Supabase e Neon para impedir que usuários executem código nativo dentro do banco. O Mehmet conseguiu bypassar essas proteções usando manipulação de pg_proc.prosrc e funções LANGUAGE internal.
Na parte 3, veio o grande salto: a type confusion. Ele percebeu que, se valores tipados não são auto-identificáveis e o sistema confia que as definições de tipo permanecem consistentes entre prepare e execute, existe uma janela de ataque. E ele provou que essa janela é real.
O mais impressionante: ao longo da pesquisa completa, ele reportou 76 vulnerabilidades aos vendors afetados. O cara é uma máquina de achar bugs em PostgreSQL.
Como se proteger (agora)
As versões corrigidas foram lançadas hoje (12 de novembro de 2026):
| Versão | Atualizar para | |
|---|---|---|
| ——– | ————— | |
| PostgreSQL 14.x | 14.25 | |
| PostgreSQL 15.x | 15.20 | |
| PostgreSQL 16.x | 16.16 | |
| PostgreSQL 17.x | 17.12 | |
| PostgreSQL 18.x | 18.7 |
Se você usa um provider gerenciado, verifique se a atualização já foi aplicada. Providers grandes como AWS e Google costumam aplicar patches de segurança rapidamente, mas “costumam” não é “sempre”.
Checklist de emergência
- Identifique todas as instâncias PostgreSQL na sua infraestrutura
- Verifique a versão de cada uma com
SELECT version(); - Atualize imediatamente para a versão corrigida
- Audite permissões: quais usuários podem executar
CREATE TYPE? - Revise logs: busque por
ALTER TYPE ... SET DATA TYPEem queries recentes - Managed providers: abra um ticket perguntando se o patch já foi aplicado
# Verificar a versão do PostgreSQL
psql -c "SELECT version();"
# Listar tipos compostos criados por usuários (possíveis vetores)
psql -c "SELECT n.nspname, t.typname, t.typowner::regrole
FROM pg_type t
JOIN pg_namespace n ON t.typnamespace = n.oid
WHERE t.typtype = 'c'
AND n.nspname NOT IN ('pg_catalog', 'information_schema');"
# Buscar prepared statements ativas
psql -c "SELECT * FROM pg_prepared_statements;"
Por que ninguém achou isso antes?
Essa é a pergunta que todo mundo faz quando um bug de 22 anos aparece. E a resposta honesta é: porque poucas pessoas olham para o core do PostgreSQL com olhos de atacante.
A comunidade PostgreSQL é excelente em manter a qualidade do código. Mas segurança ofensiva exige uma mentalidade diferente. Você não está perguntando “isso funciona?”, está perguntando “como eu faço isso quebrar?”. A maioria dos desenvolvedores e DBAs nunca vai pensar em alterar um tipo composto entre o PREPARE e o EXECUTE. É um cenário tão absurdo que ninguém testou.
Até que alguém testou.
Esse é o valor da pesquisa de segurança ofensiva. O Mehmet não estava desenvolvendo features ou corrigindo bugs. Ele estava ativamente tentando destruir coisas, e o PostgreSQL, um dos bancos de dados mais confiáveis do mundo, acabou cedendo.
O que isso muda para o ecossistema
Essa vulnerabilidade vai forçar uma discussão importante sobre como o PostgreSQL lida com mutabilidade de tipos em runtime. O fix aplicado hoje resolve o CVE específico, mas a questão arquitetural persiste: o sistema de tipos do PostgreSQL foi desenhado numa época em que ninguém pensava em prepared statements como vetor de ataque.
Algumas mudanças que provavelmente veremos:
- Invalidação automática de prepared statements quando tipos referenciados são alterados
- Tracking de tipo mais robusto no Datum, possivelmente com tags de tipo embutidas
- Restrições em ALTER TYPE durante transações com prepared statements ativas
- Auditorias de segurança mais frequentes no core, incentivadas por programas de bug bounty
O Mehmet também mencionou que essa é uma classe de vulnerabilidades, não um bug isolado. Existem outras rotas de DDL que podem produzir type confusion semelhante, como ALTER TABLE ... ADD COLUMN; ROLLBACK; ALTER TABLE ... ADD COLUMN. O patch de hoje cobre os vetores conhecidos, mas a superfície de ataque pode ser maior do que imaginamos.
Para os devs: lições práticas
Se você desenvolve aplicações que usam PostgreSQL, aqui vão algumas lições:
1. Princípio do menor privilégio funciona. Se o usuário do banco da sua aplicação não precisa de CREATE TYPE, não dê essa permissão. A maioria dos ORMs e frameworks não precisa criar tipos compostos em runtime.
2. Prepared statements não são mágica de segurança. Elas protegem contra SQL injection, sim. Mas essa vulnerabilidade mostra que o mecanismo interno de prepared statements tem suas próprias premissas que podem ser violadas.
3. Atualizações de segurança não são opcionais. Se você está rodando PostgreSQL 14.24 ou anterior, você tem um RCE esperando para acontecer. Atualize. Agora.
4. Monitore DDL em produção. Qualquer ALTER TYPE ou CREATE TYPE em um banco de produção deveria acender alarmes. Essas operações são raras em código de aplicação normal.
-- Trigger para alertar sobre alterações de tipo em produção
CREATE OR REPLACE FUNCTION alert_type_change()
RETURNS event_trigger AS $$
BEGIN
RAISE WARNING 'DDL event detected: % on %',
tg_event, tg_tag;
-- Integre com seu sistema de alertas
END;
$$ LANGUAGE plpgsql;
CREATE EVENT TRIGGER type_change_alert
ON ddl_command_end
WHEN TAG IN ('ALTER TYPE', 'CREATE TYPE')
EXECUTE FUNCTION alert_type_change();
Comparando com outros CVEs históricos do PostgreSQL
Para ter uma noção de escala, vale comparar o CVE-2026-6460 com outras vulnerabilidades famosas do PostgreSQL:
| CVE | Ano | CVSS | Descrição | Requer superusuário? | |
|---|---|---|---|---|---|
| —– | —— | —— | ———– | ——————— | |
| CVE-2023-5868 | 2023 | 4.3 | Memory disclosure via aggregate functions | Não | |
| CVE-2024-10978 | 2024 | 4.2 | SET ROLE permite acesso indevido | Não | |
| CVE-2025-1094 | 2025 | 8.1 | SQL injection via libpq escape | Não | |
| CVE-2026-6460 | 2026 | 8.8 | Type confusion, RCE via ALTER TYPE | Não |
O CVE-2026-6460 é o mais grave dos últimos anos. E a diferença crucial é que ele dá RCE, não apenas leak de memória ou escalação de privilégios dentro do banco. Estamos falando de execução de código no sistema operacional. O atacante sai do banco de dados e entra no servidor.
Quando comparamos com o CVE-2025-1094 (a SQL injection via libpq que permitiu o famoso ataque ao BeyondTrust em dezembro de 2024), o 6460 é potencialmente mais destrutivo. O 1094 dependia de uma cadeia de exploits e condições específicas. O 6460 precisa só de SQL puro e acesso ao banco.
O elefante na sala (trocadilho intencional)
O logo do PostgreSQL é um elefante. E como todo elefante, ele carrega o peso da idade. Com mais de 30 anos de desenvolvimento, o PostgreSQL tem uma base de código madura e estável, mas também herda decisões de design de uma era pré-cloud, pré-managed, pré-tudo.
A pesquisa do Mehmet Ince é um alerta: não importa quão robusto seja o software, bugs fundamentais podem se esconder por décadas quando ninguém questiona as premissas originais.
Eu uso PostgreSQL em praticamente tudo que faço. É o melhor banco relacional open source, disparado. Mas hoje é um bom dia para lembrar que “melhor” não significa “invulnerável”.
Atualize seus servidores. Revise suas permissões. E se você gerencia bancos PostgreSQL em produção, talvez seja hora de adicionar monitoramento de DDL na sua stack de observabilidade. O próximo bug de 22 anos pode estar esperando alguém curioso o suficiente para encontrá-lo.
—
Fonte de inspiração: Part 3/6: How I Ended Up Turning a 22-Year-Old PostgreSQL Type-Confusion Primitive into RCE (CVE-2026-6460) por Mehmet Ince (@mdisec)













