来源: DEF CON 32 - SQL Injection Isn’t Dead: Smuggling Queries at the Protocol Level
作者: Paul Gerste (SonarSource)
日期: 2024年8月10日

核心思想

将 HTTP 请求走私(Request Smuggling)的概念应用到二进制数据库协议层面,通过整数溢出实现 SQL 注入。

先前研究

James Kettle 的 HTTP Desync Attacks 启发了本研究的思路:

  • 通过制造 HTTP 请求边界解析的不一致实现攻击
  • 文本解析差异:chunked vs [\t]chunked
  • 逻辑差异:Content-Length vs Transfer-Encoding

问题: 其他协议是否也存在类似问题?

二进制协议的边界问题

分隔符(Delimiters)

  • 如空字符结尾的字符串
  • 攻击方式:在值中插入分隔符

长度字段(Length Fields)

  • 如 Type-Length-Value (TLV) 协议
  • 攻击方式:整数溢出、字节序问题

为什么选择数据库协议?

优势说明
适用性几乎所有 Web 应用都有数据库
严重性数据库包含敏感数据(PII、认证信息)
可利用性大多数查询包含用户输入

数据库协议对比

数据库消息格式
PostgreSQLType-Length-Value
MySQLLength-Sequence-Value
RedisType-Length-Delimiter-Value-Delimiter
MongoDBmessageLength-requestID-responseTo-opCode-value

PostgreSQL 案例

协议格式

1
2
Type (1字节) | Length (4字节整数) | Value
'Q' | 00 00 00 17 | "SELECT …"

Length 字段最大值为 2³²-1

漏洞代码 (pgx)

1
2
3
4
5
6
7
func (src *Bind) Encode(dst []byte) []byte {
dst = append(dst, 'B')
sp := len(dst)
// …
pgio.SetInt32(dst[sp:], int32(len(dst[sp:])))
return dst
}

问题:len(dst[sp:]) 返回 int 类型,强制转换为 int32 时发生截断,当消息大小超过 2³² 时长度字段溢出。

消息大小溢出

正常消息:

1
2
3
Size: 8 = 0x00000008
Type | Length | Value
'Q' | 00 00 00 08 | "AAAA"

溢出消息:

1
2
3
4
5
Size: 2³²+4 = 0x100000004
Type | Length | Value
'Q' | 00 00 00 04 | "" ← 溢出后长度为4
| 注入的消息...
| 'Q' 00 00 00 17 "DROP..." ← 被注入的恶意SQL

攻击影响

  • 可注入完整 SQL 语句(不受 UNION、子查询限制)
  • 类似堆叠查询(Stacked Queries)效果
  • 可读取/写入/删除数据库中所有数据
  • 直接数据外传较困难(应用只处理第一个响应)

Payload 构造:NOP Sled

使用大量最小消息提高命中率:

1
2
3
4
5
6
Type | Length
'Q' | 00 00 00 04 ← 最小有效消息
'Q' | 00 00 00 04
'Q' | 00 00 00 04
...
'Q' | 00 00 00 3B ← 注入的恶意消息

成功率:约 20%(最多 5 次尝试)

Payload 构造:Trampolines

利用长度字节作为有效消息类型:

1
2
Type | Length
'Q' | 'Q' 'Q' 'Q' 'Q' ← 长度字段作为类型

限制:第一个长度字节不能 > 0x3F

解决方案:交替模式

1
2
Type | Length
'Q' | 00 'Q' 00 'Q' ← 每隔一个字节是有效类型

成功率:50%(最多 2 次尝试)

受影响的 PostgreSQL 库

语言可利用修复版本
Gopgx4.18.2, 5.5.4
Gopg未修复
Gopgdriver未修复
Gopq未修复
C#Npgsql4.0.14, 5.0.18, 6.0.11, 7.0.7, 8.0.3
Javapgjdbc-
JSpg-

真实案例:Harbor

  • Harbor 容器镜像仓库(CNCF 毕业项目)
  • 默认配置存在漏洞
  • 无需认证即可利用
  • 2.11.0 版本通过更新 pgx 修复

MongoDB 案例

协议格式

1
2
messageLength | requestID | responseTo | opCode | value
4字节 4字节 4字节 4字节 BSON文档

漏洞代码 (mongodb Rust)

1
2
3
4
5
6
7
8
9
10
11
12
async fn write_to<T: AsyncWrite + Send + Unpin>(&self, mut writer: T) -> Result<()> {
let sections = self.get_sections_bytes();
let total_length = Header::LENGTH
+ std::mem::size_of::<u32>()
+ sections.len()
+ /* ... */;
let header = Header {
length: total_length as i32, // usize 截断为 i32
// ...
};
// ...
}

Payload 构造

问题:Payload 必须是有效的 UTF-8

解决方案:利用 BSON 文档元数据构造恶意字节

1
2
3
4
5
6
7
Query: { title: "A" * (0x7dd - 1), genre: "SciFi", ... }

BSON:
1308 0000 0274 6974 6c65 00 dd 0700 00 ...
Length Type Key Value长度

通过字符串长度生成 0xdd 0x07 (消息类型)

受影响的 MongoDB 库

语言可利用修复版本
Rustmongodb2.8.2
Pythonpymongo-
Gomongo-
Javamongo-java-driver-
JSmongodb-

绕过大 Payload 限制

常见防护

  • 默认请求体大小限制
  • JSON/表单解码大小限制
  • 反向代理大小限制

绕过方法

未保护端点

  • 部分端点无默认限制(如 Harbor)

压缩绕过

  • 部分服务器在解压前检查大小(Nginx、Fastify)

WebSocket

  • 许多过滤器不适用,支持大消息

替代 Body 类型

  • 如 multipart 表单,部分过滤器不适用

服务端构造

  • 通过 SSRF、模板引擎、i18n 等在服务端创建大字符串

语言对大 Payload 的支持

语言最大字符串最大缓冲区静默溢出
Go>2³²>2³²
Java2³¹-12³¹-1
C#2³¹-1>2³²
JS2²⁹-24>2³²取决于实现
Python>2³²>2³²
Rust>2³²>2³²Release 模式

未来研究

检测方法

  • 白盒测试:搭建自己的测试环境
  • 黑盒测试:需要更多研究和工具
  • 如何安全地检测漏洞库?

扩展研究

更多协议:

  • 其他数据库
  • 缓存系统
  • 消息队列

更多攻击技术:

  • 分隔符相关的攻击
  • 2 字节长度字段(仅需 65KB vs 4GB)

Takeaways

  • 整数溢出在内存安全语言中仍然相关
  • 发送大量数据是可行的
  • SQL 注入没有消亡——如果常规方法不行,就深入一层!