FlawOpen

flawopen.com/Incidents/postgresql-psql-injection-beyondtrust-treasury-december-2024

● PostgreSQL / BeyondTrust · CVE-2025-1094 · December 2024 – February 2025 · CVSS 8.1 · Alta
Pesquisa · FlawOpen

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.

💡 Explicação em Linguagem Simples (ELI5)

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

  • Referência CWE para a fraqueza descrita neste guia: CWE-89 CWE-149

Linha do Tempo do Incidente

2024-12-16

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.

2024-12-19

A CISA inclui a CVE-2024-12356 no catálogo Known Exploited Vulnerabilities.

2025-02-13

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

Passo 1

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.

Passo 2

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.

Passo 3

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.

Passo 4

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.

IMPLEMENTAÇÃO VULNERÁVEL
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
PATCH SEGURO E ROBUSTO
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

Fontes