
Débordement de tampon du tas dans pgcrypto de PostgreSQL : preuve de concept démontrant CVE-2026-2005, exécution de code à distance en tant qu'utilisateur à faibles privilèges et élévation de privilèges jusqu'au superutilisateur via un paquet OpenPGP forgé.
Commit cible : 4b324845ba5d24682b9b3708a769f00d160afbd7 (PostgreSQL 18.1 — vulnérable)
| Champ | Détail |
|---|---|
| CVE | CVE-2026-2005 |
| Type | Débordement de tampon de tas |
| Composant | contrib/pgcrypto/pgp-pubdec.c — pgp_parse_pubenc_sesskey() |
| Impact | RCE en tant qu'utilisateur OS exécutant PostgreSQL |
| CVSS | 8.8 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H) |
| Authentification requise | Faible — tout utilisateur authentifié disposant du privilège CREATE |
| Versions affectées | PostgreSQL 14.0–14.20, 15.0–15.15, 16.0–16.11, 17.0–17.7, 18.0–18.1 |
| Corrigé dans | 14.21, 15.16, 16.12, 17.8, 18.2 (12 févr. 2026) |
| Architecture | aarch64 (ARM64) — les décalages de tas et la disposition des MBuf sont spécifiques à l'arch/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"
# Install node deps
npm install
# Run the full RCE exploit chain
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-offset scan)
└─ Overwrite mdst->data → scan memory near leaked pointer
Stage 3: Pointer candidate collection
└─ Scan heap dump for non-heap addresses
Stage 4: PIE base resolution
└─ Read /proc/<pid>/maps via Docker exec (100% reliable)
Stage 5: (skipped — PIE base is known from maps)
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 (optionnel — nécessaire uniquement avec l'option --gdb)Le bug se trouve dans pgp_parse_pubenc_sesskey() de contrib/pgcrypto/pgp-pubdec.c.
Lors de l'analyse d'un paquet OpenPGP de clé de session chiffrée par clé publique (balise 1) :
memcpy) les données de la clé de session dans un tampon de taille fixe en se basant sur la longueur non validéeLe memcpy peut écrire au-delà de la limite du tampon de tas, corrompant les métadonnées ou les données adjacentes du tas, conduisant à l'exécution de code arbitraire.
pfree() pour extraire l'adresse tas de mdst->data./proc/<pid>/maps depuis le conteneur Docker pour trouver l'adresse de chargement du binaire postgres au moment de l'exécution.msrc (source) et mdst (destination) via le débordement de tas. msrc pointe vers un paquet symenc intégré contenant l'OID du superutilisateur (10) ; mdst pointe vers CurrentUserId - 4 (en tenant compte de l'en-tête SET_VARSIZE de 4 octets).CurrentUserId = 10 (superutilisateur d'amorçage), exécuter COPY FROM PROGRAM pour lancer des commandes OS arbitraires.pgcrypto est de confiance — tout utilisateur disposant du privilège CREATE (non superutilisateur) peut l'installer, ce qui rend cette faille exploitable avec de faibles privilèges.--enable-debug pour faciliter le débogage avec gdb.SRC_CHUNK_OFFSET=100, DST_CHUNK_OFFSET=172) sont spécifiques à aarch64 + glibc — d'autres architectures/allocateurs nécessitent des décalages différents.restart: always pour se rétablir automatiquement des crashs backend lors des tests de candidats PIE./proc/<pid>/maps à l'intérieur du conteneur Docker — aucun readelf n'est nécessaire sur l'hôte.CurrentUserId est lue via docker exec readelf.| Option | Défaut | Description |
|---|
--cmd | id | Commande OS à exécuter après exploitation réussie |
--key-size | 3072 | Taille de la clé RSA en bits |
--host | 127.0.0.1 | Hôte PostgreSQL |
--port | 5432 | Port PostgreSQL |
--user | postgres | Utilisateur de la base de données |
--password | (vide) | Mot de passe de la base de données |
--dbname | postgres | Nom de la base de données |
--binary | ./postgres | Chemin vers le binaire ELF postgres pour les symboles |
--scan-offset | auto | Remplacer le décalage de scan du tas depuis le pointeur divulgué |
--verbose | désactivé | Activer les sorties de débogage verbeuses |
--gdb | désactivé | Attacher GDB via tmux au point de débordement |