Every backup job, every read replica, every change-data-capture pipeline you have ever stood up needed an account with the REPLICATION attribute on Postgres. Nobody thinks twice about that account. It doesn’t touch application data, it doesn’t have DDL rights, it just streams the write-ahead log to whatever is on the other end. That assumption held for twelve years. It stopped holding on September 1, when Cyera published PostGREShell.[1][2]

What the bug actually does

The short version: logical replication lets an external tool read database changes as a stream, and the client picks which output plugin formats that stream. PostgreSQL loads plugins from a restricted, admin-controlled directory for exactly this reason — a plugin is compiled code, and loading one runs its init function with the full privileges of the server process.[2]

The restriction check exists. It just never runs on the replication path. The plugin name that gets passed to CREATE_REPLICATION_SLOT goes straight to the loader with no validation, no path check, nothing stripped. Slashes, ../ traversal, even a Windows UNC path all survive intact.[2] On Windows that means an attacker with nothing but a REPLICATION-privileged login and a reachable SMB port can point the server at a DLL sitting on their own machine. PostgreSQL fetches it, loads it, runs it. No file ever touches the target’s disk.[2]

Once code is running inside the Postgres process, the SQL-level security model — the ACLs, the role checks, row-level security — doesn’t apply anymore. Cyera’s proof of concept has the loaded plugin write directly to pg_authid, the catalog table that tracks who is superuser, and flip every privilege bit to true. That write skips the SQL executor entirely, so no permission check ever fires. The account looks like an ordinary role in every audit query. It is, permanently, superuser.[2]

From there it’s game over in the normal ways: read every table in every database, COPY ... TO PROGRAM to run OS commands, pull /etc/shadow or private keys with pg_read_file(). Cyera also found the plugin can install itself to survive a revert and can enable passwordless connections, so removing the superuser flag once doesn’t mean you’re clean.[2]

Red Hat scores it 7.2 and notes correctly that exploitation requires the attacker to already hold the REPLICATION role, which most orgs don’t hand out casually.[4] That’s the honest caveat. It’s also the whole point of the finding: REPLICATION is exactly the kind of credential that gets provisioned once for a backup tool or a CDC pipeline and then never reviewed again, because nobody thinks of “can stream the WAL” as “can become root.” Cyera’s own threat hunt across VirusTotal turned up 114 malicious PostgreSQL plugins already circulating — trojans, miners, reverse shells — which tells you this isn’t purely theoretical tradecraft.[1][2]

Why this one bothers me more than the CVSS number

A 7.2 doesn’t usually stop me mid-scroll. This one did, for a reason that has nothing to do with the exploit chain and everything to do with what got missed. The vulnerable code path existed since PostgreSQL 9.4, released in 2014. Twelve years. Through how many security audits, how many compliance reviews, how many “we hardened our database tier” projects at how many companies. The fix is two lines of C checking for a directory separator in the plugin name.[2] Nobody wrote those two lines because nobody was looking at the account that just moves bytes around for backups.

That’s the pattern I keep running into across identity and access work generally, not just databases: the accounts that get scrutiny are the ones with an obviously scary name — admin, root, superuser. The accounts that get ignored are the ones doing plumbing. A service account that reads a log. A backup role that streams a WAL. A replication user that “can’t do anything, it’s just for DR.” Those are exactly the credentials that accumulate quiet, unreviewed privilege over a decade, because reviewing them feels like busywork right up until one of them turns out to be a path to superuser.

I don’t have a tidy fix for that beyond the obvious one, which is that “low risk” needs an expiration date on the label. Revisit it. A credential that was correctly scoped as low-risk in 2014 is not automatically still low-risk in 2026, and the only way to catch the gap is to actually go look, not to trust the name on the role.

What to do this week

The patch has been out since August 13 — versions 18.6, 17.11, 16.15, 15.19, and 14.24 all carry the fix.[3] If you’re running anything older than that on any of those major versions, you’re exposed regardless of how tightly you think you’ve locked down REPLICATION grants.

  1. Patch. This is a server-side fix, not a config change — get current on your minor version.[3]
  2. Pull a list of every role with the REPLICATION attribute in every instance you run, including the ones your backup vendor or a CDC tool provisioned for you. If you don’t know who has it, that’s the finding.
  3. Where logical decoding isn’t actually in use, don’t leave wal_level = logical set “just in case.” It’s the precondition for the entire attack path.
  4. If a REPLICATION account’s login history shows anything besides your backup or CDC tooling’s IP range, treat it as compromised and rotate — the plugin’s persistence tricks mean a single privilege revert isn’t enough to trust the box again.[2]

Sources

[1] https://www.securityweek.com/12-year-old-postgresql-vulnerability-enables-database-server-takeover — SecurityWeek: 12-Year-Old PostgreSQL Vulnerability Enables Database, Server Takeover [2] https://www.cyera.com/research/postgreshell-the-database-powering-much-of-the-internet-had-an-open-door-for-12-years — Cyera Research: PostGREShell disclosure [3] https://www.postgresql.org/support/security/CVE-2026-6471/ — PostgreSQL.org: CVE-2026-6471 official advisory [4] https://access.redhat.com/security/cve/cve-2026-6471 — Red Hat: CVE-2026-6471 details