PostGREShell: The database powering much of the internet had an open door for 12 years

Imagine you've built a fortress. Guards at the front gate, scanners at every door, a guest list checked twice. You hired the best architects, ran the audits, passed the compliance reviews. By every measure, the place is locked down.
But you missed something. Around the back, there's a small, unmarked entrance used by the cleaning crew. It's been there for years, and nobody thought to put a guard on it.
Then one day, someone figures out that if you walk in through that entrance wearing a cleaning uniform, the entire fortress opens up: the armory, the vault, the control room. Once you're inside, everyone assumes you belong.
That's PostGREShell. The "fortress" is PostgreSQL, the database quietly powering much of the internet. The "cleaning crew entrance" is the replication protocol, used by backup accounts present in every PostgreSQL setup. The vulnerability we discovered means anyone with access to that entrance can bypass every security system PostgreSQL has built, load arbitrary code onto the server, escalate to full superuser, and install a backdoor that survives cleanup.
It's been sitting there, unguarded, since 2014.
TL;DR
Cyera Research discovered a critical vulnerability (CVE-2026-6471) in PostgreSQL, the world's #1 open-source database that’s used by 39,000+ companies including Netflix, Instagram, Spotify, and Uber.
The flaw lets a low-privilege "backup" account load and execute arbitrary code on the database server, achieving remote code execution across Windows, Linux, and macOS. That foothold escalates to full PostgreSQL superuser with persistent backdoor access, turning a routine replication account into total database and server compromise.
The vulnerability has existed in every PostgreSQL version since 2014 (9.4+) and was never patched until now. A threat hunt across VirusTotal turned up 114 malicious PostgreSQL plugins already in the wild, including trojans, cryptocurrency miners, and reverse shells.
Why the blast radius is enormous
Every time you stream Netflix, scroll Instagram, book an Uber, or open Spotify, a database is handling millions of transactions behind the scenes. For a huge share of the internet, that database is PostgreSQL. It’s the world's most popular open-source database, fourth-most popular overall, and the top choice among professional developers. It's the engine behind AWS RDS, Azure Database, Google Cloud SQL, Supabase, and Neon, and it's used in production by 39,000+ verified companies.
PostgreSQL isn't just storing cat photos and shopping carts. It runs financial transactions, medical records, government infrastructure, SaaS platforms, and AI/ML pipelines. That means a critical vulnerability brings a huge blast radius.
The vulnerability: one door has a guard, the other doesn't
Why databases have "backup accounts"
In production you rarely run one copy of a database. You run a primary that handles writes and one or more replicas kept in sync for backups, disaster recovery, and read scaling. PostgreSQL syncs them over a dedicated replication protocol, and to use it you need an account with the REPLICATION attribute. These accounts are everywhere. Every backup tool, standby server, CDC pipeline, and monitoring system that reads the write-ahead log uses one. They're treated as low-risk operational plumbing — PostgreSQL's own docs describe the attribute as needed to "initiate streaming replication," not to run code.

The output plugin
Logical replication (wal_level = logical) records changes as table events external tools can read. This is the basis for Change Data Capture, Debezium, cloud migrations, and real-time analytics. A client reads those changes by creating a logical replication slot, and each slot names an output plugin (like pgoutput or wal2json) that PostgreSQL loads to format the stream.
Here's the problem: that plugin name goes straight to the loader.
PostgreSQL plugins are compiled code files (.so, .dll, .dylib) loaded into the database's own process. When one loads, PostgreSQL runs its _PG_init() function immediately, with the full privileges of the server process (similar to a phone app, but one that runs as root with no permission prompt). PostgreSQL knows this is dangerous, so it built a security mechanism called check_restricted_library_name() that forces non-superusers to load plugins only from one safe, admin-controlled directory, with no path traversal or absolute paths.
Let's look at what happens when PostgreSQL loads that plugin
That mechanism works. It's just never called on the replication path.
When PostgreSQL loads the output plugin, the name from the user's CREATE_REPLICATION_SLOT command is passed directly to the loader. No validation, no sanitization, no restriction check. The SQL LOAD command, by contrast, passes a !superuser() flag that triggers the path validation. The replication path passes nothing. And the replication protocol's parser accepts almost any character inside a double-quoted plugin name: slashes, backslashes, dots, ../ traversal, even Windows UNC paths. So an attacker can hand the loader a full filesystem path, and it reaches dlopen() (Linux/macOS) or LoadLibrary() (Windows) exactly as typed.

The whole bug is code execution via dlopen(). The fix is two lines of C:
Two lines of code is what’s left this vulnerability open since 2014.
What can an attacker do with this?
The vulnerability is universal, existing on every platform PostgreSQL runs on. What differs is only how an attacker gets a malicious library to the loader.
Windows: fully remote, out of the box. Windows resolves UNC paths (\\server\share\file) transparently over SMB. When PostgreSQL calls LoadLibrary("\\attacker\share\evil.dll"), Windows silently connects to the attacker's SMB server, downloads the DLL, maps it into the PostgreSQL process, and _PG_init() runs. The attacker never touches the target's filesystem — they host the DLL on their own machine and send one command.
All that, and the exploit fits in three lines of Python:

