
Proof-of-concept exploit for CVE-2026-6471, demonstrating privilege escalation in PostgreSQL via logical decoding dlopen to achieve arbitrary code execution and superuser backdoor.
PostgreSQL logical decoding lets a non-superuser role that holds the
REPLICATION privilege create a logical replication slot and choose the
output plugin. On affected versions there is no authorization check on that
choice, so the server dlopen()s whatever the plugin name points to. Naming
a library path runs that library's code inside the postgres backend, as the
operating system account that runs the server. That is arbitrary code
execution as the database server's OS user, which for a database holding the
application data is effectively server takeover.
Fixed in PostgreSQL 18.6, 17.11, 16.15, 15.19, and 14.24 by the
output_plugin_libraries allowlist (default pgoutput, test_decoding).
Anything not on that list now errors with library "X" may not be used as an output plugin before any dlopen.
Run the same steps against both builds and only the PostgreSQL version changes. A low-privilege account (LOGIN + REPLICATION, not superuser, no OS access) does what a normal logical decoding subscriber does, then asks the server to load an arbitrary library as the output plugin. On the vulnerable build that library runs as the postgres OS user and plants a persistent superuser backdoor role, so a replication-only account ends the run with a working superuser login.
cve-2026-6471-postgres-logical-decoding-dlopen.txt is an Exploitmatic
solution (.txt): data plus asserts, no code. The runtime replays it against
the box. The solution delivers pwn.so at run time (base64 in the file),
drives the SQL as the low-privilege replication role, then logs in as the
superuser backdoor role the payload planted.pwn.c is the payload library source and pwn.so its build: a single ELF
constructor that runs as the postgres OS user, connects over the local
socket as the database superuser (peer/trust), and creates the persistent
role cve6471_backdoor LOGIN SUPERUSER PASSWORD 'BackdoorPass1'. It runs
only if the server actually loads the library, i.e. only on a vulnerable
build. Build with gcc -Os -shared -fPIC -o pwn.so pwn.c on a Linux libc
matching the server image.Preconditions: you have docker access.
The lab/ folder holds the exact replica used to verify this PoC. Build and
run the two boxes (vulnerable = postgres 16.14, patched = postgres 16.15):
docker build -t pg-6471-vuln -f lab/Dockerfile.vuln lab
docker build -t pg-6471-fixed -f lab/Dockerfile.fixed lab
docker run -d --name pg-6471-vuln -p 15432:5432 -e POSTGRES_PASSWORD=lab-super-pw pg-6471-vuln
docker run -d --name pg-6471-fixed -p 15433:5432 -e POSTGRES_PASSWORD=lab-super-pw pg-6471-fixed
Both boxes run the stock official postgres image with wal_level=logical.
The init script lab/01-repro.sh creates the low-privilege role the
solution uses, repro_rep LOGIN REPLICATION PASSWORD 'repropass', which is
not superuser.
Then replay the solution:
exploitmatic run cve-2026-6471-postgres-logical-decoding-dlopen.txt 127.0.0.1
Point at the patched box with a variable override, no file edit:
exploitmatic run cve-2026-6471-postgres-logical-decoding-dlopen.txt 127.0.0.1 \
--var ctr=pg-6471-fixed
ctr is the docker container, pw is the replication role's password.
| target | result |
|---|---|
| postgres 16.14 (vulnerable), wal_level=logical | 4/4 verified, superuser backdoor login works |
| postgres 16.15 (patched), wal_level=logical | 3/4 not verified, no backdoor |
For authorized testing and research only, on systems you own or have permission to test. This PoC demonstrates a post-authentication privilege escalation: it requires an existing low-privilege database account with the REPLICATION attribute, plus a way to place attacker-controlled code where the postgres OS user can load it. It is not a remote unauthenticated attack. The loaded code reaches database superuser because the OS account that owns the server can connect over the local socket as the superuser (peer/trust), which is standard PostgreSQL deployment practice.
| step | vulnerable 16.14 | patched 16.15 |
|---|
| recon (role is replication, not superuser) | passes | passes |
| baseline (logical slot with built-in pgoutput) | passes | passes |
| trigger (slot plugin = attacker library path) | server loads it | rejected before load |
| takeover (login as planted superuser backdoor) | superuser | no such role |