VYPR
Moderate severityNVD Advisory· Published Mar 27, 2023· Updated Feb 19, 2025

CVE-2023-0241

CVE-2023-0241

Description

pgAdmin 4 versions prior to v6.19 contains a directory traversal vulnerability. A user of the product may change another user's settings or alter the database.

AI Insight

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

pgAdmin 4 before v6.19 has a directory traversal vulnerability allowing a user to change another user's settings or alter the database.

## Vulnerability pgAdmin 4 versions prior to v6.19 contain a directory traversal vulnerability (CWE-22) [1][3]. The vulnerability arises from insufficient validation of file paths, enabling a user to access files outside the intended storage directory [4].

Exploitation

An authenticated user can exploit this by crafting a malicious file path in the file manager interface. The attack complexity is high, and network access is required [1]. No special privileges beyond a valid user account are needed, but the attacker must be able to interact with the file manager functionality.

Impact

Successful exploitation allows an attacker to change another user's settings or alter the database [1]. This could lead to unauthorized modifications of configuration or data, potentially compromising the integrity and confidentiality of the PostgreSQL environment.

Mitigation

The vulnerability is fixed in pgAdmin 4 v6.19 [1][3]. Users should update to the latest version. No workarounds are mentioned, but applying the patch from the commit [4] is recommended for those unable to upgrade.

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
< 6.196.19

Affected products

13

Patches

1
64d7289c5b38

Ensure that the authenticated users can't access each other's directories and files by providing relative paths. #5734

