Java 反序列化漏洞始末(1)

释放双眼,带上耳机,听听看~!

点击上方“凌天实验室”,“星标或置顶公众号”

漏洞、技术还是其他,我都想第一时间和你分享


  每日趣闻  

为什么程序员爱养猫?!


 
序列化

要了解反序列化漏洞,首先要对java的序列化有一定的了解。

java 可以将一个对象序列化成 JVM 认识的字节序列,这个字节序列里包含了该对象的数据,主要以对象属性为主。

当你再 A 平台序列化出的对象,在 B 平台上反序列化出来同样的对象。

 
序列化一个对象

被序列化的类必须要实现 Serializable 接口,否则将无法序列化对象。

序列化对象时使用对象输出流“ObjectOutputStream”,往指定输出流里写入 person 对象,这里我用的是控制台,可以直观的看到对象序列化后的样子。

这堆“乱码”就是序列化的内容了,懂 Java 的人可以看出来除了,类名,属性,和属性值以外还有 JNI 字段描述符,一眼基本上可以看出个大概来。


 
反序列化

为保证完整性,这里我将对象序列化成byte数组。再用“ObjectInputStream”反序列化回来。

输出这个反序列化后的对象,和我们一开始给 person 对象设定的属性值完全一样。

其实在最后 readObject() 那一步调用的同时,会调用 person 对象的 readObject() 方法。

Person 类写一个 readObject 方法上去,当对象被反序列化的时候,该方法就会被调用。


 
漏洞在哪儿?

理论上来说这样并不能构成漏洞,因为你序列化的仅仅只是对象的属性,并不能控制方法里的代码。那漏洞从何而来?

这里就要讲一下大家在总结 Java 反序列化漏洞时经常举的例子 “commons-collections-3.1反序列化漏洞”

这是 Apache 的一个开源工具包,被很多项目所依赖,如果使用了这个版本的 jar 包,正好又有反序列化漏洞,那就可以做到任意命令执行。

 
commons-collections-3.1反序列化漏洞

老规矩,先复现,再分析。

 复  现

使用 maven 下载依赖。

管他看懂看不懂,先执行一遍再说。

在 exec 方法处下个断点,可以看到调用栈。

 
分  析

从上面的代码得知,是因为最终遍历了“outerMap”这个Map对象的 Entry 集合,然后执行了 Entry 对象的的 setValue 方法导致执行了反射链。

所以先看这个 Map 的实现类 。

文中调用了一个静态方法org.apache.commons.collections.map.TransformedMap#decorate,这个静态方法又去new了一个TransformedMap对象。map传入的是一个普通的数据,valueTransformer则是构造的调用链。

TransformedMap是一个 Map 接口实现类。它继承了AbstractInputCheckedMapDecorator

AbstractInputCheckedMapDecorator 继承了 AbstractMapDecorator ,最后实现 Map 接口。

对象被实例化成功后,依次又调用了4个方法

  • entrySet()

  • iterator()

  • next()

  • setValue()


先来看 entrySet() 方法,TransformedMap 类没用,所以去父类里找。

这里去new 了一个内部类 

org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.EntrySet#EntrySet

EntrySet类中有一个iterator方法,也就是第二步被调用的。

这里又new了一个内部类

org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.EntrySetIterator#EntrySetIterator

这个类里的 next() 方法,也就是第三步被调用的。

同样,又new了一个内部类

org.apache.commons.collections.map.AbstractInputCheckedMapDecorator.MapEntry

里面有最后一步被调用的 setValue() 方法。

setValue 的第一行去调用了 checkSetValue 方法。

实际上调用的是 org.apache.commons.collections.map.TransformedMap#checkSetValue

这里去调用了 valueTransformer属性的 transform()方法。

valueTransformer 正是我传入的调用链。

在这部分代码中,transformerChain 变量就是我的调用链。

它在构造方法里传入了一个Transformer类型的数组,这个数组里包含了 ConstantTransformerInvokerTransformer 两种类型的对象,他们均实现了 Transformer 接口。

现在来看 transformerChaintransform 方法

这里循环调用数组每一个对象的transform方法,并传入object对象,再将执行结果赋值给object对象。

调用链中InvokerTransformer这个类同样实现了transform方法

意思就是把构造参数传进来的方法名、参数类型、参数值,通过反射去调用一遍。

所以这段代码意思就很明了了。

就是通过反射去调用Runtime.getRuntime().exec(“calc.exe”)

总结一下,这个对象是可以被反序列化,但并不会在反序列化时触发调用链,而是要经过迭代器迭代并且使用 setValue() 方法才行,正常情况下基本不会有这种场景。

