导语:PostgreSQL plperl 漏洞(CVE-2026-116319)是一次典型的高危内存安全事件——一个看似边缘的 Perl 过程语言扩展,因重入式
return_next()调用引发的释放后重用(Use-After-Free),将数据库宿主的操作系统权限拱手让给具备对象创建权限的低权用户。本文以架构师视角完成根因拆解、攻击面建模与生产级应急加固闭环。
一、PostgreSQL plperl 漏洞事件全景与影响矩阵
本次披露的 CVE-2026-116319 属于 PostgreSQL 过程语言扩展层的内存安全缺陷,官方评级为 High,CVSS 3.x 基础评分 8.8。其核心描述极为精炼却信息量巨大:“Use after free in PostgreSQL plperl allows an object creator to execute arbitrary code as the operating system user running the database, via reentrant return_next() calls.”
这句话拆开来看,包含四个关键要素,每一个都决定了我们的应急响应优先级:
- 攻击者画像:具备“对象创建权限”(object creator)的数据库用户,即能创建函数、扩展或相关对象的角色——这在多数业务库中并非超级用户,而是普通开发者账号。
- 攻击向量:重入式
return_next()调用,即集合返回函数(SRF)在执行过程中再次触发自身或相关 SPI 上下文,导致内存对象被提前释放后仍被引用。 - 影响后果:以数据库服务进程的操作系统用户身份执行任意代码,等价于数据库宿主机的命令执行。
- 影响版本:PostgreSQL 18.7、17.12、16.16、15.20、14.25 之前的全部版本。
该漏洞的危险性不在于远程未认证利用,而在于权限提升与横向移动:一个仅能创建函数的业务账号,即可突破数据库边界,拿到 postgres 系统用户的 shell。在多租户或 SaaS 场景下,这意味着租户隔离彻底失效。
| 评估维度 | 传统认知 | 本次漏洞实际情况 | 风险等级 |
|---|---|---|---|
| 攻击前置条件 | 需超级用户权限 | 仅需对象创建权限(普通开发者角色) | 高危 |
| 利用复杂度 | 需构造复杂 ROP 链 | 通过重入 return_next() 触发 UAF,路径相对可控 | 中高 |
| 影响范围 | 仅数据库进程内 | 逃逸至宿主 OS 用户,可执行任意命令 | 严重 |
| 修复方式 | 需大版本升级 | 小版本补丁即可,官方已发布 18.7/17.12/16.16/15.20/14.25 | 可闭环 |
企业级架构规范
flowchart TD
subgraph 接入层 ["表现层与接入网关 (Client & Gateway Tier)"]
A["低权业务账号 (对象创建权限)"] --> B["SQL 调用 plperl 集合返回函数"]
end
subgraph 规则层 ["规则编排与上下文层 (Policy & Context Tier)"]
B --> C["plperl 解释器加载 Perl 子例程"]
C --> D["SPI 上下文与 return_next() 重入检测"]
end
subgraph 领域层 ["核心执行与内存生命周期 (Core Memory Tier)"]
D --> E["重入触发提前释放 (Free)"]
E --> F["悬垂指针被再次引用 (Use-After-Free)"]
F --> G["可控内存布局劫持执行流"]
end
subgraph 存储层 ["基础设施与宿主环境 (Infrastructure Tier)"]
G --> H[("以 postgres OS 用户执行任意代码")]
H --> I["宿主机权限提升与横向移动"]
end
二、根因剖析:reentrant return_next() 如何引爆释放后重用
2.1 plperl 与 SPI 的协作模型
plperl 是 PostgreSQL 的受信过程语言扩展,允许用户用 Perl 编写存储过程。当 Perl 函数需要执行 SQL 时,会通过 SPI(Server Programming Interface)回调数据库引擎。集合返回函数(Set-Returning Function, SRF)则通过 return_next() 逐行向调用方返回结果。
问题出在“重入”这一语义上:如果 Perl 函数在 return_next() 的调用栈尚未完全展开时,再次触发对同一 SRF 上下文的调用(例如通过嵌套 SPI 查询、触发器回调或递归调用),引擎内部用于管理结果集的内存上下文(memory context)可能已被提前释放,而外层调用仍持有指向该上下文的指针。
2.2 释放后重用的典型触发路径
- 第一步:外层 SRF 调用进入
return_next(),在某个 memory context 中分配结果元组。 - 第二步:Perl 侧逻辑触发重入,内层调用执行上下文清理,释放了外层仍在使用的内存块。
- 第三步:外层调用返回后继续写入已释放的内存(悬垂指针写入),攻击者可通过堆喷射(heap grooming)控制该内存内容。
- 第四步:劫持函数指针或关键结构体字段,实现控制流转移,最终在
postgres进程上下文中执行任意代码。
从防御视角看,这类漏洞的根因是“可重入接口缺乏生命周期契约”。任何暴露给用户可编程扩展的 C 接口,都必须显式定义重入语义与内存所有权,否则 UAF 只是时间问题。这也是我们在自研数据库扩展时的首要设计红线。
2.3 为什么“对象创建权限”就足够
很多团队误以为存储过程漏洞需要超级用户才能触发。实际上,PostgreSQL 默认允许普通角色在拥有 CREATE 权限的 schema 下创建 plperl 函数(前提是 plperl 扩展已安装且语言被标记为 trusted)。一旦攻击者能创建函数,就能构造恶意 Perl 代码,主动制造重入场景。因此,“谁能创建函数”直接决定了攻击面大小。
三、生产环境应急响应与加固实操
3.1 第一步:版本盘点与影响确认
应急响应的第一动作永远是资产盘点。请在所有 PostgreSQL 实例上执行以下 SQL,确认当前版本与 plperl 扩展安装状态:
-- 确认数据库版本
SELECT version();
-- 确认 plperl 是否安装及可信状态
SELECT extname, extversion, extrelocatable
FROM pg_extension
WHERE extname IN ('plperl', 'plperlu');
-- 列出所有 plperl 函数及其属主
SELECT n.nspname AS schema_name,
p.proname AS function_name,
pg_get_userbyid(p.proowner) AS owner
FROM pg_proc p
JOIN pg_namespace n ON n.oid = p.pronamespace
JOIN pg_language l ON l.oid = p.prolang
WHERE l.lanname = 'plperl';
不要只检查 plperl,plperlu(不可信版本)风险更高——它本就允许执行任意 Perl 代码,若再叠加本漏洞,等于直接给宿主 shell。若业务未使用 Perl 过程语言,应优先考虑彻底移除扩展。
3.2 第二步:补丁升级路径
官方已在以下小版本中修复,升级是唯一根治手段。请根据你的大版本选择对应目标版本:
| 大版本 | 安全版本 | 升级建议 | 停机窗口 |
|---|---|---|---|
| PostgreSQL 18 | 18.7 | 小版本原地升级,兼容性风险低 | 分钟级 |
| PostgreSQL 17 | 17.12 | 小版本原地升级 | 分钟级 |
| PostgreSQL 16 | 16.16 | 小版本原地升级 | 分钟级 |
| PostgreSQL 15 | 15.20 | 小版本原地升级 | 分钟级 |
| PostgreSQL 14 | 14.25 | 小版本原地升级,注意 14 已接近 EOL | 分钟级 |
以 Debian/Ubuntu 的 PGDG 源为例,升级命令如下:
# 1. 更新软件源索引 sudo apt-get update # 2. 仅升级 PostgreSQL 相关包(以 16 为例) sudo apt-get install --only-upgrade postgresql-16 postgresql-client-16 postgresql-contrib-16 # 3. 重启服务使新二进制生效 sudo systemctl restart postgresql # 4. 验证版本 sudo -u postgres psql -c "SELECT version();"
点击展开:RPM 系(RHEL/CentOS/Rocky)升级命令与回滚预案
# RHEL / Rocky / AlmaLinux 使用 PGDG 仓库 sudo dnf update postgresql16-server postgresql16-libs postgresql16-contrib # 重启服务 sudo systemctl restart postgresql-16 # 回滚预案:升级前务必做逻辑备份 pg_dumpall -U postgres > /backup/pg_pre_patch_$(date +%F).sql # 若升级后异常,可降级包版本(需仓库保留旧版) sudo dnf downgrade postgresql16-server-
3.3 第三步:无法立即升级时的临时缓解
若因合规或业务窗口无法立刻打补丁,必须立即实施以下纵深防御措施,将攻击面压到最小:
- 移除或禁用 plperl:若业务未使用,执行
DROP EXTENSION plperl;并同步删除plperlu。 - 收紧函数创建权限:撤销普通角色的 CREATE 权限,改为白名单授权。
- 审计现有 plperl 函数:对存量函数做代码审查,确认无恶意重入逻辑。
- 数据库进程降权:确保
postgres系统用户无 sudo、无敏感目录写权限,缩小逃逸后的影响半径。
-- 撤销 public schema 的默认创建权限 REVOKE CREATE ON SCHEMA public FROM PUBLIC; -- 仅授权给受信任的运维角色 GRANT CREATE ON SCHEMA public TO app_trusted_role; -- 查找并禁用可疑的 plperl 函数 ALTER FUNCTION suspicious_schema.suspicious_func() OWNER TO postgres; REVOKE EXECUTE ON FUNCTION suspicious_schema.suspicious_func() FROM PUBLIC;
临时缓解只是止血,不能替代补丁。建议将“数据库小版本安全补丁”纳入月度例行运维窗口,并建立 CVE 订阅与自动告警机制,避免下次再被动应战。
四、从 PostgreSQL plperl 漏洞看数据库扩展安全治理
这次 PostgreSQL plperl 漏洞并非孤例。过去几年,数据库过程语言扩展、FDW、自定义聚合等“可编程边界”反复成为内存安全问题的温床。作为架构师,我们应当从中提炼出可复用的治理原则:
- 最小扩展原则:生产库只安装业务确需的扩展,plperl、plperlu、plpythonu 等“可执行任意代码”的扩展应默认禁用。
- 权限分层:将“能创建函数”与“能连接业务库”的角色分离,开发者账号不应具备 DDL 权限。
- 纵深防御:数据库进程以独立低权用户运行,配合容器化隔离与 seccomp 限制,降低逃逸后的爆炸半径。
- 补丁 SLA:高危 CVE 应在 72 小时内完成评估与升级,中危 30 天内闭环。
最后提醒:安全公告中的“object creator”一词值得所有 DBA 与架构师反复咀嚼——在权限模型设计上,任何“看似无害”的创建权限,都可能成为攻击链的起点。
本文核心技术背景源自 GitHub Security Advisory 官方披露:
GHSA-86xg-2gfq-h4cc (CVE-2026-116319 PostgreSQL plperl Use-After-Free)
