XStream CVE-2021-21341拒绝服务漏洞深度分析
1. 漏洞概述
CVE-2021-21341是XStream在2021年爆出的一个拒绝服务(DoS)漏洞。XStream是一个常用的Java对象和XML相互转换的工具。与常见的命令执行漏洞、JNDI注入漏洞不同,这个拒绝服务漏洞是代码逻辑上的漏洞,利用链构造十分巧妙。
2. 漏洞原理
2.1 利用链分析
该漏洞的利用链前半部分与CVE-2020-26217相同,都是利用Base64Data.get方法替换一个恶意的InputStream。区别在于之前的入口被封堵了,因此使用了PriorityQueue+ObservableList$1的方法调用到Base64Data的toString方法。
完整利用链如下:
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingCompartor
javaFx.collections.ObservableList$1
com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data.toString
com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data.get
com.sun.xml.internal.bind.v2.util.ByteArrayOutputStreamEx.readFrom
while (True) ...
2.2 核心漏洞点
漏洞的核心在于ByteArrayOutputStreamEx的readFrom()方法中的无限循环:
public void readFrom(InputStream is) throws IOException {
while(true) {
if(this.count == this.buf.length) {
byte[] data = new byte[this.buf.length * 2];
System.arraycopy(this.buf, 0, data, 0, this.buf.length);
this.buf = data;
}
int sz = is.read(this.buf, this.count, this.buf.length - this.count);
if(sz < 0) {
return;
}
this.count += sz;
}
}
在read函数中,会计算count-pos值。如果将pos设置为Integer.MIN_VALUE,count设置为0,那么count-pos会等于pos(由于整数溢出)。这将导致readFrom方法陷入无限循环,从而造成拒绝服务。
3. 利用链构造详解
3.1 利用链组件
-
Source:
PriorityQueue.readObject()- XStream会调用
PriorityQueue的readObject方法
- XStream会调用
-
Gadget:
ObservableList$1比较器- 这是一个匿名比较器类,位于
javafx.collections.ObservableList中 - 类名为
javafx.collections.ObservableList$1 - 该比较器会调用比较对象的
toString方法
- 这是一个匿名比较器类,位于
-
Sink:
ByteArrayOutputStreamEx.readFrom()- 通过
Base64Data.toString()→Base64Data.get()→ByteArrayOutputStreamEx.readFrom() - 其中的无限循环导致拒绝服务
- 通过
3.2 POC构造方法
- 确定利用链
- 构造对应的对象
- 调用XStream的
toXML方法生成恶意XML文本
关键实现细节:
-
获取匿名比较器类:
Class<?> comparatorClass = Class.forName("javafx.collections.ObservableList$1"); -
实例化比较器:
- 使用
Unsafe.allocateInstance方法直接实例化对象,绕过构造函数
- 使用
-
设置PriorityQueue:
- 创建一个
PriorityQueue实例 - 设置比较器为上面实例化的匿名比较器
- 添加包含恶意
Base64Data的对象到队列中
- 创建一个
4. 技术细节分析
4.1 整数溢出原理
Java中int类型的范围是-2,147,483,648到2,147,483,647。当计算0 - Integer.MIN_VALUE时:
Integer.MIN_VALUE的二进制表示:10000000000000000000000000000000- 取反加一后仍然是
10000000000000000000000000000000 - 因此
0 - Integer.MIN_VALUE的结果仍然是Integer.MIN_VALUE
这导致readFrom方法中的is.read()调用时传入的len参数为负数,但被转换为大正数,从而陷入无限循环。
4.2 XStream反序列化特点
XStream的一个重要特点是它可以利用没有实现Serializable接口的类进行反序列化操作,这大大扩展了可利用的类范围。
5. 影响范围
- 受影响版本:XStream 1.4.15及之前版本
- 需要较高版本的JDK(包含
javafx.collections.ObservableList类)
6. 修复方案
XStream官方在后续版本中修复了此漏洞,建议用户升级到最新版本。修复措施包括:
- 限制某些危险类的反序列化
- 对可能导致无限循环的输入进行校验
- 改进
Base64Data类的处理逻辑
7. 防御建议
- 及时升级XStream到最新版本
- 实施输入验证,过滤恶意XML输入
- 使用XStream的安全框架配置,限制可反序列化的类
- 在关键服务中实施资源限制,防止拒绝服务攻击
8. 总结
CVE-2021-21341展示了XStream在处理特定对象图时的逻辑缺陷,通过精心构造的利用链,攻击者可以触发无限循环导致拒绝服务。这个漏洞的利用链构造巧妙,结合了多个组件的特性,包括集合类、比较器和数据流处理类,最终通过整数溢出实现攻击效果。