flawopen.com/Incidents/postgresql-psql-injection-beyondtrust-treasury-december-2024
CVE-2025-1094:PostgreSQL psql SQL 注入
应用用转义文本构造 psql 输入时,无效 UTF-8 可绕过 libpq 转义并注入 SQL 或 \! 命令。修复于 2025 年 2 月 13 日。
想象一位译员,他的工作是把你说的每句话都放进引号里,这样就永远不会被当成命令。你发现,只要给他一个他不认识的字母写成的词,他就会犯糊涂,漏掉一个引号。于是你说的话跑出了引号,被当成指令读取,尽管对任何人测试过的输入,这位译员都完全按设计在工作。
核心概念与专有名词
PQescapeLiteral- libpq 中的一个函数,把值加上引号并转义,以便嵌入 SQL 文本。PQescapeIdentifier、PQescapeString 和 PQescapeStringConn 也受影响。
非法 UTF-8- 在声明的编码中格式不正确的字节序列。转义代码与 psql 对这类字节会“吞掉”后面几个字符的理解不一致。
psql 元命令- 由 psql 客户端而非服务器处理的反斜杠命令。\! 会把该行剩余内容当作操作系统 shell 命令执行。
CVE-2024-12356- BeyondTrust Privileged Remote Access 和 Remote Support 中的一个严重命令注入漏洞。在 Rapid7 的测试中,要借它实现代码执行,必须同时利用
CVE-2025-1094。 绑定参数- 与 SQL 文本分开发送给服务器的值,因此永远不需要对它做加引号或转义的判断。
框架映射
事件演进时间线
BeyondTrust 在调查部分 Remote Support SaaS 实例遭入侵后,发布关于 CVE-2024-12356 的公告 BT24-10;美国财政部随后报告称这次事件波及了其系统。
CISA 将 CVE-2024-12356 列入已知被利用漏洞目录。
PostgreSQL 发布 17.3、16.7、15.11、14.16 和 13.19,修复 CVE-2025-1094,Rapid7 同时发布分析。
攻击执行流程分解
输入进入转义函数
应用把受攻击者影响的文本交给 PQescapeLiteral 或相关的 libpq 函数,用来拼接 SQL 语句。
非法 UTF-8 藏住一个引号
根据 PostgreSQL 公告,在单引号前放置精心构造的非法多字节序列,会让转义例程漏掉对该引号的转义。
psql 提前结束字面量
语句交给 psql 后,未被转义的引号会关闭字符串字面量,其余输入被当作 SQL 或 psql 命令解析。
元命令启动 shell
Rapid7 证明,注入的文本可以包含 \! 元命令,psql 会在其运行的机器上把它当作操作系统命令执行。
源代码对比:漏洞与安全实现
使用 psycopg 3 的示意性 Python 代码,并非 BeyondTrust 的代码。有漏洞的版本重现了公告点名的危险模式:用 libpq 转义一个值,拼进 SQL 文本,再把文本交给 psql。加固版本同时去掉了这两半:在任何处理之前就拒绝格式错误的 UTF-8,并通过真正的驱动以绑定参数发送该值,于是既没有可被欺骗的转义步骤,路径上也没有 psql 元命令解析器。对于仍在做转义的代码,升级 libpq 依然重要。
import subprocess
import psycopg
def lookup_user(conn: psycopg.Connection, name: bytes) -> bytes:
# libpq 的 PQescapeLiteral(在 17.3/16.7/15.11/14.16/13.19 之前)无法
# 中和隐藏在无效 UTF-8 字节序列后的引号。
literal = conn.pgconn.escape_literal(name)
script = b"SELECT id FROM users WHERE name = " + literal + b";\n"
# psql 会将输入作为脚本执行,其中包括 \! 之类的元命令。
result = subprocess.run(["psql", "-At", "-d", "app"], input=script,
capture_output=True, check=True)
return result.stdout
import psycopg
MAX_NAME_CHARS = 128
def lookup_user(conn: psycopg.Connection, name: bytes) -> list[int]:
try:
text = name.decode("utf-8") # reject malformed input at the boundary
except UnicodeDecodeError:
raise ValueError("name is not valid UTF-8") from None
if len(text) > MAX_NAME_CHARS:
raise ValueError("name too long")
with conn.cursor() as cur:
# 该值作为绑定参数发送,与 SQL 文本分开,
# 也不会调用 psql 进程(因此没有元命令解析器参与)。
cur.execute("SELECT id FROM users WHERE name = %s", (text,))
return [row[0] for row in cur.fetchall()]
工程与系统安全加固清单
- ✓在代码库中搜索通过 subprocess 调用 psql、且输入由应用数据拼成的地方,改用带绑定参数的数据库驱动;再加一条 CI 规则,一旦出现新的此类调用就失败。
- ✓在边界处拒绝在声明编码中格式错误的输入,别让它到达任何解析器、转义函数或数据库调用;添加一个发送 b'\xc0\'' 并期望出现校验错误的测试。
- ✓不仅升级服务器,也要升级 libpq 和客户端工具:
CVE-2025-1094已在 PostgreSQL 17.3、16.7、15.11、14.16 和 13.19 中修复,应用服务器上的客户端软件包也必须更新。 - ✓在暂时无法去掉转义的地方,添加回归测试:向转义路径输入“非法 UTF-8 + 引号”,断言结果被拒绝或被安全地加上引号。
- ✓把特权远程访问工具视为攻击面的一部分:限制其管理接口,厂商一发通知就打补丁,并监控其 API 密钥是否在意外位置被使用。