CVE-2026-12044: SQL Injection in pgAdmin 4 Description Fields
A SQL injection flaw in pgAdmin 4 allows an authenticated user to break out of database description fields by injecting a single quote, then execute arbitrary SQL commands. The vulnerability exists across 16 template locations where user-supplied descriptions are rendered directly into SQL without proper escaping—in dialogs for Domains, Foreign Tables, Languages, Event Triggers, and Views. An attacker with superuser or similar elevated database roles could escalate further to run OS commands on the PostgreSQL host. However, the flaw does not grant new database access; it only bypasses application-layer controls a DBA may have configured to restrict direct SQL execution.
Source data · NVD / CISA · public domain
- CVSS
- 3.1 · 8.8 HIGH · CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
- Weaknesses (CWE)
- CWE-116, CWE-89
- Affected products
- 1 configuration(s)
- Published / Modified
- 2026-06-19 / 2026-07-01
NVD description (verbatim)
SQL injection in pgAdmin 4 across every dialog template that renders ``COMMENT ON ... IS '<description>'`` for a user-supplied description field. The Jinja templates for Domains (and their constraints), Foreign Tables, Languages, and Event Triggers, plus the Views OID-lookup query, interpolated the description directly inside a single-quoted SQL literal -- ``'{{ data.description }}'`` -- instead of passing it through the ``qtLiteral`` escape filter. An authenticated pgAdmin user with permission to create or alter the affected object types could submit a description containing an apostrophe, break out of the literal and chain arbitrary SQL. The injected SQL runs under the PostgreSQL role the user is already authenticated as; for a connected role with ``COPY ... TO/FROM PROGRAM`` (typically PostgreSQL superuser), this chains to OS command execution on the PostgreSQL host. The defect does not cross a privilege boundary -- the user already has direct SQL access to that role through pgAdmin's Query Tool -- so the attacker gains no capability beyond what their database role already grants. The marginal impact captures bypass of any application-layer Query Tool gating an operator may have configured. The defect was originally reported against the Domain Dialog ``description`` field; a code-wide audit identified sixteen sites of the same pattern across the templates listed above. The same review also surfaced ten related sinks in the pgstattuple/pgstatindex stats templates -- ``pgstattuple('{{schema}}.{{table}}')`` and the matching pgstatindex shape -- where ``qtIdent`` escapes embedded double quotes inside the identifier but not apostrophes, so a user with CREATE privilege on a schema could plant a table or index named ``foo'bar`` and a later stats viewer would render an unbalanced literal. Fix is layered: 1. Sites: replace every ``'{{ x.description }}'`` with ``{{ x.description|qtLiteral(conn) }}`` (no surrounding quotes -- the filter wraps the value in escaped quotes itself). Plumb ``conn=self.conn`` through every ``render_template`` call that loads one of these templates. Also corrects a ``{ % elif`` Jinja typo in the foreign-table schema diff (dead branch). Rewrite the ten pgstattuple/pgstatindex stats sites to address the relation via OID + ``::oid::regclass`` cast (e.g. ``pgstattuple({{ tid }}::oid::regclass)``), eliminating the embedded literal-call form entirely so that bug-class can no longer recur there. 2. Driver hardening: ``qtLiteral`` (in ``utils/driver/psycopg3/__init__.py``) used to silently return the raw unescaped value when its ``conn`` argument was falsy. It now raises ``ValueError`` -- surfacing the entire bug class going forward. The change immediately uncovered eight latent plumbing bugs (in ``schemas/__init__.py``, ``schemas/functions/__init__.py``, ``schemas/tables/utils.py``, ``foreign_servers/__init__.py``, and seven sites in ``roles/__init__.py``) -- all fixed as part of this patch. The inner ``except`` block that swallowed adapter-level failures and returned the raw value is also removed, so unadaptable inputs raise instead of leaking unescaped values. 3. Regression tests: a per-template behavioural test renders each previously-vulnerable template with an apostrophe-injection payload and asserts the escaped fragment is present and the vulnerable fragment absent; a lint test walks every ``*.sql`` template flagging any ``'{{ ... }}'`` single-quote-wrapped interpolation against an explicit allowlist; unit tests cover the new qtLiteral fail-fast and inner-except raise paths. This issue affects pgAdmin 4: from 1.0 before 9.16.
3 reference(s) · View on NVD →
SEC.co analysis · AI-assisted, reviewed against source
Technical summary
The root cause is improper Jinja template escaping in pgAdmin 4's SQL-generation code. Description fields are interpolated as `'{{ data.description }}'` instead of `{{ data.description|qtLiteral(conn) }}`. A single quote in the description breaks the literal boundary, allowing arbitrary SQL injection. The vulnerability affects 16 sites across Domain, Foreign Table, Language, Event Trigger, and View dialogs, plus 10 additional sinks in pgstattuple/pgstatindex stats templates that embed table/index names without proper identifier escaping. The injected SQL executes under the PostgreSQL role already authenticated in pgAdmin; for roles with COPY TO/FROM PROGRAM, OS command execution on the database host becomes possible. A secondary defect in the qtLiteral utility function silently returned unescaped values when the connection argument was falsy, masking eight additional plumbing bugs elsewhere in the codebase.
Business impact
A database administrator or operator using pgAdmin 4 to manage PostgreSQL instances faces a risk that authenticated users—even those restricted to a read-only or limited-access interface—could execute arbitrary SQL or OS commands by crafting malicious object descriptions. This undermines role-based access controls and audit boundaries. Organizations relying on pgAdmin's Query Tool restrictions to prevent direct SQL access or to enforce least privilege will find those controls circumvented. The impact is elevated if pgAdmin is exposed to less-trusted network segments or if multiple teams share a single instance without strict user isolation.
Affected systems
pgAdmin 4 versions 1.0 through 9.15 (inclusive) are vulnerable. The flaw spans every instance where the affected dialog templates are used, so any pgAdmin deployment managing PostgreSQL databases is at risk if users can create or modify the listed object types (Domains and their constraints, Foreign Tables, Languages, Event Triggers, and Views). Environments where pgAdmin is deployed in a high-trust zone may have lower exposure; conversely, multi-tenant or developer-facing pgAdmin instances have higher risk.
Exploitability
Exploitation requires valid pgAdmin authentication, so external, unauthenticated attacks are not possible. The attack is trivial to execute once authenticated: simply enter a single quote followed by SQL into any description field, save the object, and the injected SQL runs when the definition is rendered. No special tools, unusual network conditions, or complex prerequisite configurations are needed. The barrier to exploitation is extremely low—a user familiar with SQL injection can immediately weaponize this. For users with database-role privileges permitting OS command execution (typically superuser), remote command execution on the PostgreSQL host is achievable.
Remediation
Upgrade pgAdmin 4 to version 9.16 or later, which implements three layers of fixes: (1) replaces all vulnerable `'{{ x }}'` interpolations with the `qtLiteral` filter and plumbs the connection context through affected templates, (2) hardens the `qtLiteral` utility to raise an exception instead of silently returning unescaped values when the connection is absent, surfacing any remaining plumbing gaps, and (3) adds regression and lint tests to detect similar patterns in future code changes. The vendor's advisory should be consulted to confirm the exact patch version and any interim workarounds for instances unable to upgrade immediately.
Patch guidance
Update pgAdmin 4 from your current version to 9.16 or later using the official vendor channels (package manager, Docker image, or direct binary download). Verify the update against the vendor's cryptographic signatures and release notes. Test the patched version in a non-production environment before rolling out to production, paying particular attention to any custom templates or third-party extensions that might interact with the description fields. If you cannot patch immediately, restrict pgAdmin access to highly trusted network segments and audit all user activity creating or modifying Domains, Foreign Tables, Languages, Event Triggers, and Views for anomalous or suspicious descriptions containing SQL keywords or apostrophes.
Detection guidance
Monitor pgAdmin logs for CREATE and ALTER statements targeting Domains, Foreign Tables, Languages, Event Triggers, and Views with description fields containing apostrophes, SQL keywords (SELECT, UNION, DROP, etc.), or semicolons—these are hallmarks of injection attempts. Query the PostgreSQL audit log (if enabled via pgAudit or similar) for unexpected SQL execution stemming from pgAdmin sessions, especially commands that did not originate from interactive Query Tool use or object creation workflows. Alert on any OS-level commands executed by the PostgreSQL process (via auditd, osquery, or similar) that correlate with pgAdmin activity, as this signals potential command-execution abuse. Inspect the pgAdmin event_log table for anomalies in object creation/modification patterns.
Why prioritize this
This vulnerability merits immediate attention despite requiring authentication. First, pgAdmin is a widely-deployed administrative tool with access to critical database infrastructure, so compromise carries high consequence. Second, the exploitation barrier is negligible—any authenticated user can execute this attack in seconds with no special knowledge. Third, the affected version range is broad (1.0–9.15), covering most actively-deployed instances. Fourth, the vulnerability enables privilege escalation from a restricted pgAdmin interface to arbitrary SQL and, potentially, OS command execution. Environments where pgAdmin is exposed to developer or operator communities (vs. a single locked-down admin) face the highest immediate risk.
Risk score, explained
The CVSS 3.1 score of 8.8 (HIGH) reflects a network-accessible service requiring low-complexity exploitation by an authenticated user, with high impact across confidentiality, integrity, and availability. The attack vector is network-based (pgAdmin is typically accessed over HTTP/HTTPS), complexity is low (no special conditions or tools), authentication is required (reducing casual attack surface), and the scope is unchanged (the attacker operates within their existing database-role boundary). The high impact score captures the ability to execute arbitrary SQL and, for privileged roles, OS commands, which can lead to data exfiltration, corruption, or destruction. The lack of active in-the-wild exploitation (KEV status: not listed) does not diminish urgency—this is a straightforward injection flaw affecting infrastructure software, and public disclosure will likely trigger rapid weaponization.
Frequently asked questions
Can a user without database-creation privileges exploit this, or is it limited to administrators?
Exploitation requires permission to create or alter the vulnerable object types (Domains, Foreign Tables, Languages, Event Triggers, Views). In many organizations, these permissions are restricted to DBAs or senior operators. However, if a role has any of these privileges—even in a limited schema—the SQL injection is exploitable. The injected SQL runs under that role's permissions, so the attacker gains whatever that role can do (often full database access, and OS command execution for superuser roles).
Does this affect pgAdmin 3, or only pgAdmin 4?
The vulnerability is specific to pgAdmin 4 (versions 1.0–9.15). pgAdmin 3 used a different codebase and is not affected. If your organization runs pgAdmin 3, this issue does not apply; however, pgAdmin 3 reached end-of-life several years ago, and migration to pgAdmin 4 is strongly recommended for security and feature support.
Is there a workaround if we cannot patch immediately?
There is no complete workaround, but risk can be reduced by restricting pgAdmin network access to trusted administrative subnets only, implementing strong authentication (multi-factor authentication, IP whitelisting), and auditing all object creation and modification activity for suspicious descriptions. Additionally, avoid granting COPY TO/FROM PROGRAM and similar OS-execution privileges to any role accessible via pgAdmin except those absolutely requiring it. Monitor audit logs aggressively until patching is possible.
Will our audit logs show evidence of exploitation attempts?
PostgreSQL audit logs (if enabled via pgAudit or similar) will record the SQL statements executed, so malicious queries injected through object descriptions will appear if SQL auditing is enabled. pgAdmin's own logs may also record object creation/modification events. However, if the attacker performs cleanup (dropping tables, rolling back transactions, etc.), audit trail gaps are possible. Proactive monitoring for descriptions containing apostrophes or SQL keywords is more reliable for early detection.
This analysis is provided for informational purposes and does not constitute legal, security, or business advice. The vulnerability details, affected versions, and patch guidance are derived from the official CVE record and vendor disclosures; verify all patch version numbers and remediation steps against the vendor's official advisory before implementation. The absence of a known active exploit does not guarantee attackers will not weaponize this vulnerability; treat the CVSS 8.8 HIGH score and straightforward exploitation as indicators of serious risk requiring prompt attention. Organizations must conduct their own risk assessment based on their pgAdmin deployment architecture, user permissions, and exposure posture. This content is current as of the publication date; consult vendor security advisories for any post-publication updates or interim guidance. Source: NVD (public-domain), retrieved 2026-07-27. Analysis generated by SEC.co (claude-haiku-4-5).
Related vulnerabilities
- CVE-2026-12047LOWHTML Injection in pgAdmin 4 Cloud Deployment Wizard
- CVE-2026-12050MEDIUMpgAdmin 4 SQL Injection in Restore Point Endpoint
- CVE-2016-20062HIGHSQL Injection in Simply Poll 1.4.1 WordPress Plugin - Unauthenticated Data Theft
- CVE-2016-20063HIGHSQL Injection in Single Personal Message 1.0.3 – Credential & Data Theft Risk
- CVE-2016-20065HIGHUnauthenticated SQL Injection in Product Catalog 8 WordPress Plugin
- CVE-2016-20068HIGHUnauthenticated SQL Injection in WordPress Booking Calendar Contact Form 1.0.23
- CVE-2016-20069HIGHUnauthenticated SQL Injection in WordPress Booking Calendar Contact Form 1.0.23
- CVE-2016-20071HIGHCritical SQL Injection in WordPress 404 Redirection Manager Plugin v1.0