https://github.com/akshay-joshi/pgadmin4Aditya ToshniwalJan 13, 2023via ghsa
6 files changed · +77 126
  • web/pgadmin/tools/backup/__init__.py+5 36 modified
    @@ -19,8 +19,9 @@
     from flask_security import login_required, current_user
     from pgadmin.misc.bgprocess.processes import BatchProcess, IProcessDesc
     from pgadmin.utils import PgAdminModule, get_storage_directory, html, \
    -    fs_short_path, document_dir, does_utility_exist, get_server
    -from pgadmin.utils.ajax import make_json_response, bad_request
    +    fs_short_path, document_dir, does_utility_exist, get_server, \
    +    filename_with_file_manager_path
    +from pgadmin.utils.ajax import make_json_response, bad_request, unauthorized
     
     from config import PG_DEFAULT_DRIVER
     from pgadmin.model import Server, SharedServer
    @@ -189,40 +190,6 @@ def script():
         )
     
     
    -def filename_with_file_manager_path(_file, create_file=True):
    -    """
    -    Args:
    -        file: File name returned from client file manager
    -        create_file: Set flag to False when file creation doesn't required
    -    Returns:
    -        Filename to use for backup with full path taken from preference
    -    """
    -    # Set file manager directory from preference
    -    storage_dir = get_storage_directory()
    -    if storage_dir:
    -        _file = os.path.join(storage_dir, _file.lstrip('/').lstrip('\\'))
    -    elif not os.path.isabs(_file):
    -        _file = os.path.join(document_dir(), _file)
    -
    -    def short_filepath():
    -        short_path = fs_short_path(_file)
    -        # fs_short_path() function may return empty path on Windows
    -        # if directory doesn't exists. In that case we strip the last path
    -        # component and get the short path.
    -        if os.name == 'nt' and short_path == '':
    -            base_name = os.path.basename(_file)
    -            dir_name = os.path.dirname(_file)
    -            short_path = fs_short_path(dir_name) + '\\' + base_name
    -        return short_path
    -
    -    if create_file:
    -        # Touch the file to get the short path of the file on windows.
    -        with open(_file, 'a'):
    -            return short_filepath()
    -
    -    return short_filepath()
    -
    -
     def _get_args_params_values(data, conn, backup_obj_type, backup_file, server,
                                 manager):
         """
    @@ -367,6 +334,8 @@ def create_backup_objects_job(sid):
         try:
             backup_file = filename_with_file_manager_path(
                 data['file'], (data.get('format', '') != 'directory'))
    +    except PermissionError as e:
    +        return unauthorized(errormsg=str(e))
         except Exception as e:
             return bad_request(errormsg=str(e))
     
    
  • web/pgadmin/tools/import_export/__init__.py+6 30 modified
    @@ -17,8 +17,9 @@
     from flask_security import login_required, current_user
     from pgadmin.misc.bgprocess.processes import BatchProcess, IProcessDesc
     from pgadmin.utils import PgAdminModule, get_storage_directory, html, \
    -    fs_short_path, document_dir, IS_WIN, does_utility_exist
    -from pgadmin.utils.ajax import make_json_response, bad_request
    +    fs_short_path, document_dir, IS_WIN, does_utility_exist, \
    +    filename_with_file_manager_path
    +from pgadmin.utils.ajax import make_json_response, bad_request, unauthorized
     
     from config import PG_DEFAULT_DRIVER
     from pgadmin.model import Server
    @@ -145,33 +146,6 @@ def script():
         )
     
     
    -def filename_with_file_manager_path(_file, _present=False):
    -    """
    -    Args:
    -        file: File name returned from client file manager
    -
    -    Returns:
    -        Filename to use for backup with full path taken from preference
    -    """
    -    # Set file manager directory from preference
    -    storage_dir = get_storage_directory()
    -
    -    if storage_dir:
    -        _file = os.path.join(storage_dir, _file.lstrip('/').lstrip('\\'))
    -    elif not os.path.isabs(_file):
    -        _file = os.path.join(document_dir(), _file)
    -
    -    if not _present:
    -        # Touch the file to get the short path of the file on windows.
    -        with open(_file, 'a'):
    -            return fs_short_path(_file)
    -    else:
    -        if not os.path.isfile(_file):
    -            return None
    -
    -    return fs_short_path(_file)
    -
    -
     def _get_ignored_column_list(data, driver, conn):
         """
         Get list of ignored columns for import/export.
    @@ -297,7 +271,9 @@ def create_import_export_job(sid):
         if 'filename' in data:
             try:
                 _file = filename_with_file_manager_path(
    -                data['filename'], data['is_import'])
    +                data['filename'], not data['is_import'])
    +        except PermissionError as e:
    +            return unauthorized(errormsg=str(e))
             except Exception as e:
                 return bad_request(errormsg=str(e))
     
    
  • web/pgadmin/tools/import_export_servers/__init__.py+11 2 modified
    @@ -20,10 +20,11 @@
     from pgadmin.utils import PgAdminModule
     from pgadmin.utils.ajax import bad_request
     from pgadmin.utils.constants import MIMETYPE_APP_JS
    -from pgadmin.utils.ajax import make_json_response, internal_server_error
    +from pgadmin.utils.ajax import make_json_response, internal_server_error, \
    +    unauthorized
     from pgadmin.model import ServerGroup, Server
     from pgadmin.utils import clear_database_servers, dump_database_servers,\
    -    load_database_servers, validate_json_data
    +    load_database_servers, validate_json_data, filename_with_file_manager_path
     from urllib.parse import unquote
     from pgadmin.utils.paths import get_storage_directory
     
    @@ -118,6 +119,14 @@ def load_servers():
     
         # retrieve storage directory path
         storage_manager_path = get_storage_directory()
    +
    +    try:
    +        file_path = filename_with_file_manager_path(file_path)
    +    except PermissionError as e:
    +        return unauthorized(errormsg=str(e))
    +    except Exception as e:
    +        return bad_request(errormsg=str(e))
    +
         if storage_manager_path:
             # generate full path of file
             file_path = os.path.join(
    
  • web/pgadmin/tools/restore/__init__.py+5 25 modified
    @@ -18,8 +18,10 @@
     from flask_security import login_required, current_user
     from pgadmin.misc.bgprocess.processes import BatchProcess, IProcessDesc
     from pgadmin.utils import PgAdminModule, get_storage_directory, html, \
    -    fs_short_path, document_dir, does_utility_exist, get_server
    -from pgadmin.utils.ajax import make_json_response, bad_request
    +    fs_short_path, document_dir, does_utility_exist, get_server, \
    +    filename_with_file_manager_path
    +from pgadmin.utils.ajax import make_json_response, bad_request, \
    +    internal_server_error
     
     from config import PG_DEFAULT_DRIVER
     from pgadmin.model import Server, SharedServer
    @@ -129,28 +131,6 @@ def script():
         )
     
     
    -def filename_with_file_manager_path(_file):
    -    """
    -    Args:
    -        file: File name returned from client file manager
    -
    -    Returns:
    -        Filename to use for backup with full path taken from preference
    -    """
    -    # Set file manager directory from preference
    -    storage_dir = get_storage_directory()
    -
    -    if storage_dir:
    -        _file = os.path.join(storage_dir, _file.lstrip('/').lstrip('\\'))
    -    elif not os.path.isabs(_file):
    -        _file = os.path.join(document_dir(), _file)
    -
    -    if not os.path.isfile(_file) and not os.path.exists(_file):
    -        return None
    -
    -    return fs_short_path(_file)
    -
    -
     def _get_create_req_data():
         """
         Get data from request for create restore job.
    @@ -164,7 +144,7 @@ def _get_create_req_data():
         try:
             _file = filename_with_file_manager_path(data['file'])
         except Exception as e:
    -        return True, bad_request(errormsg=str(e)), data
    +        return True, internal_server_error(errormsg=str(e)), data, None
     
         if _file is None:
             return True, make_json_response(
    
  • web/pgadmin/utils/__init__.py+46 29 modified
    @@ -260,6 +260,45 @@ def get_complete_file_path(file, validate=True):
             return file
     
     
    +def filename_with_file_manager_path(_file, create_file=False,
    +                                    skip_permission_check=False):
    +    """
    +    Args:
    +        file: File name returned from client file manager
    +        create_file: Set flag to False when file creation doesn't required
    +    Returns:
    +        Filename to use for backup with full path taken from preference
    +    """
    +    # Set file manager directory from preference
    +    storage_dir = get_storage_directory()
    +
    +    from pgadmin.misc.file_manager import Filemanager
    +    Filemanager.check_access_permission(
    +        storage_dir, _file, skip_permission_check)
    +    if storage_dir:
    +        _file = os.path.join(storage_dir, _file.lstrip('/').lstrip('\\'))
    +    elif not os.path.isabs(_file):
    +        _file = os.path.join(document_dir(), _file)
    +
    +    def short_filepath():
    +        short_path = fs_short_path(_file)
    +        # fs_short_path() function may return empty path on Windows
    +        # if directory doesn't exists. In that case we strip the last path
    +        # component and get the short path.
    +        if os.name == 'nt' and short_path == '':
    +            base_name = os.path.basename(_file)
    +            dir_name = os.path.dirname(_file)
    +            short_path = fs_short_path(dir_name) + '\\' + base_name
    +        return short_path
    +
    +    if create_file:
    +        # Touch the file to get the short path of the file on windows.
    +        with open(_file, 'a'):
    +            return short_filepath()
    +
    +    return short_filepath()
    +
    +
     def does_utility_exist(file):
         """
         This function will check the utility file exists on given path.
    @@ -434,27 +473,12 @@ def dump_database_servers(output_file, selected_servers,
     
         object_dict["Servers"] = server_dict
     
    -    # retrieve storage directory path
    -    storage_manager_path = None
    -    if not from_setup:
    -        storage_manager_path = get_storage_directory(user)
    -
    -    # generate full path of file
    -    file_path = unquote(output_file)
    -
    -    from pgadmin.misc.file_manager import Filemanager
         try:
    -        Filemanager.check_access_permission(storage_manager_path, file_path,
    -                                            from_setup)
    +        file_path = filename_with_file_manager_path(
    +            unquote(output_file), skip_permission_check=from_setup)
         except Exception as e:
             return _handle_error(str(e), from_setup)
     
    -    if storage_manager_path is not None:
    -        file_path = os.path.join(
    -            storage_manager_path,
    -            file_path.lstrip('/').lstrip('\\')
    -        )
    -
         # write to file
         file_content = json.dumps(object_dict, indent=4)
         error_str = "Error: {0}"
    @@ -548,19 +572,12 @@ def load_database_servers(input_file, selected_servers,
         if user is None:
             return False, USER_NOT_FOUND % load_user
     
    -    # retrieve storage directory path
    -    storage_manager_path = None
    -    if not from_setup:
    -        storage_manager_path = get_storage_directory(user)
    -
         # generate full path of file
    -    file_path = unquote(input_file)
    -    if storage_manager_path:
    -        # generate full path of file
    -        file_path = os.path.join(
    -            storage_manager_path,
    -            file_path.lstrip('/').lstrip('\\')
    -        )
    +    try:
    +        file_path = filename_with_file_manager_path(
    +            unquote(input_file), skip_permission_check=from_setup)
    +    except Exception as e:
    +        return _handle_error(str(e), from_setup)
     
         try:
             with open(file_path) as f:
    
  • web/yarn.lock+4 4 modified
    @@ -8517,9 +8517,9 @@ react-checkbox-tree@^1.7.2:
         nanoid "^3.0.0"
         prop-types "^15.5.8"
     
    -"react-data-grid@git+https://github.com/EnterpriseDB/react-data-grid.git/#200d2f5e02de694e3e9ffbe177c279bc40240fb8":
    +"react-data-grid@git+https://github.com/pgadmin-org/react-data-grid.git/#200d2f5e02de694e3e9ffbe177c279bc40240fb8":
       version "7.0.0-beta.14"
    -  resolved "git+https://github.com/EnterpriseDB/react-data-grid.git/#200d2f5e02de694e3e9ffbe177c279bc40240fb8"
    +  resolved "git+https://github.com/pgadmin-org/react-data-grid.git/#200d2f5e02de694e3e9ffbe177c279bc40240fb8"
       dependencies:
         clsx "^1.1.1"
     
    @@ -10337,9 +10337,9 @@ watchpack@^2.4.0:
         glob-to-regexp "^0.4.1"
         graceful-fs "^4.1.2"
     
    -"webcabin-docker@git+https://github.com/EnterpriseDB/wcDocker/#3df8aac825ee2892f4d824de273b779cc6dbcad8":
    +"webcabin-docker@git+https://github.com/pgadmin-org/wcdocker/#3df8aac825ee2892f4d824de273b779cc6dbcad8":
       version "2.2.5"
    -  resolved "git+https://github.com/EnterpriseDB/wcDocker/#3df8aac825ee2892f4d824de273b779cc6dbcad8"
    +  resolved "git+https://github.com/pgadmin-org/wcdocker/#3df8aac825ee2892f4d824de273b779cc6dbcad8"
       dependencies:
         "@fortawesome/fontawesome-free" "^5.14.0"
         FileSaver "^0.10.0"
    

Vulnerability mechanics

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

References

6

News mentions

0

No linked articles in our index yet.