导语:PostgreSQL 扩展脚本注入并非新话题,但 CVE-2026-120153 把它的危害边界推到了新的高度——一个只拥有数据库级 CREATE 权限的低权用户,可以借助扩展脚本替换机制中的字符串拼接缺陷,以 bootstrap 超级用户身份执行任意代码。本文从架构师视角完整拆解这条提权链路,并给出可直接落地的检测与加固方案。
企业级架构规范
flowchart TD
subgraph 攻击面 ["攻击面与接入层 (Attack Surface Tier)"]
A["低权用户 (仅 CREATE 权限)"] --> B["构造恶意 schema 名 / owner 名 (含换行符)"]
end
subgraph 触发层 ["扩展安装触发层 (Extension Install Tier)"]
B --> C["CREATE EXTENSION 调用非内置扩展"]
C --> D["扩展脚本中的 @extschema@ / @owner@ 占位符替换"]
end
subgraph 注入层 ["脚本替换与注入层 (Substitution & Injection Tier)"]
D --> E["字符串拼接未转义特殊字符"]
E --> F["恶意 SQL 片段注入脚本执行流"]
end
subgraph 提权层 ["权限边界突破层 (Privilege Escalation Tier)"]
F --> G["以 bootstrap 超级用户身份执行"]
G --> H[("任意代码执行 / 数据库完全失陷")]
end
一、漏洞背景:为什么 PostgreSQL 扩展脚本注入值得高度警惕
CVE-2026-120153 被评定为高危(CVSS 7.2),其核心风险在于权限边界的跨越幅度:攻击者只需要数据库级的 CREATE 权限,就能在扩展安装过程中以 bootstrap 超级用户身份执行任意代码。这意味着一个多租户 SaaS 平台中,任何被授予建表权限的普通业务账号,都可能成为数据库整体失陷的入口。
该漏洞与 2023 年的 CVE-2023-39417 在成因上高度相似,但触发条件更苛刻也更隐蔽:它要求目标是一个非内置(non-bundled)扩展,且该扩展的脚本中存在易受攻击的替换模式。换句话说,PostgreSQL 内核本身并不直接暴露问题,真正的风险面来自第三方扩展生态与脚本替换机制的交互。
不要把扩展安装权限等同于普通 DDL 权限。扩展脚本在安装时以高权限上下文执行,任何允许低权用户触发扩展安装的设计,本质上都是在把超级用户能力外包给不可信输入。
二、PostgreSQL 扩展脚本注入的根因拆解
2.1 扩展脚本替换机制如何工作
PostgreSQL 扩展通过控制文件(.control)与 SQL 脚本(–1.sql、–2.sql 等)定义安装逻辑。脚本中支持若干占位符,例如 @extschema@ 表示扩展安装的目标 schema,@owner@ 表示扩展所有者。安装时,PostgreSQL 会读取脚本内容,将这些占位符替换为实际值,再交给执行器运行。
问题就出在替换环节:如果 schema 名或 owner 名中包含换行符、分号或其他特殊子串,而替换逻辑只是简单字符串拼接、未做标识符转义,那么恶意名称就会突破字符串边界,把注入的 SQL 片段带入脚本执行流。由于脚本执行上下文是 bootstrap 超级用户,注入片段自然获得了最高权限。
2.2 与 CVE-2023-39417 的异同
| 评估维度 | CVE-2023-39417 | CVE-2026-120153 | 架构收益 |
|---|---|---|---|
| 触发前提 | 内置扩展脚本模式 | 非内置扩展 + 脆弱脚本模式 | 影响面更依赖第三方生态 |
| 注入载体 | schema 名称 | schema 或 owner 名称 | 攻击向量更宽 |
| 所需权限 | 数据库级 CREATE | 数据库级 CREATE | 低权即可触发 |
| 执行身份 | bootstrap 超级用户 | bootstrap 超级用户 | 危害等级一致 |
很多团队在 CVE-2023-39417 修复后认为同类问题已经收敛,但本次漏洞说明:只要第三方扩展脚本仍在使用不安全的替换模式,同类注入就会以新的载体复现。升级内核只是必要条件,不是充分条件。
三、影响范围与版本矩阵
根据公告,受影响版本为 PostgreSQL 18.7、17.12、16.16、15.20、14.25 之前的全部版本。修复版本已同步发布,建议按大版本线逐一对照升级。
| 大版本线 | 受影响版本 | 修复版本 | 处置优先级 |
|---|---|---|---|
| PostgreSQL 18 | < 18.7 | 18.7 | P0 立即升级 |
| PostgreSQL 17 | < 17.12 | 17.12 | P0 立即升级 |
| PostgreSQL 16 | < 16.16 | 16.16 | P0 立即升级 |
| PostgreSQL 15 | < 15.20 | 15.20 | P1 计划升级 |
| PostgreSQL 14 | < 14.25 | 14.25 | P1 计划升级 |
四、工业级检测与加固方案
4.1 排查高风险扩展与异常命名
在升级窗口之前,建议先做一轮资产盘点:识别所有非内置扩展,检查其脚本是否使用 @extschema@ 或 @owner@ 占位符,并扫描 schema 与 role 名称中是否存在换行符、分号等异常字符。
-- 1. 列出所有已安装的非内置扩展
SELECT e.extname, e.extversion, n.nspname AS schema_name, r.rolname AS owner_name
FROM pg_extension e
JOIN pg_namespace n ON n.oid = e.extnamespace
JOIN pg_roles r ON r.oid = e.extowner
WHERE e.extname NOT IN ('plpgsql', 'adminpack');
-- 2. 扫描名称中含换行符或分号的 schema
SELECT oid, nspname
FROM pg_namespace
WHERE nspname ~ E'[nr;]';
-- 3. 扫描名称中含特殊字符的 role
SELECT oid, rolname
FROM pg_roles
WHERE rolname ~ E'[nr;]';
4.2 升级执行与回滚准备
点击展开:升级执行命令与备用回滚脚本
# 以 Debian/Ubuntu 为例,升级前务必完成逻辑备份 pg_dumpall -U postgres > /backup/pg_all_$(date +%F).sql # 更新软件源并升级到修复版本 apt-get update apt-get install --only-upgrade postgresql-16 # 验证版本 psql -U postgres -c "SELECT version();" # 回滚预案:若升级异常,使用备份恢复 # psql -U postgres -f /backup/pg_all_YYYY-MM-DD.sql
在完成升级前,严禁向不可信角色授予数据库级 CREATE 权限。对于多租户场景,应通过独立 schema 与最小权限模型隔离扩展安装能力,避免业务账号直接触发 CREATE EXTENSION。
4.3 长期加固:把扩展安装纳入受控流程
- 将 CREATE EXTENSION 权限收敛到专用运维角色,业务角色默认不授予。
- 对第三方扩展脚本做上线前静态审计,重点检查占位符替换与字符串拼接逻辑。
- 在 CI 中引入 schema/role 命名规范校验,禁止换行符、分号等特殊字符进入标识符。
- 建立 PostgreSQL 大版本安全基线,订阅官方安全公告并纳入漏洞响应 SLA。
把 PostgreSQL 扩展脚本注入的防护前移到权限模型与发布流程,而不是依赖单次内核补丁。补丁解决已知载体,权限收敛才能覆盖未知变体。
五、总结:从单点补丁到系统性防御
CVE-2026-120153 再次提醒我们:数据库安全的最大风险往往不在内核本身,而在扩展生态与权限模型的交界处。PostgreSQL 扩展脚本注入之所以危险,是因为它把低权 DDL 能力与超级用户执行上下文直接连通。升级到 18.7 / 17.12 / 16.16 / 15.20 / 14.25 是止血,收敛 CREATE 权限、审计第三方脚本、规范标识符命名才是长期免疫。
