Back to Intelligence

CVE-2026-6471: PostgreSQL Logical Decoding RCE — Detection, Hunting, and Patching Guide

SA
Security Arsenal Team
September 4, 2026
13 min read

PostgreSQL's September 2026 out-of-band security release closes CVE-2026-6471 (CVSS 7.2), a vulnerability that has been sitting in the world's most popular open-source database for twelve years — since logical decoding shipped in PostgreSQL 9.4 in 2014. The flaw allows any database account holding the REPLICATION attribute to execute arbitrary code as the operating-system user running the PostgreSQL server (typically the postgres user, which owns the entire data directory, WAL, and configuration).

Let me be direct about why this matters to defenders: the REPLICATION attribute is handed out far more liberally than SUPERUSER in most environments I've assessed. CDC pipelines (Debezium, pglogical, wal2json), backup tooling (pgBackRest, Barman, WAL-G), ETL jobs, and managed replication services all demand it — and those service accounts are frequently provisioned with weak credential hygiene, stored in plaintext connector configs, and exposed beyond the DBA team's visibility. CVE-2026-6471 converts any one of those compromised accounts into OS-level code execution on the database host. From there, an attacker owns your crown-jewel data, can tamper with WAL archives, and has a pivot point into whatever network segment the database sits in.

If you run PostgreSQL 14 through 18 with any replication-enabled role — and statistically, you do — treat this as a patch-now event.

Technical Analysis

Affected Versions

The vulnerability exists in all supported PostgreSQL major versions prior to the September 2026 cumulative update:

BranchVulnerableFixed In
PostgreSQL 18< 18.618.6
PostgreSQL 17< 17.1117.11
PostgreSQL 16< 16.1516.15
PostgreSQL 15< 15.1915.19
PostgreSQL 14< 14.2414.24

Because the flaw was introduced with logical decoding in PostgreSQL 9.4 (2014), any end-of-life branch from 9.4 through 13 is also vulnerable and will never receive a fix. If you're still running PG 13 or earlier in production — and our incident response caseload says many of you are — this is one more forcing function for your upgrade program. There is no patch coming for those branches.

How the Vulnerability Works

Logical decoding is the mechanism that streams changes from the write-ahead log (WAL) to external consumers — replication slots, CDC connectors, and downstream subscribers. A role connecting over the replication protocol (the walsender path) with the REPLICATION attribute can drive logical decoding output plugins.

CVE-2026-6471 is a trust-boundary failure in that path: input handled during logical decoding is processed with insufficient validation/sanitization, allowing a replication-role principal to break out of the SQL layer and achieve arbitrary code execution in the security context of the postmaster's OS user. The critical exploitation characteristics from a defensive standpoint:

  • Authentication required, but a low bar. The attacker needs a valid role with the REPLICATION attribute — not SUPERUSER. In real environments, replication credentials are among the most widely distributed database secrets: connector configs, Kubernetes secrets, CI variables, backup scripts.
  • Network-reachable over the replication protocol. Any host permitted by pg_hba.conf replication entries (including host replication ... 0.0.0.0/0 misconfigurations we routinely find) can attempt exploitation.
  • Post-exploitation context is the database server OS account. That means full read/write over $PGDATA, the ability to drop or alter postgresql.conf/pg_hba.conf, tamper with WAL archives and backups, load arbitrary extensions, and run whatever the host allows that user to run.

