
PoC уязвимости переполнения буфера кучи в PostgreSQL pgcrypto, демонстрирующий CVE-2026-2005: RCE с низкими привилегиями и повышение привилегий до суперпользователя посредством специально сформированного OpenPGP-пакета.
Целевой коммит: 4b324845ba5d24682b9b3708a769f00d160afbd7 (PostgreSQL 18.1 — уязвимая версия)
| Поле | Детали |
|---|---|
| CVE | CVE-2026-2005 |
| Тип | Переполнение буфера в куче |
| Компонент | contrib/pgcrypto/pgp-pubdec.c — pgp_parse_pubenc_sesskey() |
| Воздействие | RCE от имени пользователя ОС, под которым запущен PostgreSQL |
| CVSS | 8.8 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) |
| Требуемая аутентификация | Низкая — любой аутентифицированный пользователь с привилегией CREATE |
| Затронутые версии | PostgreSQL 14.0–14.20, 15.0–15.15, 16.0–16.11, 17.0–17.7, 18.0–18.1 |
| Исправлено в | 14.21, 15.16, 16.12, 17.8, 18.2 (12 февраля 2026 г.) |
| Архитектура | aarch64 (ARM64) — смещения в куче и раскладка MBuf зависят от архитектуры/glibc |
# Сборка и запуск PostgreSQL 18.1 (уязвимая версия)
docker compose up -d --build
# Ожидание готовности
docker compose logs -f postgres
# Нажмите Ctrl+C, когда увидите "database system is ready to accept connections"
# Одноразовая настройка: копируем бинарник postgres для офлайн-разбора символов
docker cp cve-2026-2005-poc:/usr/local/pgsql/bin/postgres ./postgres
# Установка зависимостей Node
npm install
# Запуск полной цепочки эксплойта RCE (чистый SQL — без взаимодействия с Docker)
node exploit.js --cmd "id"
# С подробным выводом
node exploit.js --cmd "id" --verbose
# Выполнение произвольной команды
node exploit.js --cmd "whoami"
Этап 1: Утечка указателя кучи
└─ Повреждение заголовка чанка mdst → разбор сообщения об ошибке pfree()
Этап 2: Произвольное чтение (многооконное сканирование)
└─ Перезапись mdst->data → сканирование памяти рядом с утёкшим указателем
Этап 3: Сбор кандидатов-указателей
└─ Сканирование дампов кучи на предмет 8-байтовых значений, похожих на указатели
Этап 4: Определение базового адреса PIE
└─ Голосование утёкших адресов по смещениям символов ELF (группировка
по смещению страницы, офлайн — символы разбираются из локальной копии бинарника)
Этап 5: Проверка PIE
└─ Произвольное чтение CurrentUserId по кандидатному базовому адресу → сравнение с OID сессии
Этап 6: Произвольная запись
└─ Подделка MBuf-структур msrc + mdst → перезапись CurrentUserId → 10 (суперпользователь)
Этап 7: Выполнение команд
└─ COPY FROM PROGRAM → произвольная команда ОС от имени пользователя postgres
CVE-2026-2005/
├── Dockerfile # Сборка PostgreSQL 18.1 из исходников
├── docker-compose.yml # Сервис PostgreSQL с автоматическим перезапуском
├── init.sh # Точка входа — initdb + конфигурация прослушивания
├── README.md # Этот файл
└── poc/
├── package.json # Зависимости Node.js (pg)
├── exploit.js # Полный 7-этапный эксплойт RCE (Node.js)
├── verify.sh # Быстрая проверка на основе shell-скрипта
└── test-pgcrypto.sql # SQL-тест загрузки pgcrypto
git clone https://github.com/postgres/postgres.git
cd postgres
git checkout 4b324845ba5d24682b9b3708a769f00d160afbd7
./configure \
--prefix="$HOME/projects/pg/pgsql" \
--with-libxml \
--with-libxslt \
--enable-debug \
--with-ssl=openssl
make -j$(nproc)
make install-world-bin
tmux (необязательно — нужен только с флагом --gdb)Ошибка находится в pgp_parse_pubenc_sesskey() в contrib/pgcrypto/pgp-pubdec.c.
При разборе пакета OpenPGP Public-Key Encrypted Session Key (тег 1):
memcpy копирует данные ключа сессии в буфер фиксированного размера на основе непроверенной длиныmemcpy может записать за границу буфера кучи, повреждая соседние метаданные
или данные кучи, что приводит к произвольному выполнению кода.
pfree() для извлечения адреса кучи mdst->data./proc/<pid>/maps из контейнера Docker
для поиска адреса загрузки бинарника postgres во время выполнения.msrc (источник) и mdst (назначение)
через переполнение кучи. msrc указывает на встроенный пакет symenc,
содержащий OID суперпользователя (10); mdst указывает на CurrentUserId - 4
(с учётом 4-байтового заголовка SET_VARSIZE).CurrentUserId = 10 (bootstrap-суперпользователь)
выполняется COPY FROM PROGRAM для запуска произвольных команд ОС.| Флаг | По умолчанию | Описание |
|---|---|---|
--cmd | id | Команда ОС для выполнения после успешного эксплойта |
--key-size | 3072 | Размер ключа RSA в битах |
--host | 127.0.0.1 | Хост PostgreSQL |
--port | 5432 | Порт PostgreSQL |
--user | postgres | Пользователь базы данных |
--password | (пусто) | Пароль базы данных |
--dbname | postgres | Имя базы данных |
--binary | ./postgres | Путь к ELF-бинарнику postgres для символов |
--scan-offset | авто | Переопределение смещения сканирования кучи от утёкшего указателя |
--verbose | выкл | Включить подробный отладочный вывод |
--gdb | выкл | Подключить GDB через tmux в точке переполнения |
pgcrypto является доверенным — любой пользователь с привилегией CREATE
(не суперпользователь) может установить его, что делает уязвимость эксплуатируемой с низкими привилегиями.--enable-debug для упрощения отладки с помощью gdb.SRC_CHUNK_OFFSET=100, DST_CHUNK_OFFSET=172) специфичны
для aarch64 + glibc — для других архитектур/аллокаторов нужны другие смещения.CurrentUserId через тот же
примитив произвольного чтения.