Skip to main content
CVE Vulnerability Database
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:

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.

Default Legacy - Prefooter | Experience the World’s Most Advanced Cybersecurity Platform

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.