VYPR
Moderate severityOSV Advisory· Published Sep 22, 2023· Updated Aug 2, 2024

Pgadmin4: remote code execution by an authenticated user

CVE-2023-5002

Description

A flaw was found in pgAdmin. This issue occurs when the pgAdmin server HTTP API validates the path a user selects to external PostgreSQL utilities such as pg_dump and pg_restore. Versions of pgAdmin prior to 7.6 failed to properly control the server code executed on this API, allowing an authenticated user to run arbitrary commands on the server.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

In pgAdmin versions before 7.6, an authenticated user can execute arbitrary commands via the validate_binary_path API due to insufficient path sanitization.

Vulnerability

Description

A flaw in pgAdmin's HTTP API allows authenticated users to execute arbitrary commands on the server. The vulnerable endpoint, validate_binary_path, is used to verify the path to external PostgreSQL utilities like pg_dump and pg_restore. Prior to version 7.6, the function did not properly sanitize user-supplied paths, directly passing them to subprocess.getoutput without adequate escaping. This enables command injection by crafting a malicious path [1][3].

Exploitation

Prerequisites

An attacker must be an authenticated user of the pgAdmin web interface. They can send a specially crafted request to the validate_binary_path endpoint, which is typically used during server configuration. The vulnerable code path used subprocess.getoutput with the user-controlled full_path variable, allowing arbitrary system commands to be injected. The commit fixing the issue (35f05e4) replaces this with a safer function get_binary_path_versions that validates paths before execution [3].

Impact

Successful exploitation grants the attacker arbitrary command execution on the pgAdmin server. This could lead to full compromise of the application, data theft, service disruption, or further lateral movement within the network. Given that pgAdmin is often deployed with elevated privileges to manage PostgreSQL instances, the impact is critical.

Mitigation

The vulnerability has been addressed in pgAdmin version 7.6. Users should upgrade to this version or later. The official repository commit [3] details the security fix, and updates have been released for affected distributions such as Fedora [4]. No workarounds are provided; upgrading is strongly recommended.

AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
pgadmin4PyPI
< 7.77.7

Affected products

3

Patches

1
35f05e49b363

Fix the security issue of validate bin path to consider and fix more scenarios. #6763

https://github.com/pgadmin-org/pgadmin4Aditya ToshniwalSep 20, 2023via ghsa
2 files changed · +46 50
  • web/pgadmin/misc/__init__.py+7 30 modified
    @@ -13,7 +13,8 @@
     from flask import url_for, render_template, Response, request, current_app
     from flask_babel import gettext
     from flask_security import login_required
    -from pgadmin.utils import PgAdminModule, replace_binary_path
    +from pgadmin.utils import PgAdminModule, replace_binary_path, \
    +    get_binary_path_versions
     from pgadmin.utils.csrf import pgCSRFProtect
     from pgadmin.utils.session import cleanup_session_files
     from pgadmin.misc.themes import get_all_themes
    @@ -254,37 +255,13 @@ def validate_binary_path():
     
         version_str = ''
         if 'utility_path' in data and data['utility_path'] is not None:
    -        # Check if "$DIR" present in binary path
    -        binary_path = replace_binary_path(data['utility_path'])
    -
    -        for utility in UTILITIES_ARRAY:
    -            full_path = os.path.abspath(
    -                os.path.join(binary_path,
    -                             (utility if os.name != 'nt' else
    -                              (utility + '.exe'))))
    -
    -            try:
    -                # if path doesn't exist raise exception
    -                if not os.path.exists(binary_path):
    -                    current_app.logger.warning('Invalid binary path.')
    -                    raise Exception()
    -                # escape double quotes to avoid command injection.
    -                # Get the output of the '--version' command
    -                version_string = \
    -                    subprocess.getoutput(r'"{0}" --version'.format(
    -                        full_path.replace('"', '""')))
    -                # Get the version number by splitting the result string
    -                version_string.split(") ", 1)[1].split('.', 1)[0]
    -            except Exception:
    +        binary_versions = get_binary_path_versions(data['utility_path'])
    +        for utility, version in binary_versions.items():
    +            if version is None:
                     version_str += "<b>" + utility + ":</b> " + \
                                    "not found on the specified binary path.<br/>"
    -                continue
    -
    -            # Replace the name of the utility from the result to avoid
    -            # duplicate name.
    -            result_str = version_string.replace(utility, '')
    -
    -            version_str += "<b>" + utility + ":</b> " + result_str + "<br/>"
    +            else:
    +                version_str += "<b>" + utility + ":</b> " + version + "<br/>"
         else:
             return precondition_required(gettext('Invalid binary path.'))
     
    
  • web/pgadmin/utils/__init__.py+39 20 modified
    @@ -351,35 +351,54 @@ def get_server(sid):
         return server
     
     
    +def get_binary_path_versions(binary_path: str) -> dict:
    +    ret = {}
    +    binary_path = os.path.abspath(
    +        replace_binary_path(binary_path)
    +    )
    +
    +    for utility in UTILITIES_ARRAY:
    +        ret[utility] = None
    +        full_path = os.path.join(binary_path,
    +                                 (utility if os.name != 'nt' else
    +                                  (utility + '.exe')))
    +
    +        try:
    +            # if path doesn't exist raise exception
    +            if not os.path.isdir(binary_path):
    +                current_app.logger.warning('Invalid binary path.')
    +                raise Exception()
    +            # Get the output of the '--version' command
    +            cmd = subprocess.run(
    +                [full_path, '--version'],
    +                shell=False,
    +                capture_output=True,
    +                text=True
    +            )
    +            if cmd.returncode == 0:
    +                ret[utility] = cmd.stdout.split(") ", 1)[1].strip()
    +            else:
    +                raise Exception()
    +        except Exception as _:
    +            continue
    +
    +    return ret
    +
    +
     def set_binary_path(binary_path, bin_paths, server_type,
                         version_number=None, set_as_default=False):
         """
         This function is used to iterate through the utilities and set the
         default binary path.
         """
         path_with_dir = binary_path if "$DIR" in binary_path else None
    +    binary_versions = get_binary_path_versions(binary_path)
     
    -    # Check if "$DIR" present in binary path
    -    binary_path = replace_binary_path(binary_path)
    -
    -    for utility in UTILITIES_ARRAY:
    -        full_path = os.path.abspath(
    -            os.path.join(binary_path, (utility if os.name != 'nt' else
    -                                       (utility + '.exe'))))
    -
    +    for utility, version in binary_versions.items():
    +        version_number = version if version_number is None else version_number
    +        if version_number.find('.'):
    +            version_number = version_number.split('.', 1)[0]
             try:
    -            # if version_number is provided then no need to fetch it.
    -            if version_number is None:
    -                # Get the output of the '--version' command
    -                version_string = \
    -                    subprocess.getoutput('"{0}" --version'.format(full_path))
    -
    -                # Get the version number by splitting the result string
    -                version_number = \
    -                    version_string.split(") ", 1)[1].split('.', 1)[0]
    -            elif version_number.find('.'):
    -                version_number = version_number.split('.', 1)[0]
    -
                 # Get the paths array based on server type
                 if 'pg_bin_paths' in bin_paths or 'as_bin_paths' in bin_paths:
                     paths_array = bin_paths['pg_bin_paths']
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

9

News mentions

0

No linked articles in our index yet.