
PoC de estouro de buffer no heap do pgcrypto do PostgreSQL demonstrando CVE-2026-2005: RCE com baixo privilégio e escalada de privilégios para superusuário via pacote OpenPGP elaborado.
Commit alvo: 4b324845ba5d24682b9b3708a769f00d160afbd7 (PostgreSQL 18.1 — vulnerável)
| Campo | Detalhe |
|---|---|
| CVE | CVE-2026-2005 |
| Tipo | Estouro de buffer de heap |
| Componente | contrib/pgcrypto/pgp-pubdec.c — pgp_parse_pubenc_sesskey() |
| Impacto | RCE como o usuário do SO executando o PostgreSQL |
| CVSS | 8.8 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) |
| Autenticação necessária | Baixa — qualquer usuário autenticado com privilégio CREATE |
| Afetado | PostgreSQL 14.0–14.20, 15.0–15.15, 16.0–16.11, 17.0–17.7, 18.0–18.1 |
| Corrigido em | 14.21, 15.16, 16.12, 17.8, 18.2 (12 de fevereiro de 2026) |
| Arquitetura | aarch64 (ARM64) — os deslocamentos de heap e o layout do MBuf são específicos da arquitetura/glibc |
# Build and start PostgreSQL 18.1 (vulnerable)
docker compose up -d --build
# Wait for healthy
docker compose logs -f postgres
# Ctrl+C when you see "database system is ready to accept connections"
# One-time setup: copy the postgres binary for offline symbol parsing
docker cp cve-2026-2005-poc:/usr/local/pgsql/bin/postgres ./postgres
# Install node deps
npm install
# Run the full RCE exploit chain (pure SQL — no Docker interaction)
node exploit.js --cmd "id"
# With verbose output
node exploit.js --cmd "id" --verbose
# Execute a custom command
node exploit.js --cmd "whoami"
Stage 1: Heap pointer leak
└─ Corrupt mdst chunk header → parse pfree() error message
Stage 2: Arbitrary read (multi-window scan)
└─ Overwrite mdst->data → scan memory near leaked pointer
Stage 3: Pointer candidate collection
└─ Scan heap dumps for pointer-like 8-byte values
Stage 4: PIE base resolution
└─ Vote leaked addresses against ELF symbol offsets (page-offset
grouped, offline — symbols parsed from the local binary copy)
Stage 5: PIE validation
└─ Arb read CurrentUserId at candidate base → compare with session OID
Stage 6: Arbitrary write
└─ Forge msrc + mdst MBufs → overwrite CurrentUserId → 10 (superuser)
Stage 7: Command execution
└─ COPY FROM PROGRAM → arbitrary OS command as postgres user
CVE-2026-2005/
├── Dockerfile # Builds PostgreSQL 18.1 from source
├── docker-compose.yml # PostgreSQL service with auto-restart
├── init.sh # Entrypoint — initdb + listen config
├── README.md # This file
└── poc/
├── package.json # Node.js dependencies (pg)
├── exploit.js # Full 7-stage RCE exploit (Node.js)
├── verify.sh # Shell-based quick verification
└── test-pgcrypto.sql # SQL-only test of pgcrypto loading
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 — necessário apenas com a flag --gdb)O bug está em pgp_parse_pubenc_sesskey() em contrib/pgcrypto/pgp-pubdec.c.
Ao analisar um pacote OpenPGP Public-Key Encrypted Session Key (tag 1):
memcpy dos dados da chave de sessão para um buffer de tamanho fixo com base no comprimento não validadoO memcpy pode escrever além do limite do buffer de heap, corrompendo
metadados ou dados adjacentes do heap, levando à execução arbitrária de código.
pfree() para extrair o endereço de heap mdst->data./proc/<pid>/maps do contêiner Docker
para encontrar o endereço de carregamento do binário postgres em tempo de execução.msrc (origem) e mdst (destino)
através do estouro de heap. msrc aponta para um pacote symenc embutido
contendo o OID do superusuário (10); mdst aponta para CurrentUserId - 4
(considerando o cabeçalho SET_VARSIZE de 4 bytes).CurrentUserId = 10 (superusuário bootstrap),
executa COPY FROM PROGRAM para rodar comandos arbitrários do SO.| Flag | Padrão | Descrição |
|---|---|---|
--cmd | id | Comando do SO a executar após o exploit bem-sucedido |
--key-size | 3072 | Tamanho da chave RSA em bits |
--host | 127.0.0.1 | Host do PostgreSQL |
--port | 5432 | Porta do PostgreSQL |
--user | postgres | Usuário do banco de dados |
--password | (vazio) | Senha do banco de dados |
--dbname | postgres | Nome do banco de dados |
--binary | ./postgres | Caminho para o binário ELF postgres para símbolos |
--scan-offset | auto | Substitui o deslocamento de varredura do heap a partir do ponteiro vazado |
--verbose | desativado | Habilita saída de depuração detalhada |
--gdb | desativado | Anexa o GDB via tmux no ponto de estouro |
pgcrypto é confiável — qualquer usuário com privilégio CREATE
(não superusuário) pode instalá-la, tornando isso explorável com privilégios baixos.--enable-debug para facilitar a depuração com gdb.SRC_CHUNK_OFFSET=100, DST_CHUNK_OFFSET=172) são específicos
para aarch64 + glibc — outras arquiteturas/alocadores precisam de deslocamentos diferentes.CurrentUserId através da mesma
primitiva de leitura arbitrária.