2026 年,一个诞生于 1990 年代的 PostgreSQL 扩展被检出高危缓冲区溢出漏洞。这件事本身不算罕见——老代码有老毛病,修了就好。真正让人不安的是另一个事实:大多数团队根本说不清自己系统里到底装了哪些扩展、哪些依赖、哪些已经没人维护的陈旧组件。
漏洞不是最可怕的,看不见才是。
一块 90 年代的代码,为什么到 2026 年还在跑?
PostgreSQL 的扩展生态从 90 年代就开始生长。很多扩展最初由社区成员随手写就,解决某个具体需求,然后被打包进发行版、被运维脚本自动安装、被 Docker 镜像默默继承。原作者早已转去做别的事,维护权无人接手,代码躺在磁盘上,生产环境天天加载它——但没人记得它为什么在那里。
缓冲区溢出是 C 扩展的经典缺陷。输入长度未校验、栈上固定大小缓冲区被溢出写入,攻击者可以构造恶意输入劫持控制流。这类漏洞在 2026 年的 CVE 数据库里依然频繁出现,不是因为技术太难防,而是因为没人去看那些代码。
你看不见的东西,正在替你做决定
Christophe Pettus 在演讲中点出了一个更广泛的症状:团队对自身软件栈的认知存在巨大盲区。这不仅仅是 PostgreSQL 扩展的问题——
- 系统包管理器:
apt、yum安装的包列表,有多少人能完整列出来? - Python/Node 依赖树:
pip install和npm install拉进来的间接依赖,你审查过几层? - Docker 镜像继承链:基础镜像里预装了什么,
apt list --installed的输出你看过吗? - Kubernetes 集群附加组件:Helm chart 默认启用的 sidecar、CRD、admission webhook,你逐个确认过吗?
每一层都有可能藏着一块 90 年代的代码。它不报错、不报警、不出现在监控面板上,但它在运行,它在处理输入,它在暴露攻击面。
先把家底摸清:盘点你的 PostgreSQL 扩展
解决"看不见"的问题,第一步是强制自己看见。下面是一套可以直接在 PostgreSQL 生产环境上运行的盘点脚本。
列出所有已安装扩展及其版本
-- 在每个数据库上执行,列出已安装扩展
SELECT extname AS extension_name,
extversion AS version,
n.nspname AS schema
FROM pg_extension e
JOIN pg_namespace n ON e.extnamespace = n.oid
ORDER BY extname;
输出类似:
extension_name | version | schema
----------------+---------+--------
plpgsql | 1.0 | pg_catalog
pgcrypto | 1.3 | public
pg_stat_statements | 1.9 | public
some_old_ext | 0.4 | public
some_old_ext 版本 0.4——你记得它是什么吗?谁装的?还在用吗?
检查扩展的共享库文件来源
# 列出所有扩展对应的 .so 文件路径
for ext in $(psql -At -c "SELECT extname FROM pg_extension"); do
control_file="/usr/share/postgresql/extension/${ext}.control"
if [ -f "$control_file" ]; then
module=$(grep -E '^module_pathname' "$control_file" | cut -d= -f2 | tr -d "'")
echo "$ext -> $module"
else
echo "$ext -> [control file missing]"
fi
done
这段脚本遍历每个扩展的 .control 文件,提取其指向的共享库路径。如果某个扩展指向一个你从未见见的 .so 文件,这就是一个需要追问的线索。
扫描扩展代码中的危险模式
如果你有扩展的源码(或者能从包里提取出来),可以用简单模式扫描高风险写法:
# 在扩展源码目录中扫描常见危险模式
scan_dir="/usr/share/postgresql/extension"
echo "=== 固定大小缓冲区 (strcpy / sprintf) ==="
grep -rn 'strcpy\|sprintf\|gets' "$scan_dir" --include='*.c' || echo "未发现"
echo "=== 未校验长度的 memcpy ==="
grep -rn 'memcpy' "$scan_dir" --include='*.c' | \
grep -v 'sizeof' || echo "需人工复查"
echo "=== 硬编码路径或权限 ==="
grep -rn '/tmp/\|chmod 777\|0700' "$scan_dir" --include='*.c' || echo "未发现"
这不是替代专业安全审计,而是用最低成本把最显眼的风险标记出来。strcpy 和 sprintf 在 2026 年的 C 代码里出现,几乎等于在说"这段代码上次被认真审查是在 1998 年"。
超出 PostgreSQL:给整个依赖栈做一次 X 光
PostgreSQL 扩展只是冰山一角。下面是一套跨层的盘点命令,可以直接在服务器或 CI 环境中运行:
#!/bin/bash
# dependency-xray.sh — 依赖栈 X 光扫描
set -euo pipefail
echo "========== 系统包 =========="
apt list --installed 2>/dev/null | wc -l
echo "(总数)"
echo "========== Python 依赖树 =========="
if command -v pip &>/dev/null; then
pip audit 2>/dev/null || echo "pip-audit 未安装,运行: pip install pip-audit"
fi
echo "========== Node 依赖 =========="
if [ -f package-lock.json ]; then
npm audit --production 2>/dev/null || true
fi
echo "========== Docker 镜像层 =========="
if command -v docker &>/dev/null; then
for img in $(docker images --format '{{.Repository}}:{{.Tag}}' | head -5); do
echo "--- $img ---"
docker run --rm "$img" apt list --installed 2>/dev/null | wc -l || true
done
fi
echo "========== PostgreSQL 扩展 =========="
for db in $(psql -At -c "SELECT datname FROM pg_database WHERE datistemplate = false"); do
echo "--- 数据库: $db ---"
psql -d "$db" -At -c \
"SELECT extname || ' ' || extversion FROM pg_extension ORDER BY extname"
done
运行前确保你有只读权限,不需要 root。把输出存下来,和三个月前的版本做 diff——新增的任何条目都应该有一个明确的"谁批准的、为什么需要"的答案。
每个看不见的组件,都要有一个退役计划
Pettus 的演讲最终指向的不是某个具体漏洞,而是运维纪律的缺失。以下是一份可以直接采纳的检查清单:
| 检查项 | 做法 | 频率 |
|---|---|---|
| 扩展清单 | 在每个数据库运行 pg_extension 查询,存档 |
每月 |
| 无人认领的扩展 | 对每个扩展追问:谁装的?哪个功能在用?没有就卸载 | 季度 |
| 依赖树深度 | pip audit / npm audit / apt list,关注间接依赖 |
每次部署 |
| 基础镜像内容 | docker run --rm <image> apt list --installed |
镜像更新时 |
| 扩展源码审查 | 对自定义或低维护扩展做 strcpy/sprintf 模式扫描 |
引入时 + 年度 |
| CVE 订阅 | 订阅 PostgreSQL 及关键扩展的安全公告 | 持续 |
关键原则:装上去的东西必须有退出路径。 如果一个扩展没有维护者、没有发布历史、没有安全响应渠道,它就不应该出现在生产环境里——哪怕它"一直没出过问题"。"一直没出过问题"只是"还没人去查过"的另一种说法。
1990 年代的代码在 2026 年爆出缓冲区溢出,这不是意外,这是必然。代码不会因为被遗忘而变得安全,只会因为被遗忘而变得危险。第一步不是修补漏洞,而是看见你到底装了什么。上面的 SQL 和脚本用五分钟就能跑完,但它们给你的信息,可能比你过去五年对自身依赖栈的认知还多。