FlawOpen

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

● PostgreSQL / BeyondTrust · CVE-2025-1094 · December 2024 – February 2025 · CVSS 8.1 · Высокий
Исследования · FlawOpen

CVE-2025-1094: SQL-инъекция в psql PostgreSQL

Некорректный UTF-8 обходил экранирование libpq при создании ввода psql из экранированного текста, позволяя внедрить SQL или \!. Исправлено 13.02.2025.

💡 Простыми словами (ELI5)

Представьте переводчика, который должен заключать в кавычки всё, что вы говорите, чтобы это никогда не приняли за команду. Вы обнаруживаете: если дать ему слово в незнакомом алфавите, он сбивается и теряет одну из кавычек. Сказанное вами вырывается из кавычек и читается как распоряжение, хотя переводчик сделал ровно то, для чего был создан, на всех входных данных, которые кто-либо проверял.

Ключевые понятия и термины

PQescapeLiteral
Функция libpq, которая заключает значение в кавычки и экранирует его для вставки в текст SQL. Затронуты были также PQescapeIdentifier, PQescapeString и PQescapeStringConn.
Некорректный UTF-8
Последовательность байтов, неправильно сформированная в заявленной кодировке. Код экранирования и psql по-разному считали, сколько следующих символов поглощают такие байты.
Метакоманда psql
Команда с обратной косой чертой, которую обрабатывает клиент psql, а не сервер. \! выполняет остаток строки как команду оболочки операционной системы.
CVE-2024-12356
Критическая уязвимость внедрения команд в BeyondTrust Privileged Remote Access и Remote Support. В тестах Rapid7 выполнение кода через неё требовало CVE-2025-1094.
Связанный параметр
Значение, передаваемое серверу отдельно от текста SQL, так что по нему никогда не принимается решение о кавычках или экранировании.

Связи со стандартами

  • Ссылка CWE для слабого места, описанного в этом руководстве: CWE-89 CWE-149

Хронология инцидента

2024-12-16

BeyondTrust публикует бюллетень BT24-10 о CVE-2024-12356 после расследования компрометации ряда SaaS-экземпляров Remote Support; позднее Минфин США сообщил, что этот инцидент затронул его системы.

2024-12-19

CISA вносит CVE-2024-12356 в каталог Known Exploited Vulnerabilities.

2025-02-13

PostgreSQL выпускает версии 17.3, 16.7, 15.11, 14.16 и 13.19 с исправлением CVE-2025-1094, а Rapid7 публикует свой анализ.

Пошаговый сценарий атаки

Шаг 1

Ввод попадает в функцию экранирования

Приложение берёт текст, на который влияет злоумышленник, и передаёт его в PQescapeLiteral или родственную функцию libpq, чтобы собрать SQL-запрос.

Шаг 2

Некорректный UTF-8 прячет кавычку

Согласно бюллетеню PostgreSQL, специально составленная некорректная многобайтовая последовательность перед одинарной кавычкой заставляла процедуру экранирования оставить эту кавычку неэкранированной.

Шаг 3

psql закрывает литерал раньше времени

Когда запрос передаётся в psql, неэкранированная кавычка закрывает строковый литерал, а остаток ввода разбирается как SQL или команды psql.

Шаг 4

Метакоманда запускает оболочку

Rapid7 показала, что внедрённый текст может содержать метакоманду \!, которую psql выполняет как команду операционной системы на машине, где он запущен.

Исходный код: Уязвимый vs Защищённый вариант

Иллюстративный Python на psycopg 3, а не код BeyondTrust. Уязвимая версия воспроизводит рискованный шаблон из бюллетеня: экранировать значение через libpq, вставить его в текст SQL и передать этот текст psql. Защищённая версия устраняет обе половины: она отклоняет некорректный UTF-8 до любой обработки и передаёт значение связанным параметром через полноценный драйвер, так что нет ни шага экранирования, который можно обмануть, ни разборщика метакоманд psql на пути. Обновление libpq по-прежнему важно для кода, который продолжает экранировать.

УЯЗВИМАЯ РЕАЛИЗАЦИЯ
import subprocess

import psycopg


def lookup_user(conn: psycopg.Connection, name: bytes) -> bytes:
    # PQescapeLiteral в libpq (до версий 17.3/16.7/15.11/14.16/13.19) не могла
    # нейтрализовать кавычку, скрытую за недопустимой последовательностью байтов UTF-8.
    literal = conn.pgconn.escape_literal(name)
    script = b"SELECT id FROM users WHERE name = " + literal + b";\n"
    # psql выполняет входные данные как скрипт, включая метакоманды, например \!
    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:
        # Значение передаётся как связанный параметр отдельно от текста SQL,
        # и процесс psql (а значит, и анализатор метакоманд) не используется.
        cur.execute("SELECT id FROM users WHERE name = %s", (text,))
        return [row[0] for row in cur.fetchall()]

Чек-лист по защите системы для инженеров

Источники