不过既然被爆存在漏洞,一定有解决办法。

网上给出的办法是使用 JDK 的 sun.reflect.annotation.AnnotationInvocationHandler 类。


他的 readObject 方法里去遍历的 memberValues 这个成员变量,并且需要被调用的方法通通被调用了。

不过由于这个类是被保护的,所以需要用反射的方式来实例化。

最后给出完整代码。

这里需要注意的一点是,这个地方对jdk 1.7 有效,jdk1.8 就没有执行。

原因是 jdk1.7 和 jdk1.8 AnnotationInvocationHandler 的 readObject 方法不同。


对比会发现最主要的 setValue 方法没有被调用。

 
给 jdk 1.8续命

通过 Java 反序列化 Payload 生成神器 ysoserial 测试发现。

CommonsCollections5 CommonsCollections6 CommonsCollections7 是可以在 jdk 1.8 上执行命令的

在 CommonsCollections5 payload生成类中有这样一段注释。

上面标明了 Gadget 链,需要包含的依赖,和 JDK 版本。

 
CommonsCollections5

这个 payload 主要用到了 JDK 的 BadAttributeValueExpException 异常类。

我先给出我用来生成 payload 的代码。

这个漏洞是在 LazyMap 的 get() 方法被调用时触发的。

先来尝试运行一个 demo

前面的调用链没有什么变化,问题出在 LazyMap 上。

这里 org.apache.commons.collections.map.LazyMap#decorate(java.util.Map, org.apache.commons.collections.Transformer) new 了一个 LazyMap 对象。

它的 get() 方法在 map 中不包含参数 key 时会去调用 factory 工厂变量的 transform() 方法,开始了调用链执行。

漏洞产因知道了,触发条件就是自动调用 LazyMap.get()。

正好 org.apache.commons.collections.keyvalue.TiedMapEntry 这个类符合条件。

它有一个 map 属性,会在 toString() 、hashCode() 的时候去调用 getValue() 方法,getValue 方法又会去调用 map 属性的 get() 方法。

所以可以这样构造

当执行了以下方法时就会被触发

这种场景还是很比较常见的,但还是不能完美的做到在反序列化的时候触发。

在 JDK 1.8 的 BadAttributeValueExpException 异常类正好符合所有条件。

看一下他是怎么做到自动触发的。

在反序列化的时候 会去调用 valObj 变量的 toString 方法

valObj 是当前对象的 val 属性。

根据我刚才给出的 payload 生成代码,可以构造出一个符合要求的 BadAttributeValueExpException ,当该对象被反序列化时就会自动执行命令。

 
CommonsCollections6

问题出在 HashSet 反序列化的时候。

这个过程就比较繁杂了,我直接放上一个调用栈。

之前提到过 TiedMapEntry  这个类一旦被调用了 hashCode 方法就会触发。

是在 put:611, HashMap (java.util) 这里发生的,反序列化时添加数据会对 Key 进行一次 hash 运算,其中调用了 hashCode 方法

 
CommonsCollections7

CommonsCollections7 也是可以在 jdk1.8 平台上执行的。

这里给出我生成 payload 的代码。

关键点在 Hashtable ,在 HashtablereadObject 方法里有这样一段代码

他去遍历了 hashtable 的元素并执行了 reconstitutionPut() 方法

在 for 循环里的 if 表达式中他调用了 Entry 键对象的 equals 方法

LazyMap 里没有 equals, 所以到了AbstractMapDecoratorequals方法里。

这里他判断如果 equals() 方法传来的对象和当前对象不相等则调用 map 属性的 equals

map 是个 hashmap 它去调用了 abstractMap 的方法

 参  考

浅显易懂的JAVA反序列化入门:https://xz.aliyun.com/t/4711



凌天
实验室

凌天实验室,是安百科技旗下针对应用安全领域进行攻防研究的专业技术团队,其核心成员来自原乌云创始团队及社区知名白帽子,团队专业性强、技术层次高且富有实战经验。实验室成立于2016年,发展至今团队成员已达35人,在应用安全领域深耕不辍,向网络安全行业顶尖水平攻防技术团队的方向夯实迈进。



“阅读原文”我们一起穿越安百信息安全资讯平台~

本文源自微信公众号:凌天实验室

人已赞赏
安全工具

加密攻击稳步增长,网络犯罪分子瞄准非标准端口

2019-10-14 14:04:24

安全工具

Evernote Chrome扩展漏洞分析

2019-10-14 14:04:31

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索