All it takes is a PostgreSQL account with the REPLICATION attribute (not superuser, not admin), wal_level = logical on the server, and SMB port 445 reachable from the server to the attacker.
From backup account to superuser
At this point, the attacker has code running on your database server as the postgres system user. Inside PostgreSQL, they're still just a backup account, but that doesn't last.
PostgreSQL has two layers of security. SQL-level security — ACLs, permission gates, role-based access, row-level security — is well-built and well-tested. C-level security doesn't exist. Code loaded via dlopen() runs in the same address space as PostgreSQL, with no sandbox and no checks on internal API calls. The server simply trusts any code that was loaded.

So the plugin calls an internal function to become the bootstrap superuser for the session, then writes directly to pg_authid, the catalog table that defines who is a superuser, and flips every privilege flag to true. That write never passes through the SQL executor, so no ACL check ever fires. For good measure, it installs a hook that makes every permission check return "allowed." The attacker's role goes from this:
to this:
The change is permanent, survives restarts, and looks identical to a normal ALTER ROLE in the catalog, so no admin can tell it was made through an unauthorized path. With superuser, the attacker reads every table in every database: customer data, financial records, application secrets, stored credentials. And PostgreSQL superusers can run OS commands (COPY … TO PROGRAM), read arbitrary files like /etc/shadow or private keys (pg_read_file()), and write files anywhere postgres can (lo_export()). They're not just in your database — they're on your server, ready to pivot with the credentials they just harvested.
The backdoor
Attackers want to come back. The plugin sets up three persistence mechanisms that cover each other. It rewrites pg_hba.conf to allow anyone to connect as anyone, without a password, and reloads the config instantly. It copies itself to a stable location and registers in shared_preload_libraries, so it reloads into every new backend after a restart. And it re-applies the superuser change even if an admin reverts it. So an admin who finds and fixes one backdoor still has two more to catch.
You've seen this before
PostGREShell is the latest instance of a vulnerability class that keeps recurring: a server loads plugins, the plugin name isn't validated, and an attacker abuses it to run code. The poster child is Redis, whose MODULE LOAD command has powered multiple botnet campaigns. HeadCrab alone infected 1,200+ Redis servers to mine cryptocurrency, and "RediShell" (CVE-2025-49844, CVSS 10.0) sat exposed for roughly 13 years across roughly 330,000 internet-facing instances.
The same pattern has hit OpenVPN (plugins loadable from any directory), MySQL (attacker-controlled shared libraries, which also affected MariaDB and Percona), MongoDB (uncontrolled dlopen() search path), and SQLite JDBC (arbitrary extension loading via a malicious JDBC URL).
The defenses are well-known, but this keeps happening because plugin-loading code is usually built separately from the main security model. That’s exactly what happened here, where PostgreSQL's core team guarded the SQL LOAD path and the replication team never connected the two.
Why this matters
PostGREShell turns the REPLICATION credential nobody worries about into code execution, superuser, and a persistent backdoor on the database behind much of the internet. Every version from 9.4 through 18 is affected (we confirmed it on 18.2), and logical replication is now standard production plumbing, so the vulnerable path exists almost everywhere PostgreSQL runs. Like Redis before it, this is a plugin-loading bug that ships wide and sits quiet, the unguarded back entrance to a fortress everyone assumed was locked.
How to protect yourself
Start by applying the recently released PostgreSQL security update. Then audit your REPLICATION accounts on every PostgreSQL instance:
Remove the REPLICATION attribute from any account that doesn't strictly need it. For the ones that remain, lock down pg_hba.conf so they connect only from known, trusted IPs. Never use 0.0.0.0/0 for replication.
Block outbound SMB (445) and NFS (2049) from your database servers at the firewall, which eliminates the standalone RCE paths on Windows and NFS-enabled Linux/macOS; disable autofs where it isn't needed. Monitor for anomalous replication activity, like CREATE_REPLICATION_SLOT from unexpected IPs, plugin names containing /, \, or .., and slots with unusual names.
A quick checklist to run through now:
- Update PostgreSQL with the security patch.
- Audit your REPLICATION accounts today.
- Block outbound SMB/NFS from your database servers.
- Review
pg_hba.conffor overly permissive replication rules. - Share this with your DBAs, security teams, and infrastructure engineers.
Disclosure timeline
- Feb 21, 2026: Reported to the PostgreSQL Security Team with root-cause analysis, affected versions, exploitation details, a Windows PoC, and suggested fixes.
- Feb 27, 2026: PostgreSQL acknowledged and confirmed the vulnerability (Noah Misch: "I agree this is a vulnerability. We'll fix it.").
- Mar 16, 2026: PostgreSQL confirmed the fix would ship in a scheduled minor release.
- May 14, 2026: Expected minor release / public advisory. CVE-2026-6471 assigned.
- August 22, 2026: Release published, CVE-2026-6471.
Acknowledgments
Cyera Research Labs thanks Noah Misch and the rest of the PostgreSQL Security Team for reviewing the report, confirming the vulnerability, and coordinating the fix through PostgreSQL's security release process. We appreciate their work to protect the PostgreSQL community.
For a deeper technical breakdown of the PostgreSQL vulnerability we found, check out the full write-up.

.png)

