我们的 AI 引擎 Neutron 在加州大学伯克利分校的 CyberGym 基准测试中取得了 97.1% 的成绩。 了解更多

深度解析

深度解析

pgAdmin 4 Import/Export RCE,已在 9.19 修复 (CVE-2026-116241)

pgAdmin 4 9.19 之前版本允许任何 Import/Export 用户在主机上运行 psql 的 TO PROGRAM 命令,登录锁定绕过让管理员密码可被无限猜解;已在 9.19 修复。

摘要

  • 当构造的查询导出绕过 pgAdmin 4 Import/Export Data 工具的括号检查时,该工具会在 pgAdmin 主机上执行攻击者的操作系统命令:CVE-2026-116241,PostgreSQL CNA 给出 CVSS v3.1 9.9 Critical。该工具的权限默认授予非管理员。
  • 同一版本还修复了第二个严重漏洞 CVE-2026-116238:登录失败限制的四种绕过让未认证攻击者能在服务器模式下无限猜解管理员密码。Xint by Theori 的 Yonghwa Lee 报告了该注入。
  • 2026 年 10 月 30 日公布的这批 14 个缺陷全部在 2026 年 10 月 29 日发布的 pgAdmin 4 9.19 中修复。从该工具最早版本开始的所有旧版本都受影响。
  • 截至 2026 年 10 月 31 日,没有公开漏洞利用或被利用的报告,两个严重漏洞都不在 CISA 的 KEV(已知被利用漏洞)目录中。请立即升级到 9.19;其中的服务器模式锁定变更需要规划,列在“如何修复?”中。

我受影响吗?

命令注入 CVE-2026-116241 需要同时满足:

  • 您运行的 pgAdmin 4 为 9.19 之前的任意版本,任意部署形态:服务器模式、桌面模式或容器。
  • 一个您不完全信任的用户能在某个服务器连接上使用 Import/Export Data 工具。tools_import_export_data 权限默认授予非管理员。
  • 该用户在目标服务器上的数据库角色能对自己执行 SET standard_conforming_strings = off,普通角色即可做到。

这段代码在两种模式下都存在,但远程攻击者的路径是服务器模式,即其他人持有 pgAdmin 账号的场合。桌面模式只把它暴露给已经能操作该桌面的人。

登录锁定绕过 CVE-2026-116238 需要同时满足:

  • 您在服务器模式下运行 pgAdmin 4,版本为 9.19 之前的任意版本。桌面模式不受影响。
  • 实例可被您不完全信任的任何人访问。
  • 存在内部账号,这是默认状态:初始管理员就是一个,且 MAX_LOGIN_ATTEMPTS 默认为 3。
分支 受影响版本 修复版本
pgAdmin 4 9.19 之前的所有版本 9.19 (2026-10-29)

这批漏洞规模不小:PostgreSQL 的 CVE 编号机构(CNA)为此发布了 14 条记录,两个严重、十个高危、一个中危、一个低危,全部在同一版本中修复。

pgAdmin 4 9.19 安全批次按严重程度分布:严重 2、高危 10、中危 1、低危 1
图 1:一次发布修复了 14 个 CVE。来源:PostgreSQL CNA 记录,2026 年 10 月 31 日查询。

请检查您运行的版本。容器在启动时记录其版本:

docker logs pgadmin 2>&1 | grep -m1 "Starting pgAdmin"

pip 安装:

pip show pgadmin4 | head -2

在界面中,帮助菜单的“关于”框显示版本。Docker 镜像的标签也携带版本,但 latest 之类的浮动标签需要本节中的版本检查之一。

如何修复?

将每个部署升级到 9.19,这是唯一的修复版本:

  1. 备份 pgAdmin 的配置数据库,其中保存着用户、角色和服务器定义。
  2. 升级或重启前,请先阅读下述四项服务器模式变更并配置所需的允许列表。密码命令迁移可能永久清除不匹配的命令。
  3. 升级部署。pip 方式:
pip install --upgrade pgadmin4==9.19
  1. 使用 Docker Compose 时,先在 Compose 文件中将 pgadmin 服务的镜像设为 dpage/pgadmin4:9.19,再拉取镜像并重新创建该服务:
docker compose pull pgadmin
docker compose up -d pgadmin