The severity math: CVSS 7.2 reflects the authenticated prerequisite, but in environments where replication credentials leak into CI logs or connector properties files (a finding in roughly half the Postgres assessments I've led), the effective precondition is much lower.

Exploitation Status

As of disclosure, there is no confirmed in-the-wild exploitation and the CVE is not yet listed in CISA's Known Exploited Vulnerabilities catalog. However, the 12-year exposure window, the ubiquity of PostgreSQL (including embedded in hundreds of commercial products and cloud RDS-family services), and the well-documented value of database hosts as ransomware staging points mean exploitation research will move fast. History with database flaws of this class suggests PoC publication within weeks of patch availability. Patch before that window closes — and hunt for retroactive compromise on any host that had broadly-scoped replication credentials.

Detection & Response

Detection strategy here is behavioral, not signature-based. The most reliable observable of successful exploitation is the PostgreSQL backend (postgres process on Linux, postgres.exe on Windows) spawning a child process — shells, interpreters, downloaders, or reconnaissance utilities. A healthy PostgreSQL server almost never executes OS commands as children of backend processes outside of a narrow set of legitimate cases (COPY ... PROGRAM, archive/restore commands, known extensions). The second detection surface is replication-protocol connection auditing: who is connecting with replication intent, from where, and whether those sources match your known CDC/backup infrastructure.

Sigma Rules

YAML
---
title: PostgreSQL Backend Spawning Suspicious Child Process
id: 3f8a1b42-9c6d-4e27-a5f1-2d8c7e4b9012
status: experimental
description: Detects the PostgreSQL server process spawning shells, interpreters, or living-off-the-land binaries. Successful exploitation of CVE-2026-6471 (logical decoding RCE) yields code execution as a child of a postgres backend. Legitimate causes are rare and enumerable (COPY PROGRAM, archive_command, extensions).
references:
  - https://thehackernews.com/2026/09/postgresql-fixes-12-year-old-logical.html
  - https://attack.mitre.org/techniques/T1059/
author: Security Arsenal
date: 2026/09/15
tags:
  - attack.execution
  - attack.t1059
  - attack.exploitation_for_privilege_escalation
  - attack.t1068
logsource:
  category: process_creation
  product: linux
detection:
  selection_parent:
    ParentImage|endswith:
      - '/postgres'
      - '/postmaster'
  selection_child:
    Image|endswith:
      - '/sh'
      - '/bash'
      - '/dash'
      - '/zsh'
      - '/python'
      - '/python2'
      - '/python3'
      - '/perl'
      - '/ruby'
      - '/php'
      - '/curl'
      - '/wget'
      - '/nc'
      - '/ncat'
      - '/netcat'
      - '/socat'
      - '/base64'
      - '/chmod'
      - '/chown'
      - '/id'
      - '/whoami'
      - '/uname'
  condition: selection_parent and selection_child
falsepositives:
  - Legitimate archive_command / restore_command scripts invoking shells
  - COPY ... TO/FROM PROGRAM in application workflows
  - Extensions such as pg_cron executing scheduled shell tasks
level: high
---
title: PostgreSQL Replication Connection from Unexpected Source
id: 7c2e5d91-4b8a-4f63-b1d7-9e3a6c2f8504
status: experimental
description: Detects PostgreSQL log lines indicating a replication connection was authorized or a replication slot command was issued. Baseline known CDC/backup hosts (Debezium, pgBackRest, Barman, replicas) and alert on any other source. Relevant to exploitation attempts against CVE-2026-6471, which requires replication-protocol access.
references:
  - https://thehackernews.com/2026/09/postgresql-fixes-12-year-old-logical.html
  - https://attack.mitre.org/techniques/T1190/
author: Security Arsenal
date: 2026/09/15
tags:
  - attack.initial_access
  - attack.t1190
  - attack.lateral_movement
logsource:
  category: application
  product: linux
  service: postgresql
detection:
  selection:
    - 'replication connection authorized'
    - 'replication connection attempt'
    - 'starting replication'
    - 'IDENTIFY_SYSTEM'
    - 'CREATE_REPLICATION_SLOT'
    - 'START_REPLICATION'
    - 'pg_create_logical_replication_slot'
  condition: selection
falsepositives:
  - Known replication replicas, CDC connectors, and backup infrastructure — filter via allowlist of source hosts and replication users
level: medium
---
title: PostgreSQL Role Granted Replication Attribute via SQL
id: a1d4e7c3-6f29-4b58-c2e6-5a9d3f7b1248
status: experimental
description: Detects SQL statements granting or creating roles with the REPLICATION attribute. A prerequisite for CVE-2026-6471 exploitation is a replication-capable principal; unexpected grants may indicate an attacker staging access from a compromised superuser or DBA session.
references:
  - https://thehackernews.com/2026/09/postgresql-fixes-12-year-old-logical.html
  - https://attack.mitre.org/techniques/T1098/
author: Security Arsenal
date: 2026/09/15
tags:
  - attack.persistence
  - attack.t1098
  - attack.privilege_escalation
logsource:
  category: application
  product: linux
  service: postgresql
detection:
  selection_keywords:
    - 'CREATE ROLE'
    - 'CREATE USER'
    - 'ALTER ROLE'
    - 'ALTER USER'
  selection_attr:
    - 'REPLICATION'
  condition: selection_keywords and selection_attr
falsepositives:
  - DBA provisioning of new replicas, CDC connectors, or backup service accounts — correlate with change tickets
level: medium

A note on tuning: rule one is the high-signal rule. Before deploying it fleet-wide, enumerate your legitimate archive_command, restore_command, and COPY PROGRAM usage per host and build an allowlist. In most production estates, a postgres parent spawning bash -c with an encoded payload is effectively never benign.

KQL — Microsoft Sentinel / Defender

This query hunts the same behavior across hosts whether you're ingesting via Syslog/CEF, Defender for Endpoint on Linux, or auditd. Baseline your known replication infrastructure first — the second half of the query surfaces replication connections from non-baselined sources.

KQL — Microsoft Sentinel / Defender
// Hunt 1: postgres backend spawning suspicious child processes (post-exploitation of CVE-2026-6471)
let SuspiciousChildren = dynamic(["sh","bash","dash","zsh","python","python3","perl","curl","wget","nc","ncat","socat","chmod","id","whoami"]);
union isfuzzy=true
    (DeviceProcessEvents
    | where InitiatingProcessFileName has_any ("postgres", "postmaster")
    | where FileName has_any (SuspiciousChildren)
    | project TimeGenerated, DeviceName, InitiatingProcessFileName, InitiatingProcessCommandLine, FileName, ProcessCommandLine, AccountName),
    (Syslog
    | where ProcessName =~ "postgres"
    | where SyslogMessage has_any ("COPY", "PROGRAM", "archive_command")
    | project TimeGenerated, Computer, ProcessName, SyslogMessage)
| order by TimeGenerated desc;

// Hunt 2: replication-protocol activity and replication slot manipulation (requires pgaudit/statement logging shipped to Syslog or CommonSecurityLog)
Syslog
| where SyslogMessage has_any ("replication connection authorized", "CREATE_REPLICATION_SLOT", "START_REPLICATION", "pg_create_logical_replication_slot", "IDENTIFY_SYSTEM")
| extend SourceIP = extract(@"connection received: host=\[?([0-9a-fA-F:\.]+)\]?", 1, SyslogMessage)
| summarize ConnectionCount = count(), FirstSeen = min(TimeGenerated), LastSeen = max(TimeGenerated) by Computer, SourceIP, ProcessName
| order by ConnectionCount desc;

// Hunt 3: roles granted REPLICATION attribute (staging for CVE-2026-6471 exploitation)
Syslog
| where SyslogMessage has_any ("CREATE ROLE", "CREATE USER", "ALTER ROLE", "ALTER USER") and SyslogMessage has "REPLICATION"
| project TimeGenerated, Computer, SyslogMessage, ProcessName
| order by TimeGenerated desc;

To make Hunts 2 and 3 fire, you need statement logging on the PostgreSQL side: set log_connections = on and log_statement = 'ddl' (minimum) or deploy pgaudit with pgaudit.log = 'ddl, role' and ship logs via syslog to Sentinel. If your database estate is dark from a telemetry standpoint, this CVE is your justification to fix that.

Velociraptor VQL

Use this artifact for retro-hunting across Linux database hosts — both for suspicious postgres child processes and to inventory replication-capable roles and slot configurations on disk where config files are readable.

VQL — Velociraptor
-- Hunt: PostgreSQL post-exploitation indicators (CVE-2026-6471)
-- Part 1: live postgres processes with suspicious children or command lines
SELECT Pid, Ppid, Name, Exe, CommandLine, Username, CreateTime
FROM pslist()
WHERE Name =~ 'postgres'
   OR CommandLine =~ 'postgres'

-- Part 2: enumerate PostgreSQL process tree for shell interpreters spawned under backends
SELECT Pid, Ppid, Name, Exe, CommandLine, Username, CreateTime
FROM pslist()
WHERE Exe =~ '(sh|bash|dash|python|perl|curl|wget|nc|ncat|socat)$'
  AND Ppid IN (
      SELECT Pid FROM pslist() WHERE Name =~ 'postgres'
  )

-- Part 3: pull pg_hba.conf and postgresql.conf for replication exposure review
SELECT OSPath, Size, Mtime,
       read_file(filename=OSPath) AS Content
FROM glob(globs=['/etc/postgresql/*/main/pg_hba.conf',
                 '/var/lib/pgsql/*/data/pg_hba.conf',
                 '/**/pg_hba.conf'])
WHERE Content =~ 'replication'

Part 3 gives you the raw material for exposure analysis: any host replication line with a wide CIDR (anything broader than your known replica/CDC subnets) is both an exploitation prerequisite and a misconfiguration to fix regardless of this CVE.

Remediation & Verification Script

The following Bash script audits a PostgreSQL host: checks the running version against the fixed releases, inventories roles carrying the REPLICATION attribute, reviews pg_hba.conf replication scoping, and lists existing replication slots. Run it on every database host; the version check is your pass/fail gate.

Bash / Shell
#!/bin/bash
# CVE-2026-6471 audit script — Security Arsenal
# Checks PostgreSQL version vs. fixed releases and inventories replication attack surface.
set -u

echo "=== [1] PostgreSQL version check ==="
if command -v psql >/dev/null 2>&1; then
    PGVER=$(psql -tAc "SHOW server_version;" 2>/dev/null || sudo -u postgres psql -tAc "SHOW server_version;" 2>/dev/null)
    echo "Server version: ${PGVER:-UNKNOWN}"
else
    PGVER=$(postgres --version 2>/dev/null || pg_config --version 2>/dev/null)
    echo "psql not found; binary version: $PGVER"
fi

# Flag end-of-life / unpatched branches
case "$PGVER" in
    9.*|10.*|11.*|12.*|13.*) echo "[FAIL] End-of-life branch — no patch will be released. Upgrade required." ;;
    14.*)  v=${PGVER#14.};  [ "${v%%.*}" -lt 24 ] && echo "[FAIL] Patch to 14.24" || echo "[OK] 14.24+" ;;
    15.*)  v=${PGVER#15.};  [ "${v%%.*}" -lt 19 ] && echo "[FAIL] Patch to 15.19" || echo "[OK] 15.19+" ;;
    16.*)  v=${PGVER#16.};  [ "${v%%.*}" -lt 15 ] && echo "[FAIL] Patch to 16.15" || echo "[OK] 16.15+" ;;
    17.*)  v=${PGVER#17.};  [ "${v%%.*}" -lt 11 ] && echo "[FAIL] Patch to 17.11" || echo "[OK] 17.11+" ;;
    18.*)  v=${PGVER#18.};  [ "${v%%.*}" -lt 6 ]  && echo "[FAIL] Patch to 18.6"  || echo "[OK] 18.6+" ;;
    *) echo "[WARN] Could not parse version — verify manually." ;;
