Skip to main content
Vulnerability Database/CVE-2026-17346

CVE-2026-17346: pgAdmin 4 SQL Injection Vulnerability

CVE-2026-17346 is a SQL injection vulnerability in pgAdmin 4 that allows low-privileged users to inject arbitrary SQL via crafted object names. This article covers the technical details, affected versions, impact, and mitigation.

Published:

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Sources: National Vulnerability Database, CISA KEV, Exploit-DB

CVE-2026-17346 Overview

CVE-2026-17346 is a SQL injection vulnerability [CWE-89] in pgAdmin 4 that stems from an incomplete fix for CVE-2026-12044. The earlier patch hardened sixteen SQL template sinks with qtLiteral, but left several sinks in the test_sql_string_literal_lint.py ALLOWLIST under the assumption that schema, table, publication, and subscription names from pg_catalog could not contain apostrophes. PostgreSQL permits arbitrary characters in quoted identifiers, so a low-privileged user with CREATE TABLE, CREATE PUBLICATION, or CREATE SUBSCRIPTION rights can plant a booby-trapped object name. When any user opens that object's Statistics or Dependencies tab in pgAdmin, arbitrary SQL executes in the viewer's database session.

Critical Impact

A low-privileged attacker can inject arbitrary SQL into a higher-privileged administrator's database session by planting apostrophe-containing object names, escalating privileges through cross-user session hijack in pgAdmin.

Affected Products

  • pgAdmin 4 Index Statistics sink: versions from 1.0 before 9.17
  • pgAdmin 4 Publications sinks (pg and ppas/EPAS dialects): versions from 5.0 before 9.17
  • pgAdmin 4 Subscriptions sinks: versions from 5.0 before 9.17

Discovery Timeline

  • 2026-07-31 - CVE-2026-17346 published to NVD
  • 2026-08-05 - Last updated in NVD database

Technical Details for CVE-2026-17346

Vulnerability Analysis

The vulnerability is a second-order SQL injection in pgAdmin 4's Jinja-rendered SQL templates. When a user opens the Statistics or Dependencies tab for an index, publication, or subscription, pgAdmin renders a query that interpolates the target object's name directly into a single-quoted SQL literal. Because the object name is treated as trusted, no escaping is applied.

An attacker who owns any schema or database object can supply a name containing an apostrophe followed by stacked SQL. When a database administrator later inspects that object in pgAdmin, the injected statements run under the administrator's privileges. The affected sinks include coll_stats.sql for the all-indexes listing (both 16_plus and default PostgreSQL template variants), the publication dependencies.sql and get_position.sql templates (pg and ppas/EPAS dialects), and the subscription dependency templates.

Root Cause

The root cause is misplaced trust in identifiers returned by pg_catalog through the browser tree. Maintainers added the affected sinks to an ALLOWLIST in test_sql_string_literal_lint.py under the assumption that catalog-sourced names were safe. PostgreSQL, however, allows arbitrary characters in quoted identifiers, invalidating the assumption. The templates interpolated '{{ name }}' as a raw string literal instead of routing the value through the qtLiteral(conn) filter.

Attack Vector

Exploitation requires only low-privileged authenticated access to a PostgreSQL server managed by pgAdmin. The attacker creates a table, publication, or subscription with an apostrophe-embedded name. When any pgAdmin user with higher privileges opens the corresponding Statistics or Dependencies tab, the crafted SQL executes in that user's session.

text
 pg_catalog.quote_ident(pgb_table.schemaname)||'.'||pg_catalog.quote_ident(pgb_table.tablename) AS pubtable
 FROM pg_catalog.pg_publication_tables pgb_table
 LEFT JOIN pg_catalog.pg_class cl ON pgb_table.tablename = cl.relname
-WHERE pubname = '{{ pname }}' AND pgb_table.schemaname NOT LIKE 'pgagent';
+WHERE pubname = {{ pname|qtLiteral(conn) }} AND pgb_table.schemaname NOT LIKE 'pgagent';

The diff above shows the vulnerable raw interpolation replaced with the qtLiteral(conn) filter. Source: pgAdmin security commit 73b3218.

Detection Methods for CVE-2026-17346

Indicators of Compromise

  • Presence of PostgreSQL objects with names containing apostrophes, such as tables, publications, or subscriptions returned by queries against pg_class, pg_publication, or pg_subscription.
  • Unexpected SQL statements in PostgreSQL server logs executed under an administrator role immediately after opening a Statistics or Dependencies tab in pgAdmin.
  • pgAdmin instances running versions prior to 9.17 that permit low-privileged user access to shared PostgreSQL servers.

Detection Strategies

  • Audit pg_catalog for object identifiers containing single-quote characters: SELECT relname FROM pg_class WHERE relname LIKE '%''%'; and equivalent queries against pg_publication and pg_subscription.
  • Enable PostgreSQL log_statement = 'all' on managed servers and correlate stacked-statement patterns with pgAdmin session activity.
  • Review commits 73b3218 and f75452b to identify vulnerable template paths and confirm patch application.

Monitoring Recommendations

  • Alert on PostgreSQL DDL events that create objects whose names contain quote characters or SQL keywords such as DROP, GRANT, or COPY.
  • Monitor pgAdmin web server access logs for repeated requests to stats and dependencies endpoints from administrator sessions.
  • Track privilege changes and role modifications occurring shortly after Statistics or Dependencies tab access.

How to Mitigate CVE-2026-17346

Immediate Actions Required

  • Upgrade pgAdmin 4 to version 9.17 or later on all workstations and shared deployments.
  • Rename any existing PostgreSQL objects whose identifiers contain apostrophes until patched pgAdmin builds are deployed.
  • Restrict CREATE TABLE, CREATE PUBLICATION, and CREATE SUBSCRIPTION privileges to trusted users on shared servers.

Patch Information

The fix is delivered in pgAdmin 4 version 9.17. The patch routes all previously unescaped name interpolations through qtLiteral(conn) and updates publications/__init__.py and subscriptions/__init__.py to pass conn=self.conn into the dependencies.sqlrender_template call. ALLOWLIST entries in test_sql_string_literal_lint.py were removed, and a behavioral regression test now renders each fixed template with a stacked-statement apostrophe payload to verify escaping. See the pgAdmin issue tracker #10193 and commits 73b3218 and f75452b.

Workarounds

  • Instruct administrators to avoid opening Statistics and Dependencies tabs for untrusted or user-created objects until pgAdmin 9.17 is installed.
  • Revoke object-creation privileges from untrusted roles: REVOKE CREATE ON DATABASE <db> FROM PUBLIC; and grant explicitly to trusted users only.
  • Use dedicated pgAdmin instances per privilege tier so that low-privileged users and administrators do not share the same browser session context.
bash
# Verify no apostrophe-containing identifiers exist before upgrading
psql -c "SELECT nspname, relname FROM pg_class c JOIN pg_namespace n ON n.oid=c.relnamespace WHERE relname LIKE '%''%' OR nspname LIKE '%''%';"
psql -c "SELECT pubname FROM pg_publication WHERE pubname LIKE '%''%';"
psql -c "SELECT subname FROM pg_subscription WHERE subname LIKE '%''%';"

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Sources: National Vulnerability Database, CISA KEV, Exploit-DB

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.