java反序列化漏洞复现
JAVA反序列化分析思路
从危险函数出发开始寻找不同类的同名函数实现任意方法调用(反射/动态加载字节码)
反射基础
r是一个Runtime实例,c是一个Runtime类,对c使用反射(getMethod)来获取exec方法,然后在r上调用这个方法
漏洞复现
CC1
1 |
|
transformer
InvokerTransformer(反射利用点)
首先是由InvokerTransformer类中的transform实现了任意方法调用的同时还在构造函数中导致参数可控
上面的java反射就相当于
(实际上自己构造的时候在IDE中直接看需要填入的变量还是挺方便的,这里不太好看懂)
如以下语句就可以实现命令执行
将Runtime实例传入transfrom,cls就获取了Runtime类,method获取了Runtime类中的exec方法,然后对Runtime实例使用传入的传输calc
poc如下
最后的目标是回到readObject,所以此时要寻找不同的方法来调用transform
MAP
在TransformedMap类里面可以找到调用了transfrom方法的位置
而valueTransformer是TransformedMap构造函数中的一部分,也就是可控
如下构造就可以对invokerTransformer调用tansform方法(由于构造函数是保护的,这里用到decorate装饰器)
继续向上寻找发现是TransformedMap的父类的MapEntry类重写了setValue方法
则可以如下,构造一个TransformedMap并且调用setValue即可(让TransformedMap的value为构造的invokertransformer然后通过setValue调用transform)
现在则需要一个可以遍历数组的地方并且需要value可控或者不同名的调用了setValue
AnnotationInvocationHandler
pop链的最后一步
满足两个if条件后即可调用setvalue
但是这个类是一个default类,需要用反射创建,并且可以看见他的构造函数中的第一个参数是一个注解类(如Override)
此时有三个问题:
1.如何通过if判断来进入这个setvalue中
2.setValue貌似不可控
3.Runtime对象本身是不可序列化的,需要通过反射
Runtime实现反射调用
解决Runtime不可序列化的问题
正常情况,由于Runtime对象构造函数是私有的,需要通过getRuntime方法来获取这个对象
上图,通过.class获取了Runtime类,然后getMethod反射获取getRuntime方法,然后调用这个方法,获取r这个Runtime实例化对象,最后反射获取exec方法并且在r上调用
转换为InvokerTransform类中则是
chainedtransformer
这时候通过chainedtransform实现循环调用来使得调用更加简洁
进入if语句
1.通过getKey来获取传入的memberValue中的键是否存在于menberTypes中
如上,反序列化入口获取了type参数也就是下面的Override.class
接着获取了这个类中的成员方法,检查poc中传入的键在这个类中的成员方法终会是否存在
则传入一个有成员方法的类并且将key改成他的一个方法名
如Target的value方法
如上修改即可2.
即检查是否可以强转,这里不需要绕过
实现可控SetValue
正常而言,这里会调用
也就是这里的transform的value不可控
也就相当于要把下面的语句换成上面的语句(实现value可控)
ConstantTransformer
其transformer方法直接返回自身的一个变量同时可控
就可以利用它通过Runtime.getRuntime()创建一个Runtime实例
此时即可转换成poc中的
pop链回溯
通过反射实例化了AnnotationInvocationHandler类—>对他的value遍历并且调用setvalue,调用TransformedMap的父类的MapEntry#SetValue–>transform方法(这一步为了实现可控SetValue借助到ConstantTransformer类)—>在value中,我们恶意构造的chainTransformer类实现命令执行
总结
第一次看java的链子,看了点反射跟基本语法就强行看完了,感觉看懂了但是让自己复现还是很困难(语法不清晰),写的逻辑不太清晰
注意:以上的链子是利用TransformedMap回调调用+AnnotationInvocationHandler类调用SetValue来到transfrom方法,YSO链子的Lazymap需要利用动态代理
本质上TransformedMap和Lazymap都只是一个装饰器,TransformedMap是回调调用(新加入一个键值对的时候调用transform) LazyMap是懒加载(get找不到值的时候去触发transform)
CC6
为了解决sun.reflect.annotation.AnnotationInvocationHandler#readObject在8u71后的变化(不再直接调用自定义的map而是弄了一个LinkedHashMap)
Map
首先看一下LazyMap,他的get方法调用了transform
又回到了如何调用LazyMap#get(cc1demo利用的是Hashmap,yso的链子用的是动态代理调用lazymap)的问题,这里利用了TiedMapEntry#getValue,
hashCode方法调用了getValue
hashmap的hash方法调用了hashcode
他的readObject方法又调用了hash方法
则得到链子
1 |
|
同样写一个chain,decorate绑定到outputmap上面
再把这个恶意的map绑定到TME的key上,最后构造一个hashmap,将TME放到他的key上
问题
调试发现,反序列化的时候并没有进入tranfrom方法,在map中判断存在这个叫做keykey的key
这是因为hashmap#put的时候触发了对TME的hash操作
所以最后添加上remove操作
总结
跟cc1很像的链子,主要是这次分析换成了LazyMap,绕过了jdk8u71
CC3
链子
利用字节码打的cc链
首先记住 可以通过newTransformer和getOutputProperties来RCE
cc3的关键在于TrAXFilter类的构造方法调用了newTransformer来绕过InvokerTransformer的限制
至于如何调用这个类的构造方法,利用类InstantiateTransformer
字节码
需要保证name为空 给class赋值
bytecodes不为空,同时对于_tfactory需要存在调用getExternalExtensionsMap的时候不返回空
因为他是TransformerFactoryImpl类,给他赋一个即可
对于TrAXFilter的赋值
需要保证以下调用链的正常
写一个反射修改属性的函数
构造合法的恶意类
首先需要继承AbstractTranslet
注意name和tfactory
总结
链子剩余的部分和cc1 cc6相同,主要是会写恶意字节码类