esac

echo ""
echo "=== [2] Roles with REPLICATION attribute ==="
sudo -u postgres psql -c "SELECT rolname, rolsuper, rolreplication, rolcanlogin FROM pg_authid WHERE rolreplication OR rolsuper ORDER BY rolsuper DESC;"

echo ""
echo "=== [3] Active replication slots ==="
sudo -u postgres psql -c "SELECT slot_name, slot_type, active, restart_lsn FROM pg_replication_slots;"

echo ""
echo "=== [4] pg_hba.conf replication entries (check CIDR scope) ==="
PGHBA=$(sudo -u postgres psql -tAc "SHOW hba_file;" 2>/dev/null)
echo "hba_file: $PGHBA"
sudo grep -Ei '^[[:space:]]*(host|local).*replication' "$PGHBA" 2>/dev/null | grep -v '^[[:space:]]*#' || echo "No replication entries found (or unreadable)."

echo ""
echo "=== [5] Open replication-protocol sessions ==="
sudo -u postgres psql -c "SELECT pid, usename, application_name, client_addr, state FROM pg_stat_replication;"

echo ""
echo "Audit complete. Rotate credentials for every role listed in section [2] after patching."

Remediation

1. Patch immediately. Upgrade to PostgreSQL 18.6, 17.11, 16.15, 15.19, or 14.24 (or later) per the official PostgreSQL security announcement at postgresql.org/support/security. For package-managed installs (apt/yum/dnf), a minor-version upgrade is in-place and requires only a service restart — but plan the restart around replication lag on primaries with standbys, and patch replicas first. If you consume PostgreSQL through a managed service (RDS, Aurora, Azure Database for PostgreSQL, Cloud SQL), verify the provider has applied the engine patch and confirm your instance's effective minor version; managed providers lag community releases by days to weeks.