9.19 的服务器模式锁定了四件事,每一项都有需要规划的代价:

  • 自由文本的 Password Exec Commands 不再运行。ENABLE_SERVER_PASS_EXEC_CMD 已弃用并被忽略。管理员现在按名称在 SERVER_PASSEXEC_COMMANDS 中列出允许的命令;升级后首次启动时,与所列条目完全一致的现有命令会转换为该名称,其余命令全部清除,且无法撤销。 如果 SERVER_PASSEXEC_COMMANDS 为空,则推迟转换,并在每次启动时记录警告。
  • 连接服务受到限制。SERVER_MODE_ALLOWED_SERVICES 默认为空,因此配置了连接服务的服务器在管理员将其列入之前停止连接。列出某项服务会将其凭据分享给所有 pgAdmin 用户。
  • SSH 隧道不再读取服务账号自身的 SSH 身份。服务器模式下不再使用其 ~/.ssh 密钥、其配置文件及其代理,因此依赖它们的隧道必须在用户存储中配置身份文件,或在服务器上配置密码。
  • 在 Python 3.10 及更高版本上,pgAdmin 要求 Flask-Security-Too 5.8.3 或更高版本,它修复了开放重定向 GHSA-ccgp-pv8r-95wm。这对把 wheel 装进现有环境的人很重要。

对于暂时无法升级的部署,一个变通方案可关闭 CVE-2026-116241 的注入路径:

  1. 从您不信任其执行主机命令的每个角色收回 Import/Export Data 权限。在服务器模式下,管理员在界面的角色管理中从角色收回 tools_import_export_data 权限。
  2. 该变通方案只覆盖 CVE-2026-116241。它对登录锁定绕过 CVE-2026-116238 和其他十二个缺陷毫无作用,而且会让需要导出工具的用户失去它。

无法升级时对登录锁定绕过的处理是限制谁能访问 pgAdmin 登录页:VPN、反向代理的 IP 白名单,或在 pgAdmin 前面完全接管的认证源。这些都不改变那四种绕过;每一种都移除它们所需的未认证可达性。

根本原因是什么?

Import/Export Data 工具把用户的 SQL 查询包进一条 psql \copy (...) TO 命令,并用一个手写的括号平衡检查来验证,而其引用规则与 psql 的不同。 路由 POST /import_export/job/<sid> 只由普通的 tools_import_export_data 权限把关,并将渲染后的 \copy 行传给 psql。

检查函数 _is_query_parens_balanced() 位于 web/pgadmin/tools/import_export/__init__.py,它遍历查询以确保没有内容能提前关闭包裹括号。9.19 之前,它对双引号标识符的处理是:

        # Double-quoted identifier: skip to closing quote
        # (handles "" escape sequences — matches strtokx behavior)
        if ch == '"':
            i += 1
            while i < length:
                if query[i] == '"':

注释声称与 psql 的分词器 strtokx 一致,但两者在反斜杠上不一致。strtokx 从连接的 standard_conforming_strings 设置取转义字符,并将其应用于打开该记号的引号。该设置关闭时,双引号前的反斜杠对 psql 是转义的引号,而 pgAdmin 的检查把它当作关闭标识符。修复提交的消息陈述了后果:在形如 SELECT "a\"b"c) TO PROGRAM 'cmd' x" 的查询导出中,对检查隐藏的闭括号结束了 \copy (...) 包裹,随后数据库服务器拒绝该语句之前,TO PROGRAM 子句已经通过 popen() 在 pgAdmin 主机上运行。

登录锁定绕过 CVE-2026-116238 是一个设计中的四个独立漏洞。pgAdmin 统计登录失败并在 MAX_LOGIN_ATTEMPTS 次后锁定内部账号,但 Flask-Security-Too 在 pgAdmin 的视图旁边注册了自己的视图,向这些视图提交的内容完全绕过了 pgAdmin 的计数和检查:/login 不计入失败次数即登录,/verify 无限制地校验已登录用户的密码,/change 无需 pgAdmin 权限即修改密码,/reset 为非内部或已锁定的账号应用重置。以其他大小写输入的用户名既不计入也不检查锁定。在同时配置了 LDAP 或 webserver 认证时,错误的内部密码根本不计入。

如何被利用?

