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:
- An attacker with
pg_create_subscriptionrights creates a subscription with a specially crafted name containing embedded SQL commands. - The malicious subscription name sits dormant in the database catalog.
- When
pg_createsubscriberis next invoked (for example, during a standby promotion workflow), the utility callsdrop_existing_subscriptions()to clean up existing subscriptions. - The function builds its SQL command strings by directly interpolating the subscription name, so the injected SQL payload becomes part of the command string.
- 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:
c2e44c370for the PostgreSQL 18 (main) branchd7de7fa84backported 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:
- A new local variable,
subname_esc, holds the escaped subscription name. 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.- All three
appendPQExpBuffercalls now usesubname_escinstead of the rawsubname, which closes the injection vector in theALTER SUBSCRIPTION ... DISABLE,ALTER SUBSCRIPTION ... SET, andDROP SUBSCRIPTIONcommands. PQfreemem(subname_esc)frees the memory thatPQescapeIdentifierallocated, 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 Version | Vulnerable Versions | Fixed Version |
|---|---|---|
| 18 | All versions before 18.4 | 18.4 |
| 17 | All versions before 17.10 | 17.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
- PostgreSQL Security Advisory: CVE-2026-6476
- NVD Entry: CVE-2026-6476
- PostgreSQL 18.4, 17.10, 16.14, 15.18, and 14.23 Released
- PostgreSQL 17.10 Release Notes
- PostgreSQL 18.4 Release Notes
- Commit c2e44c370 (PostgreSQL 18 branch fix)
- Commit d7de7fa84 (PostgreSQL 17 branch backport)
- Commit d389415ffad (additional reference)
- Postgres May 2026 Security Update: 11 CVEs, All Versions Affected



