Automated Threat Modeling

PostgreSQL CVE-2026-6476: Brief Summary of SQL Injection via pg_createsubscriber Subscription Names

A brief summary of CVE-2026-6476, a high-severity SQL injection in PostgreSQL's pg_createsubscriber utility that allows privilege escalation to superuser. Covers the patch and the affected versions.

CVE Analysis

7 min read

ZeroPath CVE Analysis
ZeroPath CVE Analysis

2026-05-14

PostgreSQL CVE-2026-6476: Brief Summary of SQL Injection via pg_createsubscriber Subscription Names
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

Introduction

A single unescaped identifier in PostgreSQL's pg_createsubscriber utility opened the door for any user with pg_create_subscription rights to escalate to superuser through a crafted subscription name. For organizations running PostgreSQL 17 or 18 with logical replication workflows, this vulnerability (CVSS 7.2) is a serious privilege escalation path, and it is straightforward to exploit once the attacker has the prerequisite access.

Technical Information

The root cause of CVE-2026-6476 is a classic SQL injection flaw. The drop_existing_subscriptions() function in src/bin/pg_basebackup/pg_createsubscriber.c concatenated the raw subscription name directly into SQL command strings without escaping or quoting it. The name went into three distinct SQL statements: ALTER SUBSCRIPTION ... DISABLE, ALTER SUBSCRIPTION ... SET (slot_name = NONE), and DROP SUBSCRIPTION. Because the code treated the name as a trusted literal, an attacker who could control the subscription name could inject arbitrary SQL into these commands.

The attack works like this:

  1. An attacker with pg_create_subscription rights creates a subscription with a specially crafted name containing embedded SQL commands.
  2. The malicious subscription name sits dormant in the database catalog.
  3. When pg_createsubscriber is next invoked (for example, during a standby promotion workflow), the utility calls drop_existing_subscriptions() to clean up existing subscriptions.
  4. The function builds its SQL command strings by directly interpolating the subscription name, so the injected SQL payload becomes part of the command string.
  5. The commands execute with superuser privileges, and the attacker gets full control over the database instance.

The CVSS vector string is AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H. The vulnerability is network exploitable with low attack complexity, but it requires high privileges (pg_create_subscription rights) as a prerequisite. Confidentiality, integrity, and availability impact are all rated high, which is consistent with code executing as superuser.

Yu Kunpeng reported the vulnerability. The pg_createsubscriber utility was introduced in PostgreSQL 17, which is why versions before 17 are unaffected.

Patch Information

Nathan Bossart wrote the official fix, Amit Kapila reviewed it, and Noah Misch committed it on May 11, 2026. It shipped in PostgreSQL 18.4 and 17.10. Two commits carry the fix:

  • c2e44c370 for the PostgreSQL 18 (main) branch
  • d7de7fa84 backported to the PostgreSQL 17 branch

Both commits contain the same logical change. The fix adds a call to PQescapeIdentifier(), libpq's standard function for safely quoting SQL identifiers. Before the patch, the code interpolated the raw subname variable directly into the three SQL statements. After it, PQescapeIdentifier() produces a new escaped variable, subname_esc, and the statements use that instead.

Here is the key diff from commit c2e44c370:

@@ -1118,18 +1118,23 @@ drop_existing_subscriptions(PGconn *conn, const char *subname, const char *dbnam { PQExpBuffer query = createPQExpBuffer(); PGresult *res; + char *subname_esc; Assert(conn != NULL); + subname_esc = PQescapeIdentifier(conn, subname, strlen(subname)); + /* * Construct a query string. These commands are allowed to be executed * within a transaction. */ appendPQExpBuffer(query, "ALTER SUBSCRIPTION %s DISABLE;", - subname); + subname_esc); appendPQExpBuffer(query, " ALTER SUBSCRIPTION %s SET (slot_name = NONE);", - subname); - appendPQExpBuffer(query, " DROP SUBSCRIPTION %s;", subname); + subname_esc); + appendPQExpBuffer(query, " DROP SUBSCRIPTION %s;", subname_esc); + + PQfreemem(subname_esc);

Walking through the change:

  1. A new local variable, subname_esc, holds the escaped subscription name.
  2. PQescapeIdentifier(conn, subname, strlen(subname)) wraps the identifier in double quotes and escapes any embedded double-quote characters, so the result is always a syntactically safe SQL identifier, whatever the name contains.
  3. All three appendPQExpBuffer calls now use subname_esc instead of the raw subname, which closes the injection vector in the ALTER SUBSCRIPTION ... DISABLE, ALTER SUBSCRIPTION ... SET, and DROP SUBSCRIPTION commands.
  4. PQfreemem(subname_esc) frees the memory that PQescapeIdentifier allocated, so the change does not introduce a memory leak.

This is a clean, minimal, and idiomatic fix. Instead of custom escaping logic, the patch uses PostgreSQL's own built-in PQescapeIdentifier() API, the canonical way to handle SQL identifiers safely in client-side code. The change is small, but it completely closes the injection surface in pg_createsubscriber's subscription cleanup path.

These are minor version upgrades, so a full pg_upgrade is not required. Administrators can stop the Postgres service, install the updated binaries for their specific version, and restart the database.

Affected Systems and Versions

The vulnerability affects PostgreSQL major versions 17 and 18 only:

Major VersionVulnerable VersionsFixed Version
18All versions before 18.418.4
17All versions before 17.1017.10

Versions before PostgreSQL 17 (including 16.x, 15.x, 14.x, and earlier) are not affected, because the pg_createsubscriber utility first appeared in version 17.

An attacker needs pg_create_subscription rights on the target database. Environments that restrict this privilege to trusted administrators have a smaller attack surface, though patching is still the recommended course of action.

Vendor Security History

PostgreSQL has a mature and transparent security response process. The May 2026 security update that fixed CVE-2026-6476 also resolved 10 other CVEs and over 60 bugs across all supported major versions (14 through 18).

PostgreSQL is the fourth most popular database technology globally, according to the DB Engines rankings, and it holds the top ranking among developers who want to use it (47 percent in the Stack Overflow 2025 Developer Survey). A vulnerability in PostgreSQL therefore reaches a broad ecosystem. The project's track record of timely, well-documented security releases is a significant reason users continue to trust it.

References

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Gerrit
  • CVS
  • Jira
  • Linear
  • Slack
  • Security Compass
Explore integrations
Security magnifying glass visualization