PostgreSQL 类型混淆漏洞深度剖析:min/max 聚合优化器 SORTOP 缺陷如何导致任意代码执行

深度剖析 PostgreSQL 类型混淆漏洞 CVE-2026-116331(CVSS 8.8):从 min/max 聚合优化器 SORTOP 机制切入,拆解规划器类型推导缺陷、攻击链路与提权原理,并给出多版本升级、SORTOP 校验加固与生产环境应急响应落地方案。

导语:PostgreSQL 类型混淆漏洞(CVE-2026-116331)是近期数据库安全领域最值得警惕的高危缺陷之一——一个看似无害的 SORTOP 聚合选项,竟能让普通对象创建者以数据库运行用户的身份执行任意代码。本文以架构师视角,从查询规划器的 min/max 聚合优化路径切入,完整还原漏洞成因、攻击链路与生产级加固方案。

🎯 核心技术PostgreSQL 查询规划器 / 聚合优化
⚡ 风险等级High · CVSS 8.8
🚀 修复收益阻断提权链路 / 消除 RCE 面
🚨 严重隐患告警

受影响版本:PostgreSQL < 18.7、< 17.12、< 16.16、< 15.20、< 14.25。攻击者仅需具备创建用户自定义聚合(CREATE AGGREGATE)的权限,即可在数据库服务进程上下文中执行任意代码,等同于操作系统级提权。请立即评估并升级。

📐 PostgreSQL 类型混淆漏洞攻击链路与规划器执行全景
企业级架构规范

flowchart TD
    subgraph 攻击面 ["攻击者接入层 (Attacker Tier)"]
        A["低权限对象创建者 (CREATE AGGREGATE 权限)"] --> B["构造自定义聚合 + 伪造 SORTOP 选项"]
    end
    subgraph 规划层 ["查询规划与优化层 (Planner Tier)"]
        B --> C["min/max 聚合优化路径识别 (Optimize Aggregate)"]
        C --> D["依据 SORTOP 推导输入/结果同类型 (Type Confusion)"]
    end
    subgraph 执行层 ["核心执行引擎层 (Executor Tier)"]
        D --> E["跳过类型转换直接复用比较函数 (Unsafe Cast)"]
        E --> F["函数指针/内存布局被误解释 (Arbitrary Code Exec)"]
    end
    subgraph 宿主层 ["基础设施与宿主层 (Host Tier)"]
        F --> G[("以 postgres 系统用户身份执行任意代码")]
    end
  
图 1: PostgreSQL 类型混淆漏洞从低权限聚合创建到规划器误判、执行器越界复用的完整提权链路

一、漏洞背景:为什么 min/max 聚合优化会成为攻击入口

PostgreSQL 的查询规划器对 MIN() / MAX() 聚合有一条经典的优化捷径:当聚合带有 SORTOP(排序操作符)选项时,规划器可以将其改写为「索引有序扫描取首行」或「排序后取首行」,从而避免全表聚合扫描。这条优化在 OLTP 场景下收益显著,但它隐含了一个强假设——聚合的输入类型与结果类型一致,且排序操作符与聚合语义严格匹配。

问题在于,SORTOP 是用户自定义聚合时可以自由声明的元数据。规划器在优化阶段信任了这一声明,却未对「输入类型 / 结果类型 / 排序操作符类型」三者做一致性校验。当攻击者故意声明一个输入与结果类型不同的聚合,并配上不匹配的 SORTOP 时,规划器会按「同类型」路径生成执行计划,执行器随后以错误的类型解释内存中的 Datum,形成典型的类型混淆(Type Confusion)。

💡 架构核心决策

类型混淆类漏洞的危险性在于:它不依赖内存破坏原语即可跨越信任边界。在 PostgreSQL 中,聚合函数、比较函数均以函数指针形式在服务进程内调用,一旦类型被误解释,攻击者可通过精心构造的聚合转移函数(SFUNC)与最终函数(FINALFUNC)组合,将受控数据当作函数指针或结构体布局使用,最终在 postgres 进程上下文中执行任意代码。

二、PostgreSQL 类型混淆漏洞的技术原理拆解

2.1 SORTOP 语义与规划器的信任边界

在 pg_aggregate 系统表中,aggsortop 字段记录了该聚合可用于 min/max 优化的排序操作符。规划器在 preprocess_minmax_aggregates 阶段会检查:聚合是否只有一个参数、是否声明了 aggsortop、排序操作符是否可用于索引。全部满足时,规划器将聚合改写为子查询 + LIMIT 1 的形式。

关键缺陷在于:改写过程中,规划器假定 aggsortop 的左右操作数类型与聚合的输入类型、结果类型完全一致,并据此省略了显式的类型转换节点(CoerceViaIO / RelabelType)。当攻击者构造如下聚合时,假设被打破:

-- 攻击者构造:输入为 int4,结果声明为 text,SORTOP 指向 int4 比较符
CREATE FUNCTION evil_sfunc(state text, val int4) RETURNS text
  AS 'SELECT $1 || $2::text' LANGUAGE sql IMMUTABLE;

CREATE AGGREGATE evil_min(int4) (
  SFUNC = evil_sfunc,
  STYPE = text,
  SORTOP = <(int4, int4)   -- 声明排序操作符,触发 min/max 优化
);

-- 规划器将按 int4 同类型路径优化,执行器却以 text 解释结果 Datum
SELECT evil_min(id) FROM some_table;

2.2 从类型混淆到任意代码执行

类型混淆本身只是「解释错误」,要升级为 RCE,攻击者需要把错误解释转化为可控的函数调用。PostgreSQL 的执行器在聚合收尾阶段会调用 FINALFUNC,其函数指针来自 pg_proc 元数据,本身不可直接篡改;但攻击者可以通过以下路径放大影响:

  • Datum 布局错位:text 与 int4 的 Datum 表示不同(前者为 varlena 指针,后者为内联值),错误解释可导致执行器将攻击者控制的字节序列当作 varlena 头部解析,进而触发越界读写。
  • 比较函数误用:SORTOP 指向的比较函数被以错误类型调用,攻击者可借此在排序过程中操纵内存布局,为后续利用铺路。
  • 扩展函数链:结合 PL/pgSQL 或 C 语言扩展中的既有函数,攻击者可将受控数据流导向危险函数,最终以 postgres 用户身份执行系统命令。
⚠️ 生产避坑风险

许多团队误以为「只读账号无法提权」。本漏洞的触发前提仅是 CREATE AGGREGATE 权限,而该权限在多数业务库中会随 schema 的 CREATE 权限一并授予应用账号。请立即审计 pg_aggregate 中是否存在非官方扩展创建的聚合对象。

三、影响范围与风险矩阵

评估维度 传统认知 本漏洞实际情况 风险结论
触发权限 需超级用户 仅需 CREATE AGGREGATE 高危
攻击复杂度 需内存破坏原语 纯 SQL 即可构造 低门槛
影响后果 数据泄露 OS 用户级 RCE 严重
受影响版本 — 14.25 / 15.20 / 16.16 / 17.12 / 18.7 之前 全系

四、工业级修复与加固方案

4.1 版本升级(首选)

官方已在 18.7、17.12、16.16、15.20、14.25 中修复该缺陷,核心改动是在 min/max 聚合优化阶段增加输入类型、结果类型与 SORTOP 操作数类型的一致性校验,不满足时回退到通用聚合路径。生产环境应优先执行小版本升级:

点击展开:各发行版升级命令与验证脚本
# Debian / Ubuntu (PGDG 源)
sudo apt update && sudo apt install --only-upgrade postgresql-16

# RHEL / Rocky / AlmaLinux
sudo dnf update postgresql16-server

# 升级后验证版本
psql -c "SELECT version();"

# 审计是否存在可疑自定义聚合
psql -c "SELECT p.proname, a.aggsortop::regoperator FROM pg_aggregate a JOIN pg_proc p ON p.oid = a.aggfnoid WHERE a.aggsortop <> 0;"

4.2 权限收敛与运行时加固

在无法立即升级的窗口期,应通过权限收敛降低攻击面。核心原则是:普通应用账号不应具备在业务 schema 中创建聚合、函数与操作符的能力。

REVOKE CREATE ON SCHEMA public FROM PUBLIC;SQL
-- 1. 收回 public schema 的默认创建权限
REVOKE CREATE ON SCHEMA public FROM PUBLIC;

-- 2. 为应用账号仅授予必要 DML 权限
GRANT USAGE ON SCHEMA public TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app_user;

-- 3. 审计非系统 schema 中的自定义聚合
SELECT n.nspname, p.proname, a.aggsortop
FROM pg_aggregate a
JOIN pg_proc p ON p.oid = a.aggfnoid
JOIN pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname NOT IN ('pg_catalog', 'information_schema');
💡 架构核心决策

长期来看,应将「扩展安装 / 聚合创建」纳入变更管控流程,由 DBA 在独立 schema 中集中管理,应用账号仅持有 USAGE 权限。这一模式与最小权限原则一致,可同时防御本类漏洞及未来同族的规划器信任边界缺陷。

五、应急响应检查清单

  • 确认所有 PostgreSQL 实例版本,标记低于修复版本的节点。
  • 审计 pg_aggregate 中 aggsortop <> 0 的非官方聚合对象。
  • 检查应用账号是否具备 CREATE 权限,按需回收。
  • 排查数据库日志中异常的 min/max 聚合查询与规划时间突增。
  • 升级后回归验证依赖 min/max 优化的慢查询性能是否回退。
原文出处与权威溯源声明:
本文核心技术背景源自 GitHub Security Advisory 官方通告:
GHSA-8qpw-62p2-qh8p (CVE-2026-116331)

发表回复

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