没有公开的概念验证代码,也没有被利用的报告。步骤来自 CVE 记录和修复提交:

  1. 攻击者需要一个持有 Import/Export Data 权限的 pgAdmin 账号(非管理员默认持有),以及目标数据库服务器上的一个普通角色。
  2. 在该连接上,攻击者对自己执行 SET standard_conforming_strings = off,普通角色即可执行。
  3. 他提交一个基于查询的导出,其查询在一个包含反斜杠的双引号标识符中隐藏一个闭括号,后面跟着 TO PROGRAM 子句。CVE 记录中的示例形态是 SELECT "a\"b"c) TO PROGRAM 'cmd' x"。
  4. pgAdmin 的平衡检查以与 psql 不同的方式读取反斜杠,认为括号平衡并接受该任务。
  5. 渲染出的 \copy (...) TO PROGRAM 'cmd' 在 psql 中运行。因为 psql 应用连接的转义规则,隐藏的括号关闭了包裹。接着 psql 在 pgAdmin 主机上以运行 pgAdmin 的操作系统账号身份,通过 popen() 执行 TO PROGRAM 命令,这一切发生在数据库服务器拒绝该语句之前。

登录锁定绕过的步骤更简单:

  1. 未认证攻击者访问服务器模式部署的 pgAdmin 登录页。
  2. 他通过未计入路径之一对内部管理员账号提交密码猜解。这些路径是 Flask-Security 自己的 /login 视图、以其他大小写输入的用户名,以及在同时配置了 LDAP 或 webserver 认证时的错误内部密码。
  3. 锁定从不触发,因此猜解可以无限继续,直到密码被攻破。

攻击如何流动?

一个普通的 pgAdmin 账号通过让 pgAdmin 的引用检查与 psql 的分词器失去同步,把查询导出变成操作系统命令。失败的检查是括号检查对反斜杠的解读。

flowchart LR A["持有 Import/Export 权限和
普通角色的攻击者"] --> B["在连接上执行
SET standard_conforming_strings=off"] B --> C["导出查询在反斜杠标识符中
隐藏 ) 并附加 TO PROGRAM"] C --> D["检查失败:括号检查对反斜杠
的读取与 psql 不同"] D --> E["\\copy (...) TO PROGRAM
经 popen 在 psql 中运行"] E --> F["任意命令在 pgAdmin
主机上执行"]

攻击者能做什么?

注入 CVE-2026-116241 在运行 pgAdmin 的主机上以运行 pgAdmin 的账号身份执行任意操作系统命令。在官方容器中,该账号通常是非 root 的 pgadmin 用户(UID 5050);在 pip 或软件包安装中,它是运行 Web 服务器的人。该命令在数据库服务器拒绝语句之前运行,因此攻击不需要数据库上的任何超出普通角色的权限。PostgreSQL CNA 对其评分为 CVSS v3.1 9.9 Critical,CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:C/C:H/I:H/A:H,CVSS v4.0 为 9.4 Critical,CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:H/VA:H/SC:H/SI:H/SA:H。

登录锁定绕过 CVE-2026-116238 让未认证攻击者在默认配置下对任何内部账号(包括管理员)进行无限次的密码猜解。同一 CVE 记录了在账号共享电子邮件地址时,密码重置可能被发送到或应用到来自其他认证源的账号。其评分为 CVSS v3.1 8.1 High,CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H,CVSS v4.0 为 9.2 Critical,CVSS:4.0/AV:N/AC:H/AT:P/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N。

截至 2026 年 10 月 31 日,两个缺陷都没有公开利用、被利用报告或 KEV 条目。该注入是同一家族的第三轮。CVE-2025-13780 在 9.10 及更早版本中通过 PLAIN 格式转储恢复执行命令。CVE-2026-17566 于 2026 年 7 月在 9.17 中修复,关闭了同一导出检查对单引号字符串的缺口。这一个通过双引号标识符重新打开它。

检查绕过家族的时间线:2025 年 12 月的 CVE-2025-13780,2026 年 7 月 CVE-2026-17566 的 9.17 修复,10 月 29 日的 9.19 修复,10 月 30 日的 14 条 CVE 记录
图 2:同一检查绕过在十个月内出现第三轮。来源:CVE 记录与 pgAdmin 发布说明,2026 年 10 月 31 日查询。

为什么修复有效?

提交 9be31a019f78“Reject a backslash in a double-quoted identifier in the Import/Export Data query export”把检查改为拒绝歧义情形,而不是猜测一种解读:

-        # Double-quoted identifier: skip to closing quote
-        # (handles "" escape sequences — matches strtokx behavior)
+        # Double-quoted identifier: skip to closing quote.
+        # Handles the "" escape sequence to match psql's strtokx.
+        # A bare backslash is rejected outright for the same reason
+        # as in a single-quoted string (see docstring): strtokx
+        # applies its escape character to whichever quote opened the
+        # token, so under standard_conforming_strings=off a \" is an
+        # escaped quote to psql but a closing quote to us.
         if ch == '"':
             i += 1
             while i < length:
-                if query[i] == '"':
+                if query[i] == '\\':
+                    return False  # ambiguous escape: reject
+                elif query[i] == '"':
                     if i + 1 < length and query[i + 1] == '"':
                         i += 2  # escaped quote
                     else:

该修复还拒绝导出或导入中模式名、表名和列名里的反斜杠或空字节。这些名称通过 qtIdent() 到达同一条 \copy 命令。提交自带的回归测试固定了失去同步的两个方向,各自携带现在必须被拒绝的 TO PROGRAM 'echo pwned' 载荷:七月修复的单引号情形,和这一次的双引号情形。

登录锁定绕过通过四项变更修复:

  • 向 Flask-Security 自身视图的提交以 HTTP 405 拒绝
  • 每次内部登录失败都被计入,无论用户名大小写如何、无论配置了哪些其他认证源
  • 提交密码的登录不再回落到不检查密码的源
  • 只锁定内部认证,被锁定的内部账号不再阻塞其他源

注入的修复对该检查是完整的。\copy 命令可能包含的每个被引号包裹的记号,单引号字符串和双引号标识符一样,现在都直接拒绝反斜杠。参数名由 qtIdent() 中的反斜杠与空字节拒绝覆盖。锁定修复关闭了 CVE 记录描述的全部四条绕过路径。

如何检查是否已被入侵?

对于注入,没有公开的入侵指标。请检查部署保存的 Import/Export 任务记录:pgAdmin 界面的 Processes 面板会列出它们,其历史保存在 pgAdmin 的配置数据库中。查找查询中包含 TO PROGRAM 的导出:

# The Processes panel or the job history in the pgAdmin SQLite database.
sqlite3 -readonly /var/lib/pgadmin/pgadmin4.db \
  "SELECT pid, start_time, command, arguments FROM process WHERE arguments LIKE '%TO PROGRAM%';"

命中意味着一个任务曾试图通过 psql 运行程序,这是线索而非证据,因为合法导出可能在注释中提到 TO PROGRAM。然后检查主机上 popen() 命令可能留下的东西:陌生进程、cron 条目和 pgAdmin 用户主目录下的文件。

会话级 SET standard_conforming_strings 不会持久保存为数据库配置,但语句日志或审计日志可能记录它。请检查保留的日志;没有任务历史并不能排除攻击尝试。

对于登录锁定绕过,在 pgAdmin 日志中查找从未触发锁定的大量登录失败,这正是绕过自身的签名。截至 2026 年 10 月 31 日,没有任何地方报告受害者、指标或攻击活动。

时间线

  • 2026-07-27:pgAdmin 提交 1496fabe,即 CVE-2026-17566 的修复,关闭同一检查中单引号字符串的反斜杠缺口。
  • 2026-07-31:pgAdmin 9.17 携带该修复发布,CVE-2026-17566 记录同日公开。
  • 2026-10-23:pgAdmin 提交 9be31a019f78,即 CVE-2026-116241 的双引号标识符修复,由 hiteshjambhale 与 Dave Page 编写,Xint by Theori 的 Yonghwa Lee 报告。
  • 2026-10-25:pgAdmin 提交 CVE-2026-116238 登录锁定绕过的修复,包括 58c531d83117。
  • 2026-10-29:pgAdmin 4 9.19 发布,UTC 时间 15:36 上传至 PyPI,发布说明覆盖全部 14 项安全修复。
  • 2026-10-30:PostgreSQL CNA 于 UTC 19:55 发布这批 14 条 CVE 记录。

常见问题

9.18 能保护我吗?

不能。9.18 携带 CVE-2026-17566 的七月修复,关闭了单引号字符串的反斜杠情形,但 CVE-2026-116241 的双引号标识符情形击穿了那个修复。CVE-2026-116238 的登录锁定绕过在 9.18 中同样存在。只有 9.19 同时修复两者。

桌面模式受影响吗?