2. No patch available? Reduce the precondition. There is no true configuration workaround — the vulnerable code path is inherent to logical decoding. Compensating controls while you stage the patch:

  • Audit and minimize the REPLICATION attribute population. Run section [2] of the script above. Every role with rolreplication = true that is not actively driving a replica, CDC connector, or backup job should have the attribute revoked: ALTER ROLE <name> NOREPLICATION;. Remember: SUPERUSER implies replication capability too — apply the same scrutiny.
  • Tighten pg_hba.conf replication entries. Scope every host replication line to the exact source IPs of legitimate consumers, prefer scram-sha-256 authentication, and eliminate any trust or broad-CIDR entries. Replication connections should also be wrapped in TLS (hostssl).
  • Rotate replication credentials. Assume any replication credential stored in a connector config, container secret, or CI variable may have been exposed over the flaw's 12-year life. Rotate after patching, and move secrets into a proper vault.

3. Upgrade end-of-life branches. PostgreSQL 13 and older are permanently vulnerable. If business constraints prevent an immediate major upgrade, isolate those hosts at the network layer (deny all inbound except application and known replica sources) and prioritize them in your migration program.

4. Hunt retroactively. Given the exposure window, deploy the Sigma and KQL detections above against historical logs where retention permits. A postgres backend that spawned bash, curl, or chmod at any point in your log history deserves forensic attention — image the host before rebuilding.

5. Fix the telemetry gap. You cannot detect replication-protocol abuse you never logged. Enable log_connections = on, log_statement = 'ddl' (or pgaudit), and ship PostgreSQL logs to your SIEM. This is table stakes for any database holding regulated data under PCI-DSS or HIPAA.

Conclusion

CVE-2026-6471 is a textbook case of the "trusted feature, trusted too far" failure mode. Logical decoding was designed for a world where replication roles were tightly held DBA secrets; twelve years later, those credentials live in Kafka Connect configs and GitHub repos. The vulnerability's long dwell time cuts both ways — it means broad exposure, but it also means your detection telemetry may already contain the evidence if you know where to look. Patch to the fixed minor releases, shrink your replication-role population to the documented minimum, tighten pg_hba.conf, and put eyes on postgres child-process behavior. Database hosts are where ransomware crews and state actors go to finish the job. Don't make it easy for them.

Related Resources

Security Arsenal Managed SOC Services AlertMonitor Platform Book a SOC Assessment soc-mdr Intel Hub

Is your security operations ready?

Get a free SOC assessment or see how AlertMonitor cuts through alert noise with automated triage.