flawopen.com/Incidents/postgresql-psql-injection-beyondtrust-treasury-december-2024
CVE-2025-1094: SQL-инъекция в psql PostgreSQL
Некорректный UTF-8 обходил экранирование libpq при создании ввода psql из экранированного текста, позволяя внедрить SQL или \!. Исправлено 13.02.2025.
Представьте переводчика, который должен заключать в кавычки всё, что вы говорите, чтобы это никогда не приняли за команду. Вы обнаруживаете: если дать ему слово в незнакомом алфавите, он сбивается и теряет одну из кавычек. Сказанное вами вырывается из кавычек и читается как распоряжение, хотя переводчик сделал ровно то, для чего был создан, на всех входных данных, которые кто-либо проверял.
Ключевые понятия и термины
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, так что по нему никогда не принимается решение о кавычках или экранировании.
Связи со стандартами
Хронология инцидента
BeyondTrust публикует бюллетень BT24-10 о CVE-2024-12356 после расследования компрометации ряда SaaS-экземпляров Remote Support; позднее Минфин США сообщил, что этот инцидент затронул его системы.
CISA вносит CVE-2024-12356 в каталог Known Exploited Vulnerabilities.
PostgreSQL выпускает версии 17.3, 16.7, 15.11, 14.16 и 13.19 с исправлением CVE-2025-1094, а Rapid7 публикует свой анализ.
Пошаговый сценарий атаки
Ввод попадает в функцию экранирования
Приложение берёт текст, на который влияет злоумышленник, и передаёт его в PQescapeLiteral или родственную функцию libpq, чтобы собрать SQL-запрос.
Некорректный UTF-8 прячет кавычку
Согласно бюллетеню PostgreSQL, специально составленная некорректная многобайтовая последовательность перед одинарной кавычкой заставляла процедуру экранирования оставить эту кавычку неэкранированной.
psql закрывает литерал раньше времени
Когда запрос передаётся в psql, неэкранированная кавычка закрывает строковый литерал, а остаток ввода разбирается как SQL или команды psql.
Метакоманда запускает оболочку
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()]
Чек-лист по защите системы для инженеров
- ✓Найдите в коде вызовы psql через subprocess, чей ввод собирается из данных приложения, замените их драйвером базы данных со связанными параметрами и добавьте правило CI, которое проваливается на новых таких вызовах.
- ✓Отклоняйте на границе ввод, некорректный в заявленной кодировке, до того как он попадёт в парсер, функцию экранирования или базу; добавьте тест, отправляющий b'\xc0\'' и ожидающий ошибку проверки.
- ✓Обновляйте libpq и клиентские утилиты, а не только сервер:
CVE-2025-1094исправлена в PostgreSQL 17.3, 16.7, 15.11, 14.16 и 13.19, и клиентские пакеты на серверах приложений тоже нужно обновить. - ✓Там, где экранирование пока нельзя убрать, добавьте регрессионный тест, который подаёт некорректный UTF-8 с кавычкой в путь экранирования и проверяет, что результат отклонён или безопасно заключён в кавычки.
- ✓Считайте инструменты привилегированного удалённого доступа частью своей поверхности атаки: ограничьте их интерфейсы управления, ставьте исправления по первому уведомлению поставщика и следите за использованием их API-ключей из неожиданных мест.
Источники
- PostgreSQL — бюллетень безопасности по CVE-2025-1094
- Rapid7 — CVE-2025-1094: SQL-инъекция в psql PostgreSQL, найденная при анализе CVE-2024-12356 от BeyondTrust
- SQL-инъекция — полный справочник по классу на 11 языках
- Внедрение команд — полный справочник по классу
- Защищают ли хранимые процедуры от SQL-инъекций?