导语:PostgreSQL dblink UAF 漏洞(CVE-2026-120147)是一次典型的“合法功能组合出非法内存状态”的安全事故——它不需要溢出、不需要注入,只需要一个对象创建者、一次类型转换、一次重入调用,就能让数据库进程在释放后的堆内存上继续读取结果集,最终以 postgres 系统用户的身份执行任意代码。CVSS 8.8,攻击向量网络可达、复杂度低、无需用户交互,这是每一个跑着 dblink 的生产库都必须当天响应的级别。
一、PostgreSQL dblink UAF 漏洞的攻击面全景
先把结论摆在最前面:这不是一个“远程未授权打穿数据库”的漏洞,它的前置条件是攻击者具备在目标库中创建对象(函数、类型、转换)的权限。在很多业务库里,这等同于“任何能建表的应用账号”。一旦满足这个条件,攻击者就可以构造一条精心设计的调用链,让 dblink 在读取结果集的过程中被重入并关闭连接,随后外层调用继续访问已经释放的连接对象——这就是标准的 Use-After-Free。
从 CVSS 向量看:AV:N / AC:L / PR:L / UI:N / S:U / C:H / I:H / A:H。网络可达、复杂度低、低权限即可、无需交互,机密性/完整性/可用性全部高危。翻译成运维语言就是:只要你的库暴露在网络上,且业务账号有建对象权限,这个洞就是可打的。
企业级架构规范
flowchart TD
subgraph 攻击入口层 ["攻击入口层 (Attacker Entry Tier)"]
A["低权限账号 (CREATE 权限)"] --> B["构造恶意类型转换函数 (CAST / Type Conversion)"]
end
subgraph 外层调用层 ["外层 dblink 调用层 (Outer Call Tier)"]
B --> C["dblink() 发起远程查询并持有连接句柄"]
C --> D["逐行读取结果集 (Result Fetch Loop)"]
end
subgraph 重入触发层 ["重入触发层 (Re-entrancy Tier)"]
D --> E["结果列触发类型转换回调"]
E --> F["回调内部再次进入 dblink 并 dblink_disconnect"]
end
subgraph 内存破坏层 ["内存破坏层 (Memory Corruption Tier)"]
F --> G["连接对象被释放 (free)"]
D --> H["外层继续读取已释放内存 (use-after-free)"]
end
subgraph 影响层 ["影响层 (Impact Tier)"]
H --> I["堆内存布局操控 / 函数指针劫持"]
I --> J["以 postgres 系统用户执行任意代码"]
end
二、根因拆解:为什么“重入 + 关闭连接”会变成 UAF
2.1 dblink 的连接生命周期假设
dblink 扩展的核心抽象是“命名连接”:dblink_connect() 建立连接并挂到会话的连接哈希表中,dblink() 发起查询并返回一个结果游标,调用方通过 dblink_fetch() 逐行拉取。整个设计隐含了一个关键假设:在一次 dblink 调用读取结果集的过程中,底层连接对象不会被其他路径释放。
这个假设在单线程、无回调的普通 SQL 执行路径下成立。但 PostgreSQL 允许在类型转换(CAST)中挂载用户自定义函数,而用户自定义函数可以执行任意 SQL——包括再次调用 dblink。于是“外层正在读结果集”与“内层关闭连接”这两个动作可以在同一个调用栈里同时发生,假设被打破。
2.2 重入路径的精确时序
把攻击链拆成时序,问题就非常清晰了:
- 步骤 1:攻击者创建自定义类型转换函数
evil_cast(text) RETURNS int,函数体内调用dblink_disconnect('victim_conn')。 - 步骤 2:攻击者发起
SELECT * FROM dblink('victim_conn', 'SELECT ...') AS t(c int),外层调用持有连接句柄并开始逐行读取。 - 步骤 3:结果集某一列在返回给客户端前触发类型转换,进入
evil_cast。 - 步骤 4:
evil_cast内部调用dblink_disconnect,连接对象被free,但外层游标仍持有指向该对象的指针。 - 步骤 5:外层继续
dblink_fetch,读取已释放内存,触发 UAF。
UAF 本身只是“读已释放内存”,但 PostgreSQL 的堆分配器(palloc / aset)在连接对象释放后,攻击者可以通过后续的 SQL 执行精确控制堆布局,把已释放的连接对象内存重填为可控内容,从而劫持函数指针或回调结构,完成从 UAF 到 RCE 的跨越。这就是 CVSS 给出 C:H/I:H/A:H 的原因。
三、影响版本矩阵与修复版本对照
官方在 2026 年 11 月同步发布了五条维护线的修复版本。注意:这不是一个“只影响最新版”的洞,14 到 18 全线中招,很多还在跑 14/15 的老库同样需要升级。
| 维护线 | 受影响版本 | 修复版本 | 升级紧迫度 |
|---|---|---|---|
| PostgreSQL 18 | < 18.7 | 18.7 | 立即升级 |
| PostgreSQL 17 | < 17.12 | 17.12 | 立即升级 |
| PostgreSQL 16 | < 16.16 | 16.16 | 立即升级 |
| PostgreSQL 15 | < 15.20 | 15.20 | 立即升级 |
| PostgreSQL 14 | < 14.25 | 14.25 | 立即升级 |
PostgreSQL 14 已接近社区 EOL 窗口,部分发行版仓库可能不会及时跟进 14.25 的打包。如果你的库还在 14 上,请优先评估升级到 16/17 的迁移路径,而不是长期停留在 14.25 打补丁——补丁能堵这个洞,堵不住下一个。
四、应急响应:从检测到加固的四步落地
4.1 第一步:确认 dblink 是否在用、谁在用
很多团队其实并不清楚自己的库里有没有装 dblink。先查扩展,再查权限,最后查连接配置。
-- 1. 确认 dblink 扩展是否安装
SELECT extname, extversion, extnamespace::regnamespace
FROM pg_extension
WHERE extname = 'dblink';
-- 2. 列出所有非超级用户但拥有 CREATE 权限的角色
SELECT r.rolname, n.nspname
FROM pg_roles r
JOIN pg_namespace n ON true
WHERE NOT r.rolsuper
AND has_schema_privilege(r.rolname, n.oid, 'CREATE')
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
ORDER BY r.rolname;
-- 3. 检查是否存在可疑的自定义 CAST 与函数
SELECT castsource::regtype, casttarget::regtype, castfunc::regproc
FROM pg_cast
WHERE castfunc 0
AND castsource::regtype::text NOT LIKE 'pg_%';
4.2 第二步:临时缓解(无法立即升级时)
如果生产库因为业务耦合无法当天升级,可以先用“最小权限 + 扩展隔离”把攻击面压到最低。核心思路是:不让普通业务账号拥有创建 CAST / 函数的权限,dblink 只给受控账号使用。
点击展开:临时缓解完整 SQL 脚本与回滚方案
-- 临时缓解:收回 public schema 的建对象权限 REVOKE CREATE ON SCHEMA public FROM PUBLIC; -- 仅允许受控角色创建函数与类型 REVOKE CREATE ON SCHEMA public FROM app_rw; GRANT CREATE ON SCHEMA app_trusted TO dblink_owner; -- 收回 dblink 断开连接的公共执行权限 REVOKE EXECUTE ON FUNCTION dblink_disconnect(text) FROM PUBLIC; GRANT EXECUTE ON FUNCTION dblink_disconnect(text) TO dblink_owner; -- 审计:记录所有 dblink 调用(需配合 pgaudit) ALTER SYSTEM SET pgaudit.log = 'function,ddl'; SELECT pg_reload_conf(); -- 回滚方案(确认升级完成后执行) -- GRANT CREATE ON SCHEMA public TO PUBLIC; -- GRANT EXECUTE ON FUNCTION dblink_disconnect(text) TO PUBLIC;
4.3 第三步:升级执行
小版本升级(如 16.15 → 16.16)通常只需替换二进制并重启,不需要 initdb。但生产环境请务必先在从库或预发环境验证扩展兼容性,尤其是 dblink、postgis 这类带 C 代码的扩展。
# 1. 备份(逻辑 + 物理双保险) pd_dump -Fc -f /backup/pre_upgrade.dump your_db pg_basebackup -D /backup/base -Ft -z -P # 2. 停止服务 systemctl stop postgresql-16 # 3. 安装修复版本(以 Debian/Ubuntu PGDG 源为例) apt-get update apt-get install -y postgresql-16=16.16-1.pgdg120+1 # 4. 启动并验证 systemctl start postgresql-16 psql -c "SELECT version();" psql -c "SELECT extname, extversion FROM pg_extension WHERE extname='dblink';"
4.4 第四步:长期加固
- 权限最小化:业务账号默认不授予 CREATE 权限,建对象走独立的 migration 账号。
- 扩展白名单:生产库只允许安装经过安全评审的扩展,dblink 能用 postgres_fdw 替代的优先替代。
- 审计常态化:开启 pgaudit,对
CREATE CAST、CREATE FUNCTION、dblink_disconnect做重点告警。 - 版本节奏:把 PostgreSQL 小版本升级纳入月度例行窗口,而不是等 CVE 出来再救火。
如果你的系统大量依赖 dblink 做跨库查询,建议借这次漏洞做一次架构评估:postgres_fdw 在连接管理、事务语义和权限模型上都更现代,且不暴露 dblink_disconnect 这类可被重入利用的接口。把 dblink 逐步迁移到 postgres_fdw,是比“打补丁”更彻底的解法。
五、从这次 PostgreSQL dblink UAF 漏洞能学到什么
CVE-2026-120147 给所有数据库内核与扩展开发者上了一课:任何“持有资源句柄 + 允许回调”的 API 设计,都必须显式考虑重入。dblink 的连接对象没有引用计数,没有“正在使用”标记,也没有在 disconnect 时检查是否有活跃游标——这三个缺失中的任何一个被补上,这个洞都不会成立。
对架构师而言,更值得记住的是攻击面的组合性:单独看“类型转换”是安全的,单独看“dblink_disconnect”是安全的,单独看“低权限建对象”在很多团队眼里也是可接受的——但三者组合起来就是一个 CVSS 8.8 的 RCE。安全评审不能只看单点,要看调用链。
本文核心技术背景源自 GitHub Advisory Database 与 PostgreSQL 官方安全公告:
GHSA-63j6-rm8q-mhqx (GitHub Advisory Database)
CVE-2026-120147 (PostgreSQL Security Information)
