导语:PostgreSQL 类型混淆漏洞 CVE-2026-120141 是一次典型的“类型系统边界失守”事件——物化集合返回函数(materialized set-returning functions)在返回动态 RECORD 类型时缺乏严格的运行时类型一致性校验,使得一个仅有普通数据库账号权限的用户,能够以数据库服务进程的操作系统用户身份执行任意代码。CVSS 8.8 的高危评级意味着这不是一次可以排期到下个迭代的普通缺陷,而是一场必须立即响应的生产安全事件。
CVE-2026-120141 的 CVSS 评分为 8.8(High)。攻击者无需超级用户权限,仅凭一个可执行 SQL 的普通数据库用户,即可通过返回动态类型不一致的 RECORD 行,触发类型混淆并最终以 postgres 进程的 OS 用户身份执行任意代码。这意味着“数据库账号隔离”这一最基础的信任边界在受影响版本中已实质失效。
一、PostgreSQL 类型混淆漏洞的攻击面全景与影响矩阵
在展开根因分析之前,我们必须先厘清这次 PostgreSQL 类型混淆漏洞的精确影响边界。很多团队在收到 CVE 通告时的第一反应是“我们没用到 json_populate_recordset,应该没事”——这是极其危险的误判。通告明确指出 json_populate_recordset() 只是受影响函数之一,真正的缺陷位于所有物化集合返回函数的通用执行路径上。
1.1 受影响版本矩阵与升级目标
官方已在多个大版本分支中同步修复,修复版本号如下表所示。请特别注意:PostgreSQL 的次版本升级是二进制兼容的,无需 dump/restore,这是本次应急响应中成本最低、收益最高的动作。
| 大版本分支 | 受影响版本 | 修复版本 | 升级方式 | 风险等级 |
|---|---|---|---|---|
| PostgreSQL 18.x | < 18.7 | 18.7 | 次版本原地升级 | 低风险 |
| PostgreSQL 17.x | < 17.12 | 17.12 | 次版本原地升级 | 低风险 |
| PostgreSQL 16.x | < 16.16 | 16.16 | 次版本原地升级 | 低风险 |
| PostgreSQL 15.x | < 15.20 | 15.20 | 次版本原地升级 | 低风险 |
| PostgreSQL 14.x | < 14.25 | 14.25 | 次版本原地升级 | 低风险 |
| PostgreSQL ≤ 13.x | 全部(EOL) | 无官方修复 | 必须跨大版本迁移 | 高风险 |
仍运行 PostgreSQL 13 及更早版本的实例已处于 EOL 状态,官方不会为该分支提供补丁。这类实例在本漏洞面前等同于“永久暴露”。如果你的生产环境仍停留在 13,请将本次事件作为强制迁移的决策依据,而不是继续打补丁续命。
1.2 攻击者视角的利用前置条件
从威胁建模角度看,本漏洞的利用门槛远低于多数人的直觉。攻击者只需要满足以下条件即可发起利用:
- 拥有一个可登录的数据库账号,且具备执行 SQL 的基本权限(如
CONNECT+USAGEon schema); - 能够调用或间接触发物化集合返回函数(包括通过视图、函数封装、ORM 生成语句等间接路径);
- 能够构造返回动态 RECORD 类型不一致的行数据。
换句话说,在多租户 SaaS、数据分析平台、以及任何向外部提供“可执行 SQL 能力”的产品中,这个漏洞的暴露面几乎覆盖全部租户。这也是它被评为 High 而非 Medium 的核心原因。
企业级架构规范
flowchart TD
subgraph 接入层 ["表现层与接入网关 (Client & Gateway Tier)"]
A["普通数据库用户 (低权限账号)"] --> B["SQL 请求入口 (连接池 / 应用层)"]
end
subgraph 规则层 ["解析与类型推导层 (Parser & Type Inference Tier)"]
B --> C["查询解析与函数解析 (SRF 识别)"]
C --> D["RECORD 动态类型推导 (Type Inference)"]
end
subgraph 领域层 ["核心执行与物化层 (Executor & Materialization Tier)"]
D --> E["集合返回函数物化 (Materialize SRF)"]
E --> F{"动态 RECORD 类型一致性校验"}
F -->|"校验缺失 (受影响版本)"| G["类型混淆 (Type Confusion)"]
F -->|"严格校验 (修复版本)"| H["安全拒绝并报错"]
end
subgraph 存储层 ["基础设施与 OS 边界 (Infrastructure Tier)"]
G --> I["越界内存访问 / 类型指针误用"]
I --> J["以 postgres OS 用户执行任意代码"]
H --> K[("正常返回结果集")]
end
二、根因拆解:物化 SRF 与动态 RECORD 类型的信任失守
2.1 什么是“物化集合返回函数”
要理解这个 PostgreSQL 类型混淆漏洞,必须先理解 PostgreSQL 中集合返回函数(Set-Returning Function, SRF)的执行模型。当一个 SRF 出现在查询的 FROM 子句中时,优化器需要将其“物化”(materialize)——即把函数逐行产出的结果缓存为一个元组表,再参与后续的连接、过滤与投影。
问题恰恰出在“物化”这个动作上。SRF 的返回类型在函数定义时声明为 SETOF record 时,其每一行的实际列结构是运行时动态决定的。执行器在物化阶段会依据首行或调用方提供的列定义描述符(TupleDesc)来解读后续所有行。如果函数在不同行之间返回了结构不一致的 RECORD,而执行器又未做严格校验,就会把 A 类型的内存布局当作 B 类型来解读——这就是类型混淆(Type Confusion)的本质。
类型混淆类漏洞的危险性在于它绕过了所有基于“类型”构建的安全假设。数据库的权限模型、内存安全边界、乃至 C 语言层的指针语义,都建立在“一个值就是它声明的类型”这一契约之上。一旦契约被打破,后续所有防御都形同虚设。
2.2 json_populate_recordset 为何成为典型攻击载体
json_populate_recordset() 之所以被通告点名,是因为它天然满足攻击所需的全部条件:它接受一个任意的 JSON 数组,并将其展开为 SETOF record;调用方通过 AS (col type, ...) 指定列定义;而 JSON 数据的结构完全由攻击者控制。攻击者可以精心构造 JSON 数组,使得不同元素在“列数、列类型、嵌套结构”上产生差异,诱导物化阶段产生类型不一致的 RECORD 行。
-- 攻击者控制 JSON 内容,列定义由调用方声明
-- 当不同行的实际结构与声明的 TupleDesc 不一致时,
-- 受影响版本会在物化阶段产生类型混淆
SELECT *
FROM json_populate_recordset(
NULL::record,
'[{"a": 1, "b": "x"},
{"a": [1,2,3], "b": {"nested": true}}]'
) AS t(a int, b text);
需要强调的是,上述语句在修复版本中会被安全地拒绝并抛出类型不匹配错误;而在受影响版本中,执行器可能将数组或对象的内存表示误读为标量,进而造成越界访问或指针误用,最终被攻击者转化为可控的代码执行原语。
2.3 从类型混淆到 OS 级 RCE 的最后一公里
类型混淆本身只是“内存误读”,要升级为任意代码执行,攻击者还需要完成两步:其一,通过精心构造的 RECORD 结构实现可控的内存布局操纵(如伪造 varlena 头部、篡改函数指针或类型转换跳转表);其二,借助 PostgreSQL 进程内已有的可执行原语(如表达式求值、类型输入函数调用)完成控制流劫持。由于 PostgreSQL 后端进程以独立的 OS 用户(通常是 postgres)运行,一旦控制流被劫持,攻击者便获得了该 OS 用户权限下的任意命令执行能力——这正是 CVSS 8.8 中“High Integrity/Confidentiality Impact”的来源。
不要误以为“数据库只在内网”就能免疫。大量数据泄露事件的起点都是“某个低权限应用账号被攻陷”。本漏洞将“应用账号被攻陷”的后果从“数据泄露”直接升级为“数据库服务器沦陷”,横向移动的跳板价值极高。
三、应急响应:PostgreSQL 类型混淆漏洞的 24 小时处置清单
3.1 第一步:资产盘点与版本确认
应急响应的第一原则是“先看清战场”。请在所有 PostgreSQL 实例上执行版本查询,并与修复版本矩阵比对。注意要覆盖主库、只读副本、逻辑复制订阅端以及所有测试/预发环境——攻击者往往从防护最薄弱的环境切入。
-- 确认精确版本号
SELECT version();
-- 结构化提取,便于批量比对
SELECT current_setting('server_version_num')::int AS version_num,
current_setting('server_version') AS version_str;
3.2 第二步:优先升级,而非打补丁
PostgreSQL 官方对次版本的安全修复采用“累积发布”策略,直接升级到对应分支的最新次版本即可获得修复,无需单独打补丁。以下为 Debian/Ubuntu 与 RHEL 系发行版的升级命令示例。升级前请务必确认已配置好备份与回滚预案。
点击展开:各发行版 PostgreSQL 升级执行命令与回滚预案
# Debian / Ubuntu (PGDG 官方源) sudo apt update sudo apt install --only-upgrade postgresql-16 sudo systemctl restart postgresql # RHEL / Rocky / AlmaLinux (PGDG 官方源) sudo dnf update postgresql16-server postgresql16-libs sudo systemctl restart postgresql # 升级后验证 psql -c "SELECT version();" # 回滚预案:保留旧版本二进制包,必要时降级 # sudo apt install postgresql-16= # 注意:次版本降级通常安全,但请先在预发环境验证
3.3 第三步:无法立即升级时的临时缓解
对于因业务窗口限制暂时无法升级的实例,可采取以下纵深防御措施降低暴露面。请注意:这些措施是缓解而非修复,不能替代升级。
- 收敛函数执行权限:撤销普通业务账号对
json_populate_recordset等高风险 SRF 的 EXECUTE 权限,仅授予确需使用的角色。 - 网络层隔离:通过
pg_hba.conf严格限制可连接来源 IP,禁止数据库端口对非应用网段暴露。 - 账号最小权限:审计所有非超级用户账号,回收不必要的
CREATE、EXECUTE权限。 - 行为监控:开启
log_statement = 'all'(临时)或通过 pgAudit 记录对高风险函数的调用,建立异常检测规则。
-- 撤销 PUBLIC 对高风险 SRF 的默认执行权限
REVOKE EXECUTE ON FUNCTION json_populate_recordset(anyelement, json)
FROM PUBLIC;
-- 仅授权给明确需要的业务角色
GRANT EXECUTE ON FUNCTION json_populate_recordset(anyelement, json)
TO app_trusted_role;
-- 审计当前拥有 EXECUTE 权限的角色
SELECT r.rolname, p.proname
FROM pg_proc p
JOIN pg_roles r ON r.oid = ANY(p.proacl::regrole[])
WHERE p.proname LIKE '%populate_record%';
长期来看,应将“数据库账号权限最小化”纳入架构基线:应用账号只应拥有 DML 权限,DDL 与高风险函数执行权限应通过独立的迁移账号或受控通道完成。本次 PostgreSQL 类型混淆漏洞再次证明,权限边界就是安全边界。
四、修复后的验证与长期加固
4.1 验证修复是否生效
升级完成后,必须通过回归验证确认修复生效。最直接的方式是构造一个类型不一致的 RECORD 场景,确认数据库返回明确的类型错误而非静默执行。
-- 修复版本应抛出类型不匹配错误(如:cannot cast type ...)
-- 受影响版本可能静默执行或产生异常内存行为
SELECT *
FROM json_populate_recordset(
NULL::record,
'[{"a": 1}, {"a": [1,2,3]}]'
) AS t(a int);
4.2 建立数据库安全基线与持续监控
单次应急响应解决的是“这一次”,而架构师要解决的是“下一次”。建议将以下动作固化为常态化的数据库安全基线:
| 加固维度 | 传统做法 | 现代加固方案 | 架构收益 |
|---|---|---|---|
| 版本管理 | 年度大版本升级 | 订阅 PGDG 安全通告,次版本 7 日内跟进 | 漏洞窗口缩短 90% |
| 权限模型 | 应用共用超级用户 | 按角色拆分 DML/DDL/函数执行权限 | 攻击面收敛 |
| 审计能力 | 仅开启慢查询日志 | pgAudit 全量审计 + 异常函数调用告警 | 可检测可追溯 |
| 网络暴露 | 数据库端口对内网开放 | pg_hba.conf 白名单 + 零信任网关 | 横向移动阻断 |
若你的实例在升级前已对外提供多租户 SQL 执行能力,请将本次事件按“可能已失陷”处理:立即轮换数据库账号口令、审查 pg_stat_activity 历史与 OS 层异常进程、检查 postgres 用户家目录与 /tmp 下的可疑文件。类型混淆漏洞的利用可能不留明显 SQL 日志痕迹。
五、架构师视角的复盘与启示
CVE-2026-120141 给所有数据库使用者上了一堂昂贵的课:类型系统不是语法糖,而是内存安全的最后一道防线。PostgreSQL 类型混淆漏洞的根因并非某个函数的实现疏忽,而是“动态类型 + 物化执行”这一组合在边界校验上的系统性缺口。对于架构师而言,这带来三条可迁移的工程原则:
- 信任边界必须显式化:任何接受外部输入并参与类型推导的组件,都必须在运行时做严格的一致性校验,而非依赖调用方“守规矩”。
- 权限即攻击面:数据库账号的每一份多余权限,都是漏洞利用链上的一级台阶。最小权限不是运维口号,是安全架构的硬约束。
- 应急能力要前置建设:本次漏洞从通告到修复版本发布的时间窗口极短,能否在 24 小时内完成资产盘点、升级验证与回滚预案,取决于平时是否把“可升级性”当作架构属性来设计。
最后,请把这次 PostgreSQL 类型混淆漏洞的处置过程沉淀为团队的安全 runbook:版本矩阵、升级命令、验证脚本、回滚步骤、以及失陷排查清单。下一次高危通告到来时,你需要的不是重新思考,而是直接执行。
本文核心技术背景源自 GitHub Security Advisory 官方通告:
GHSA-fhmx-cqh3-5vvj (CVE-2026-120141 PostgreSQL 类型混淆漏洞)
