PostgreSQL dblink UAF 漏洞深度剖析:CVE-2026-120147 重入调用链与应急加固实战

【PostgreSQL dblink UAF 漏洞】深度剖析 PostgreSQL dblink 扩展 CVE-2026-120147 高危 UAF 漏洞:CVSS 8.8,攻击者可通过类型转换重入 dblink 关闭连接触发释放后使用,进而以数据库系统用户身份执行任意代码。本文从内存生命周期、...

导语:PostgreSQL dblink UAF 漏洞(CVE-2026-120147)是一次典型的“合法功能组合出非法内存状态”的安全事故——它不需要溢出、不需要注入,只需要一个对象创建者、一次类型转换、一次重入调用,就能让数据库进程在释放后的堆内存上继续读取结果集,最终以 postgres 系统用户的身份执行任意代码。CVSS 8.8,攻击向量网络可达、复杂度低、无需用户交互,这是每一个跑着 dblink 的生产库都必须当天响应的级别。

🎯 核心技术PostgreSQL dblink 扩展 / 内存生命周期管理
⚡ 架构难度资深架构级 (Level 4)
🚨 风险等级CVSS 8.8 High / 可 RCE

一、PostgreSQL dblink UAF 漏洞的攻击面全景

先把结论摆在最前面:这不是一个“远程未授权打穿数据库”的漏洞,它的前置条件是攻击者具备在目标库中创建对象(函数、类型、转换)的权限。在很多业务库里,这等同于“任何能建表的应用账号”。一旦满足这个条件,攻击者就可以构造一条精心设计的调用链,让 dblink 在读取结果集的过程中被重入并关闭连接,随后外层调用继续访问已经释放的连接对象——这就是标准的 Use-After-Free。

从 CVSS 向量看:AV:N / AC:L / PR:L / UI:N / S:U / C:H / I:H / A:H。网络可达、复杂度低、低权限即可、无需交互,机密性/完整性/可用性全部高危。翻译成运维语言就是:只要你的库暴露在网络上,且业务账号有建对象权限,这个洞就是可打的。

📐 PostgreSQL dblink UAF 漏洞重入触发链路全景
企业级架构规范

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
  
图 1:PostgreSQL dblink UAF 漏洞的重入触发链路,从低权限建对象到外层读取已释放连接,最终完成代码执行。

二、根因拆解:为什么“重入 + 关闭连接”会变成 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 只给受控账号使用。

REVOKE CREATE ON SCHEMA public FROM PUBLIC;SQL
REVOKE EXECUTE ON FUNCTION dblink_disconnect(text) FROM PUBLIC;SQL
点击展开:临时缓解完整 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)

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注