flawopen.com/Incidents/postgresql-psql-injection-beyondtrust-treasury-december-2024
CVE-2025-1094: injeção SQL no psql do PostgreSQL
UTF-8 inválido burlava libpq ao gerar entrada psql com texto escapado: SQL ou \! podia ser injetado. Corrigido em 13 fev. 2025.
Imagine um tradutor cuja função é pôr entre aspas tudo o que você diz, para que nada seja confundido com uma ordem. Você descobre que, se entregar a ele uma palavra num alfabeto que ele não reconhece, ele se confunde e deixa cair uma das aspas. O que você disse escapa das aspas e passa a ser lido como instrução, embora o tradutor tenha feito exatamente o que foi projetado para fazer com todas as entradas que alguém testou.
Conceitos Centrais e Termos
PQescapeLiteral- Uma função da libpq que põe um valor entre aspas e o escapa para ser colado em texto SQL. PQescapeIdentifier, PQescapeString e PQescapeStringConn também foram afetadas.
UTF-8 inválido- Uma sequência de bytes malformada na codificação declarada. O código de escape e o psql discordavam sobre quantos caracteres seguintes esses bytes consomem.
Meta-comando do psql- Um comando com barra invertida tratado pelo cliente psql, não pelo servidor. \! executa o restante da linha como comando de shell do sistema operacional.
CVE-2024-12356- Uma falha crítica de injeção de comandos no Privileged Remote Access e no Remote Support da BeyondTrust. Nos testes da Rapid7, chegar à execução de código por ela exigia a
CVE-2025-1094. Parâmetro vinculado- Um valor enviado ao servidor separado do texto SQL, de modo que nenhuma decisão de aspas ou de escape é tomada sobre ele.
Mapeamentos de frameworks
Linha do Tempo do Incidente
A BeyondTrust publica o aviso BT24-10 sobre a CVE-2024-12356 após investigar o comprometimento de algumas instâncias SaaS do Remote Support; o Tesouro dos EUA informou depois que esse incidente chegou aos seus sistemas.
A CISA inclui a CVE-2024-12356 no catálogo Known Exploited Vulnerabilities.
O PostgreSQL lança as versões 17.3, 16.7, 15.11, 14.16 e 13.19 corrigindo a CVE-2025-1094, e a Rapid7 publica sua análise.
Fluxo de Ataque Passo a Paso
A entrada chega a uma função de escape
Uma aplicação recebe texto influenciado pelo atacante e o passa para PQescapeLiteral ou uma função parecida da libpq para montar uma instrução SQL.
UTF-8 inválido esconde uma aspa
Segundo o aviso do PostgreSQL, uma sequência multibyte inválida colocada antes de uma aspa simples fazia a rotina de escape deixar essa aspa sem escape.
O psql encerra o literal antes da hora
Quando a instrução é enviada ao psql, a aspa sem escape fecha o literal de string e o restante da entrada é interpretado como SQL ou como comandos do psql.
Um meta-comando executa um shell
A Rapid7 mostrou que o texto injetado pode incluir o meta-comando \!, que o psql executa como comando do sistema operacional na máquina onde ele roda.
Código-Fonte: Vulnerável vs. Seguro
Python ilustrativo com psycopg 3, não é o código da BeyondTrust. A versão vulnerável reproduz o padrão arriscado citado no aviso: escapar um valor com a libpq, inseri-lo no texto SQL e enviar esse texto ao psql. A versão reforçada elimina as duas metades: rejeita UTF-8 malformado antes de qualquer processamento e envia o valor como parâmetro vinculado por um driver de verdade, sem etapa de escape a ser enganada e sem o interpretador de meta-comandos do psql no caminho. Atualizar a libpq continua importante para qualquer código que ainda faça escape.
import subprocess
import psycopg
def lookup_user(conn: psycopg.Connection, name: bytes) -> bytes:
# PQescapeLiteral do libpq (antes de 17.3/16.7/15.11/14.16/13.19) não
# neutralizar uma aspa oculta após uma sequência de bytes UTF-8 inválida.
literal = conn.pgconn.escape_literal(name)
script = b"SELECT id FROM users WHERE name = " + literal + b";\n"
# psql executa a entrada como um script, incluindo metacomandos como \!
result = subprocess.run(["psql", "-At", "-d", "app"], input=script,
capture_output=True, check=True)
return result.stdout
import psycopg
MAX_NAME_CHARS = 128
def lookup_user(conn: psycopg.Connection, name: bytes) -> list[int]:
try:
text = name.decode("utf-8") # reject malformed input at the boundary
except UnicodeDecodeError:
raise ValueError("name is not valid UTF-8") from None
if len(text) > MAX_NAME_CHARS:
raise ValueError("name too long")
with conn.cursor() as cur:
# O valor é enviado como parâmetro vinculado, separado do texto SQL,
# e nenhum processo psql (nem, portanto, analisador de metacomandos) é usado.
cur.execute("SELECT id FROM users WHERE name = %s", (text,))
return [row[0] for row in cur.fetchall()]
Lista de Verificação de Segurança para Engenharia
- ✓Procure no código chamadas de subprocesso ao psql cuja entrada é montada com dados da aplicação e troque-as por um driver de banco com parâmetros vinculados; adicione uma regra de CI que falhe com novas ocorrências.
- ✓Rejeite na fronteira qualquer entrada malformada na codificação declarada, antes que chegue a um parser, a uma função de escape ou ao banco; adicione um teste que envie b'\xc0\'' e espere erro de validação.
- ✓Atualize a libpq e as ferramentas cliente, não só o servidor: a
CVE-2025-1094foi corrigida no PostgreSQL 17.3, 16.7, 15.11, 14.16 e 13.19, e os pacotes cliente nos servidores de aplicação também precisam de atualização. - ✓Onde o escape ainda não puder ser removido, adicione um teste de regressão que envie UTF-8 inválido seguido de aspa ao caminho de escape e verifique que o resultado é rejeitado ou fica entre aspas com segurança.
- ✓Trate ferramentas de acesso remoto privilegiado como parte da sua superfície de ataque: restrinja as interfaces de gerenciamento, aplique os patches assim que o fornecedor avisar e monitore o uso das chaves de API a partir de locais inesperados.
Fontes
- PostgreSQL — aviso de segurança da CVE-2025-1094
- Rapid7 — CVE-2025-1094: injeção SQL no psql do PostgreSQL, encontrada ao analisar a CVE-2024-12356 da BeyondTrust
- Injeção de SQL — referência completa da classe em 11 linguagens
- Injeção de comandos — referência completa da classe
- Stored procedures evitam injeção de SQL?