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.
| Package | Affected versions | Patched versions |
|---|---|---|
pgadmin4PyPI | < 6.19 | 6.19 |
Affected products
13- pgAdmin/pgAdmin 4description
- ghsa-coords12 versionspkg:pypi/pgadmin4pkg:rpm/opensuse/pgadmin4&distro=openSUSE%20Leap%2015.4pkg:rpm/opensuse/pgadmin4&distro=openSUSE%20Tumbleweedpkg:rpm/suse/pgadmin4&distro=SUSE%20Enterprise%20Storage%207.1pkg:rpm/suse/pgadmin4&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP3-ESPOSpkg:rpm/suse/pgadmin4&distro=SUSE%20Linux%20Enterprise%20High%20Performance%20Computing%2015%20SP3-LTSSpkg:rpm/suse/pgadmin4&distro=SUSE%20Linux%20Enterprise%20Module%20for%20Server%20Applications%2015%20SP4pkg:rpm/suse/pgadmin4&distro=SUSE%20Linux%20Enterprise%20Real%20Time%2015%20SP3pkg:rpm/suse/pgadmin4&distro=SUSE%20Linux%20Enterprise%20Server%2015%20SP3-LTSSpkg:rpm/suse/pgadmin4&distro=SUSE%20Linux%20Enterprise%20Server%20for%20SAP%20Applications%2015%20SP3pkg:rpm/suse/pgadmin4&distro=SUSE%20Manager%20Proxy%204.2pkg:rpm/suse/pgadmin4&distro=SUSE%20Manager%20Server%204.2
< 6.19+ 11 more
- (no CPE)range: < 6.19
- (no CPE)range: < 4.30-150300.3.9.1
- (no CPE)range: < 8.2-1.1
- (no CPE)range: < 4.30-150300.3.9.1
- (no CPE)range: < 4.30-150300.3.9.1
- (no CPE)range: < 4.30-150300.3.9.1
- (no CPE)range: < 4.30-150300.3.9.1
- (no CPE)range: < 4.30-150300.3.9.1
- (no CPE)range: < 4.30-150300.3.9.1
- (no CPE)range: < 4.30-150300.3.9.1
- (no CPE)range: < 4.30-150300.3.9.1
- (no CPE)range: < 4.30-150300.3.9.1
Patches
164d7289c5b38Ensure that the authenticated users can't access each other's directories and files by providing relative paths. #5734
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- github.com/advisories/GHSA-9crj-hpxh-f6qgghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2023-0241ghsaADVISORY
- github.com/akshay-joshi/pgadmin4/commit/64d7289c5b3831137b17bb4c5022ef4f63d2ef42ghsaWEB
- github.com/pgadmin-org/pgadmin4/issues/5734ghsaWEB
- jvn.jp/en/jp/JVN01398015ghsaWEB
- jvn.jp/en/jp/JVN01398015/mitre
News mentions
0No linked articles in our index yet.