O scram_iterations do PostgreSQL esconde uma armadilha de segurança em migrações

- Leitura
- 8 min
- Código
- 2
- Fontes
- 1
O parâmetro chegou ao PostgreSQL 16 para tornar configurável a contagem de iterações do SCRAM-SHA-256, mas a correção da falha que ele abriu só deslocou o problema, sem fechá-lo.
O consultor Christophe Pettus, da PGX Inc., publicou no Planet PostgreSQL↳PostgreSQL11 conteúdosPostgreSQL via SSL com GolangData · abr 20195 itens legais sobre data types do PostgreSQLData · mar 20195 serviços gratuitos na cloud para bancos de dados PostgresData · fev 2025Ver tudo em Data → mais um capítulo da série "All Your GUCs in a Row", desta vez sobre scram_iterations. O nome sugere um ajuste de servidor, mas não é isso: o parâmetro funciona como valor-padrão aplicado no instante em que uma senha é definida. Depois disso, o número fica gravado dentro da própria senha, não na configuração do banco, e é esse detalhe que explica todo o resto do comportamento do GUC.
O que o parâmetro realmente controla
O contexto é user (qualquer role pode alterá-lo com SET), o padrão é 4096 e o intervalo vai de 1 a 2147483647. Ele chegou ao PostgreSQL 16 (2023); antes disso o número era uma constante fixa no código-fonte, também 4096, seguindo a recomendação mínima da RFC 7677 (2015). O servidor só lê esse valor em dois momentos: quando alguém define uma senha nova e quando alguém tenta logar como uma role sem senha SCRAM para checar.
Como o número vira parte da senha
O valor é a contagem de iterações PBKDF2 do SCRAM-SHA-256: quantas vezes o cliente roda HMAC-SHA-256 sobre senha e sal para derivar a chave usada na troca de autenticação. O resultado do hash em si não é armazenado. O que fica em pg_authid.rolpassword é a string SCRAM-SHA-256$4096:<salt>$<StoredKey>:<ServerKey>, com a contagem embutida ali mesmo.
Isso significa que mudar scram_iterations e recarregar a configuração não altera nenhuma senha existente, porque cada uma já carrega sua própria contagem. O pg_dumpall -r reproduz a string literalmente, então o número sobrevive a um restore, a um pg_upgrade e até a uma cópia para o auth_file do PgBouncer. A única forma de migrar uma senha para uma contagem nova é redefini-la, o que exige conhecê-la, coisa que o servidor não guarda.
Quem paga a conta das iterações
Aqui está o ponto que interessa a quem opera a aplicação, não só ao DBA: na autenticação scram-sha-256, o servidor quase não trabalha. Pettus mediu dez conexões cada em roles com senhas de 1, 4096, 100 mil e 1 milhão de iterações. O backend↳Back-end49 conteúdosIntegração front-end com backend: 7 decisões que evitam caos entre APIs, BFF e GraphQLDev (Back & Front) · abr 2026Como criar uma FAKE API REST para testes — JSONPlaceholderDev (Back & Front) · set 2025Construindo um aplicativo de bate-papo de IA simples com Spring AI e AngularDev (Back & Front) · jul 2025Ver tudo em Dev (Back & Front) → usou cerca de 50 ms de CPU somados nas dez conexões, em todos os cenários. O cliente, por sua vez, gastou 0,05, 0,14, 1,9 e 18,3 segundos, respectivamente.
Em pgbench -C, que reconecta a cada transação, o padrão (4096) sustentou 75 conexões por segundo contra 264 com 1 iteração. No PostgreSQL 18, com log_connections = 'setup_durations', a linha de conexão pronta para a role de um milhão de iterações registrou authentication=1802.897 ms: é o próprio servidor contabilizando o tempo que passou esperando o cliente terminar de calcular.
O método password em pg_hba.conf é a exceção, porque ali o cliente manda a senha em texto puro e o servidor faz a derivação a cada login: 1,7 segundo de CPU de backend por conexão a um milhão de iterações, pago pelo servidor a cada tentativa, inclusive as erradas. Como resume Pettus, esse método já era uma má ideia antes mesmo de scram_iterations existir.
O que a iteração protege, e o que não protege
O benefício real é resistência a ataques de força bruta offline caso pg_authid vaze, por exemplo num backup esquecido no bucket errado ou numa réplica que saiu da sala do datacenter. Quem tem a string não loga com ela direto, porque o StoredKey é um hash da chave que o cliente precisa provar que conhece; resta tentar senhas, e cada tentativa custa a contagem completa de iterações.
Isso não ajuda em nada contra senhas de 32 caracteres geradas por máquina: não são adivinháveis nem com uma iteração, então elevar o contador para roles de serviço cobra de toda conexão sem comprar segurança alguma. E a própria RFC 5802 lembra que a string guardada mais uma troca espionada, ou um servidor falso, permitem que um atacante se passe pelo usuário sem nunca aprender a senha. Contra isso, quem resolve é sslmode=verify-full ou channel_binding=require, não o número de iterações.
O CVE que a correção não fechou de vez
Este é o ponto central para quem está pensando em mexer no valor por motivo de segurança. Para não revelar quais usernames existem, o servidor precisa responder com um sal e uma contagem plausíveis mesmo para uma role que não existe, numa "troca simulada". Desde o PostgreSQL 10 isso funciona com um sal derivado do username e um nonce do cluster, mais uma contagem de iterações.
Até agosto de 2026, essa contagem simulada era sempre a constante 4096, o que não era problema até o PostgreSQL 16 tornar o valor real configurável. A partir daí, configurar scram_iterations = 100000 e criar roles fazia cada role real responder i=100000, enquanto toda role fantasma respondia i=4096: um oráculo de existência de usuário, catalogado como CVE-2026-14672, relatado por Radim Marek e corrigido por Nathan Bossart nas versões 18.6, 17.11 e 16.15.
O problema é que a correção não fecha o oráculo, apenas o desloca. Pettus rodou uma sonda antes e depois do patch:
--- 18.4, postgresql.conf scram_iterations = 100000
u100k: i=100000
u4096: i=4096
no_such_role: i=4096
--- 18.6, mesma configuração
u100k: i=100000
u4096: i=4096
no_such_role: i=100000No servidor corrigido, os fantasmas agora combinam com as roles criadas depois de elevar o valor, e são as roles criadas antes dele que ficam diferentes, denunciando senhas na contagem antiga. Como escreve Pettus:
Leia de novo o segundo bloco. A correção não fechou o oráculo: ela o moveu.
Christophe Pettus, consultor da PGX Inc.Read the second block again. The fix did not close the oracle; it moved it.
A única cura de fato é colocar toda senha do cluster na mesma contagem do postgresql.conf, e um inventário prévio ajuda a medir o tamanho do problema:
select coalesce(substring(rolpassword from '^SCRAM-SHA-256\$(\d+):'),
left(rolpassword, 3), '(none)') as password_type,
count(*)
from pg_authid
group by 1 order by 2 desc, 1;E como o contexto do parâmetro é user, não há como impor essa correção de cima: qualquer role autorizada a trocar a própria senha pode rodar SET scram_iterations = 1 antes do \password, e o servidor não tem piso mínimo para impedir isso.
O risco inverso: servidor hostil, cliente preso
Como é o servidor quem anuncia a contagem e o cliente quem faz o trabalho pesado, um servidor comprometido ou malicioso pode anunciar um número gigantesco e prender o cliente em PBKDF2 pelo tempo que quiser. A RFC 5802 já alertava para isso, e, até a libpq 18.6 e o release candidate da 19, connect_timeout não interrompe esse laço: Pettus testou um psql com connect_timeout=3 contra um servidor simulando vinte milhões de iterações, e o cliente queimou 39 segundos de CPU antes de reportar que o timeout havia expirado.
No valor máximo do GUC isso passaria de uma hora na mesma máquina, e um servidor real já teria desistido da conexão por authentication_timeout (60 segundos) sem que o cliente percebesse, porque ninguém mais estava ouvindo do outro lado. Sehrope Sarkuni propôs, em maio de 2026, um conjunto de patches na lista hackers para fazer o laço respeitar connect_timeout e adicionar um teto de 100.000 no próprio cliente; Daniel Gustafsson apoiou o ajuste do timeout, mas foi contra criar mais um parâmetro de conexão.
Outros drivers já adotaram esse teto por conta própria. O pgjdbc 42.7.11 (abril de 2026, CVE-2026-42198) e o node-postgres 8.21 recusam qualquer contagem acima de 100.000 a menos que scramMaxIterations seja elevado manualmente; o PgBouncer 1.26.0 (setembro de 2026, CVE-2026-6669) recusa de cara qualquer valor acima de 1.000.000 vindo do servidor. Na prática: elevar scram_iterations acima de 100 mil e resetar senhas faz aplicações Java e Node pararem de conectar com aquela role, com um erro do próprio driver, sem relação aparente com a configuração do servidor.
Antes de mudar esse valor
A recomendação de Pettus, e o ponto de partida sensato para qualquer equipe que opere PostgreSQL em produção, pode ser resumido em quatro decisões:
- Para roles de aplicação com senha gerada aleatoriamente, manter o padrão 4096: as iterações não protegem nada ali, e toda conexão paga o preço à toa.
- Para roles com senha escolhida por humano, o ideal é eliminar a senha: certificados, GSSAPI ou OAuth (disponível desde o PostgreSQL 18).
- Se a decisão for mesmo elevar o valor, escolher um número defensável e não acima de 100.000 (o teto que os principais drivers já impõem), configurar em
postgresql.conf, nunca em sessão, e garantir que toda ferramenta que define senha (\password,createuser --pwprompt, provisionamento automatizado) rode sobre libpq 16 ou mais recente, para não gravar silenciosamente um 4096. - Depois disso, resetar todas as senhas do cluster antes de considerar a tarefa concluída, usando o inventário em
pg_authidpara confirmar.
Como fecha Pettus:
Um
Christophe Pettus, consultor da PGX Inc.A raised scram_iterations with a mix of old and new passwords behind it is not a stronger configuration. It is a weaker one with a tell.scram_iterationselevado com uma mistura de senhas antigas e novas por trás não é uma configuração mais forte. É uma mais fraca, e com uma marca registrada.
Fonte: Planet PostgreSQL
Highlights
- PostgreSQL 16 abriu oráculo de existência de usuário ao tornar scram_iterations configurável (CVE-2026-14672)
- Patch nas versões 18.6, 17.11 e 16.15 não fechou a brecha, apenas a deslocou entre senhas antigas e novas
- Cliente paga CPU, servidor não: um milhão de iterações consome 18,3 segundos no cliente contra 50 ms no servidor
- Drivers como pgjdbc 42.7.11 e node-postgres 8.21 recusam valores acima de 100.000 sem parâmetro manual
- Servidor hostil pode anunciar 20 milhões de iterações e prender cliente em PBKDF2 por horas (connect_timeout não interrompe)
Este artigo foi escrito por Roberto Diniz, colunista de banco de dados. Conteúdo produzido por agente de IA da redação iMasters, sob revisão editorial humana. Saiba como produzimos no expediente.
PGConf.EU 2026: Postgres ganha radar e radar-analyst, dupla open source para diagnóstico sem agente residente
Na PGConf.EU 2026, em Valencia, o especialista Jimmy Angelakos apresentou radar e radar-analyst, que tiram uma fotografia completa do banco sem exigir agente rodando, e detalhou o pg_statviz, que guarda séries temporais dentro do próprio Postgres.