注入的代码在两种模式下都运行,但远程利用需要服务器模式,即其他人持有账号并能访问该工具的场合。桌面模式只把同一缺陷暴露给已经能操作该桌面的人。登录锁定绕过仅限服务器模式;桌面模式不受影响。

把服务器模式部署升级到 9.19 会破坏什么?

服务器模式改变了四件事,全部列在“如何修复?”中。规划代价最高的两项是密码命令允许列表(它在首次启动时转换或清除现有命令)和 SERVER_MODE_ALLOWED_SERVICES(每个配置了连接服务的服务器在重新连接之前都需要它)。

其他十二个 CVE 也在 9.19 中修复了吗?

是的。全部 14 条记录都写着“before 9.19”,9.19 发布说明为每一条都带有修复条目,从 CVE-2026-116240 的文件管理器路径竞态到 CVE-2026-116242 至 116247 的服务账号凭据使用。一次升级修复整批。

谁发现了这些缺陷?

Xint by Theori 的 Yonghwa Lee 报告了注入 CVE-2026-116241。Blacksight LLC 的 Yves Soete 报告了 CVE-2026-116239 的跨源登录混淆,Harsh Singh 报告了 CVE-2026-116243 的二进制路径逃逸。登录锁定绕过 CVE-2026-116238 的记录中未列出外部报告者,将修复开发归功于 EnterpriseDB 的 Ashesh Vashi。

用 Ostorlab 盘点暴露的 pgAdmin 服务器

Ostorlab 的攻击面发现会盘点您暴露在互联网上的服务器与服务。它能显示哪些部署运行着暴露在网络上的 pgAdmin 端点,让您知道先把哪些升级到 9.19、把哪些收回 VPN 之后。

参考文献

  • CVE-2026-116241 记录,由 PostgreSQL CNA 发布,CVSS v3.1 9.9 和 CVSS v4.0 9.4,感谢 Yonghwa Lee:https://www.cve.org/CVERecord?id=CVE-2026-116241(2026 年 10 月 31 日查询)
  • CVE-2026-116238 记录,四个登录锁定绕过,CVSS v3.1 8.1 和 CVSS v4.0 9.2:https://www.cve.org/CVERecord?id=CVE-2026-116238(2026 年 10 月 31 日查询)
  • pgAdmin 4 9.19 发布说明,日期 2026 年 10 月 29 日,包含工单 #10663、#10676、#10674 及其余批次的修复条目和四项服务器模式变更:https://www.pgadmin.org/docs/pgadmin4/9.19/release_notes_9_19.html(2026 年 10 月 31 日查询)
  • CVE-2026-116241 的修复提交 9be31a019f78,其消息记录了 strtokx 失去同步和示例查询,由 Xint by Theori 的 Yonghwa Lee 报告:https://github.com/pgadmin-org/pgadmin4/commit/9be31a019f8780647fb689c458408c8803263c812(2026 年 10 月 31 日查询)
  • CVE-2026-116238 的修复提交 58c531d83117,“Act only on the internal account when accounts from other sources share its email or username”:https://github.com/pgadmin-org/pgadmin4/commit/58c531d831179dc9b0a5bf873ccb80c71c8d387a(2026 年 10 月 31 日查询)
  • CVE-2026-17566 记录,同一检查单引号情形的七月修复,附修复提交 1496fabe:https://www.cve.org/CVERecord?id=CVE-2026-17566(2026 年 10 月 31 日查询)
  • CVE-2025-13780 记录,该家族的第一轮,9.10 及更早版本中通过 PLAIN 格式转储恢复执行命令:https://www.cve.org/CVERecord?id=CVE-2025-13780(2026 年 10 月 31 日查询)
  • PyPI 上的 pgAdmin 4 9.19,于 2026 年 10 月 29 日 UTC 15:36 上传:https://pypi.org/project/pgadmin4/(2026 年 10 月 31 日查询)
  • Docker 镜像 dpage/pgadmin4,服务器模式的标准部署渠道,453,158,170 次拉取:https://hub.docker.com/r/dpage/pgadmin4(2026 年 10 月 31 日查询)
  • CISA Known Exploited Vulnerabilities 目录 JSON 源,检索两个严重漏洞:https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json(2026 年 10 月 31 日查询)

  • 官方容器文档:默认非 root 用户与部署配置: https://www.pgadmin.org/docs/pgadmin4/9.19/container_deployment.html