
PoC de desbordamiento de búfer en el montón (heap buffer overflow) en pgcrypto de PostgreSQL que demuestra CVE-2026-2005: RCE con bajos privilegios y escalada de privilegios a superusuario mediante un paquete OpenPGP manipulado.
Commit objetivo: 4b324845ba5d24682b9b3708a769f00d160afbd7 (PostgreSQL 18.1 — vulnerable)
| Campo | Detalle |
|---|---|
| CVE | CVE-2026-2005 |
| Tipo | Desbordamiento de búfer en el heap |
| Componente | contrib/pgcrypto/pgp-pubdec.c — pgp_parse_pubenc_sesskey() |
| Impacto | RCE como el usuario del SO que ejecuta PostgreSQL |
| CVSS | 8.8 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) |
| Autenticación requerida | Baja — cualquier usuario autenticado con privilegio CREATE |
| Afectados | PostgreSQL 14.0–14.20, 15.0–15.15, 16.0–16.11, 17.0–17.7, 18.0–18.1 |
| Corregido en | 14.21, 15.16, 16.12, 17.8, 18.2 (12 de febrero de 2026) |
| Arquitectura | aarch64 (ARM64) — los offsets del heap y el diseño de MBuf son específicos de la arquitectura/glibc |
# Compilar e iniciar PostgreSQL 18.1 (vulnerable)
docker compose up -d --build
# Esperar a que esté saludable
docker compose logs -f postgres
# Ctrl+C cuando veas "database system is ready to accept connections"
# Configuración única: copiar el binario de postgres para el análisis de símbolos sin conexión
docker cp cve-2026-2005-poc:/usr/local/pgsql/bin/postgres ./postgres
# Instalar dependencias de node
npm install
# Ejecutar la cadena completa de explotación RCE (SQL puro — sin interacción con Docker)
node exploit.js --cmd "id"
# Con salida verbosa
node exploit.js --cmd "id" --verbose
# Ejecutar un comando personalizado
node exploit.js --cmd "whoami"
Etapa 1: Fuga de puntero del heap
└─ Corromper el encabezado del chunk mdst → analizar el mensaje de error de pfree()
Etapa 2: Lectura arbitraria (escaneo multi-ventana)
└─ Sobrescribir mdst->data → escanear la memoria cerca del puntero filtrado
Etapa 3: Recopilación de candidatos a puntero
└─ Escanear volcados del heap en busca de valores de 8 bytes similares a punteros
Etapa 4: Resolución de la base PIE
└─ Votar las direcciones filtradas contra los offsets de símbolos ELF (agrupados
por offset de página, sin conexión — los símbolos se analizan desde la copia local del binario)
Etapa 5: Validación de PIE
└─ Lectura arbitraria de CurrentUserId en la base candidata → comparar con el OID de la sesión
Etapa 6: Escritura arbitraria
└─ Forjar MBufs msrc + mdst → sobrescribir CurrentUserId → 10 (superusuario)
Etapa 7: Ejecución de comandos
└─ COPY FROM PROGRAM → comando arbitrario del SO como usuario postgres
CVE-2026-2005/
├── Dockerfile # Compila PostgreSQL 18.1 desde el código fuente
├── docker-compose.yml # Servicio PostgreSQL con reinicio automático
├── init.sh # Punto de entrada — initdb + configuración de escucha
├── README.md # Este archivo
└── poc/
├── package.json # Dependencias de Node.js (pg)
├── exploit.js # Exploit RCE completo de 7 etapas (Node.js)
├── verify.sh # Verificación rápida basada en shell
└── test-pgcrypto.sql # Prueba solo SQL de la carga de 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 (opcional — solo se necesita con la bandera --gdb)El error está en pgp_parse_pubenc_sesskey() en contrib/pgcrypto/pgp-pubdec.c.
Al analizar un paquete OpenPGP de clave de sesión cifrada con clave pública (etiqueta 1):
memcpy copia los datos de la clave de sesión en un búfer de tamaño fijo basado en la longitud no validadaEl memcpy puede escribir más allá del límite del búfer del heap, corrompiendo metadatos
o datos adyacentes del heap, lo que lleva a la ejecución arbitraria de código.
pfree() para extraer la dirección del heap de mdst->data./proc/<pid>/maps desde el contenedor Docker
para encontrar la dirección de carga del binario de postgres en tiempo de ejecución.msrc (origen) y mdst (destino)
mediante el desbordamiento del heap. msrc apunta a un paquete symenc incrustado
que contiene el OID de superusuario (10); mdst apunta a CurrentUserId - 4
(teniendo en cuenta el encabezado SET_VARSIZE de 4 bytes).CurrentUserId = 10 (superusuario de arranque),
ejecutar COPY FROM PROGRAM para ejecutar comandos arbitrarios del SO.pgcrypto es confiable — cualquier usuario con privilegio CREATE
(no superusuario) puede instalarla, lo que hace que esto sea explotable con privilegios bajos.--enable-debug para facilitar la depuración con gdb.SRC_CHUNK_OFFSET=100, DST_CHUNK_OFFSET=172) son específicos
de aarch64 + glibc — otras arquitecturas/asignadores necesitan offsets diferentes.CurrentUserId a través de la misma
primitiva de lectura arbitraria.| Bandera | Predeterminado | Descripción |
|---|
--cmd | id | Comando del SO a ejecutar tras una explotación exitosa |
--key-size | 3072 | Tamaño de la clave RSA en bits |
--host | 127.0.0.1 | Host de PostgreSQL |
--port | 5432 | Puerto de PostgreSQL |
--user | postgres | Usuario de la base de datos |
--password | (vacío) | Contraseña de la base de datos |
--dbname | postgres | Nombre de la base de datos |
--binary | ./postgres | Ruta al binario ELF de postgres para los símbolos |
--scan-offset | automático | Sobrescribir el offset de escaneo del heap desde el puntero filtrado |
--verbose | desactivado | Habilitar salida de depuración verbosa |
--gdb | desactivado | Adjuntar GDB mediante tmux en el punto de desbordamiento |