对TransformMap
调用了setValue()
,但因为他的父类是AbstractInputCheckedMapDecorator
其中的MapEntry
继承AbstractMapEntryDecorator
,而这个AbstractMapEntryDecorator
实现了Map.entry
部分,同时其子类MapEntry
重写了setValue
,所以TransformMap
在Mapentry轮询的时候调用setValue
调用的是他爹的
AnnotationInvocationHandler因为不是公共类,所以只能通过反射的形式获取而后实例化
1 | Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler"); |
循环调用部分
1 | Method getruntimemethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class); |
可能比较难理解的是InvokerTransformer
里面这个部分
1 | public Object transform(Object input) { |
这里第一步先传入一个Runtime.class
再反射获取getMethod
方法,然后再通过invoke,这里iArgs传参的是("getRuntime,null")
,相当于执行Class.getMethod("getRuntime",null)
,所以返回的是个getRuntime
的method
然后是第二个
1 | Runtime runtime = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getruntimemethod); |
这一部分说白了就是getruntimemethod.invoke(null,null)
getruntimemethod
是第一步返回的执行getRuntime
的method
,这属于调用反射的invoke
给它执行下就是,所以返回的是个Runtime
。
谁来调用ChainedTransformer
1 | InvokerTransformer[] Testtransformers = new InvokerTransformer[]{ |
这里ChainedTransformer
我们写到了到TransformedMap.decorate(map,null,TestTransformer)
所以当最外层的AnnotationInvocationHandler
实现类反序列化时候,ReadObject
之中的MapEntry
轮询在调用到memberValue.setValue()
也就是调用我们给他的TransformedMap
的setValue()
而TransformedMap
它的父类重写了setValue
所以又会去调它TransformedMap.checkValue(Value)
的时候会调用到他的valueTransformer.transform(value)
,这里valueTransformer
就是我给他的ChainedTransformer
那自然也就调用了ChainedTransformer.Transformer(Value)
ChainedTransformer
调用的时候需要输入value
但他这里已经写死了是个AnnotationTypeMismatchExceptionProxy
,我们如何将他ChainedTransformer
数组最初输入执行的做一个Runtime
类
1 | Object value = memberValue.getValue(); |
首先看我们的最初的ChainTransformer
1 | public Object transform(Object object) { |
确实,Transformer
数组保持如下写法在输入的时候会把AnnotationTypeMismatchExceptionProxy
会通过
setValue
->checkValue
->valueTransformer.Transformer(Value)
给传入,污染的执行数组的初始value。
1 | Transformer[] transformers = new Transformer[]{ |
所以这里数组中第一个我们需要添加一个,会无视传入的value
并且可以满足在轮询执行的第一轮会返回Runtime.class
的一个Transformer
类。
所以我们引入了ConstantTransformer
,他构造传入的Object
,即便调用transform(value)对他传参,它返回的还是构造时候传入的Object
1 | public ConstantTransformer(Object constantToReturn) { |
所以我们可以通过在第一个使用ConstantTransformer(Runtime.class)
来让AnnotationTypeMismatchExceptionProxy
输入的setValue(Value)
废掉,并使得第一个会直接返回Runtime.class
以供后面的使用。
InvokerTransformer
第一次调用时候获取的是method
这里可能会比较绕
一开始想为什么这里不直接调用getRuntime
,而是去调用getMethod
让他返回getRuntime
的Method
,主要还是Runtime的序列化问题,其次
InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},neew Object[]{"getRuntime",null})
因为,我们传入的是Runtime.class
,进入之后cls
相当于是Runtime.class.getClass()
1 | Class cls = input.getClass(); |
那cls
当然是没有getRuntime()
能被获取到
但getMethod
是有的,所以可以获取cls
的getMethod
然后反射执行相当于Runtime.class.getMethod("getRuntime",null)
所以返回一个getRuntime
的Method
,之后再由其他几个InvokerTransformer
往后执行。
差不多就这个意思
1 | Method th = (Method) Runtime.class.getClass().getMethod("getMethod", String.class, Class[].class).invoke(Runtime.class,new Object[]{"getRuntime",null}); |
getMethod
套一个getMethod
确实稍微有点绕,只要牢记getMethod
的两个参,前一个是方法名,后一个是变量类型就行。
invoke
也是同理,牢记前面是obj
后面是传入的参就行。
另一条cc1
正向来说,前半部入口是AnnotationInvokcationHandler
运行到memberValues.entrySet()
再调用一个做动态代理类用的AnnotationInvokcationHandler
类
1 | for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) { //<-这里调用代理类的.entrySet() |
而这个代理类AnnotationInvokcationHandler
又是去代理的lazymap
的一个实现类,所以动态代理中memberValues
就是lazymap
1 | class AnnotationInvocationHandler implements InvocationHandler, Serializable { |
我们要通过memberValues.entrySet()
触发的invoke
走到最后触发memberValues.get()
他这里要求传入的method
也就是调用的方法,必须是空参调用才能走到我们目标的memberValues.get(member)
题外话:这里其实原本设计的是要传入注解方法的,所以才做的这些个判断
1 | public Object invoke(Object proxy, Method method, Object[] args) { |
中间相较于第一个cc1
区别是用lazyMap
替换了TransformedMap
往后的都是一样,用的lazyMap.get()
中会调用factory.transform()
1 | public Object get(Object key) { |
再简单捋一下这条链子,先是有了LazyMap.get()
再往上走发现了AnnotationInvokcationHandler
的invoke
里有get()
所以也就说明AnnotationInvokcationHandler
在作为LazyMap
的动态代理调用方法的时候可以执行到LazyMap
的get
但是这里有个if
导致需要动态代理
在执行无参调用
的方法
才可以走到get()
巧合的是他AnnotationInvokcationHandler
本身ReadObject
的时候就会调用一个memberValues.entrySet()
就省得再去找别的ReadObject
的了。
所以我们就可以新建一个AnnotationInvokcationHandler1
作为LazyMap
的动态代理
而后再新建一个正常的AnnotationInvokcationHandler2
把上一个AnnotationInvokcationHandler1
塞进去
对AnnotationInvokcationHandler2
正常序列化
再反序列化的时候就readObject
->.entry()
->invoke
->.get(Value)
->.transformer(Value)
后面就和一开始那条链一样了
cc6为什么Lazymap
需要处理一下删除掉其中map
的key
因为我们在TiedMaoEntry
将其put
到Hashmap
的时候,会触发hashcode()
1 | TiedMapEntry ti = new TiedMapEntry(lazymap,"aaa"); |
最终走到Lazymap
这里,并把外部的TiedMapEntry
的value
最终作为key
put给lazymap
的map
当中
1 | public Object get(Object key) { |
所以当触发反序列化的时候我们的put
时候lazymap
中的key
和value
会下面判True
也就自然调用不了transform
1 | if (map.containsKey(key) == false) |
解决访问这里两种
1.lazymap.remove(TiedMapEntry传入的value)
,让lazymap
这里map
中的table[0]
变为空
2.修改掉这个键值对,让他换成别的反正key
别和TiedMapEntry传入的value
一样就行(还是推荐第一种 我比较闲才会用这种)
cc3的过程
1 | InstantiateTransformer(paramstype,params) -> transformer(TrAXFilter.class) -> TrAXFilter(tp) -> .newTransformer() -> TemplatesImpl.newTransformer() -> .getTransletInstance() -> .defineTransletClasses() -> loader.defineClass(_bytecodes) -> newInstance() |
刚接触时其中容易不好理解的部分是InstantiateTransformer.transformer
中实例化TrAXFilter
的部分,所以需要正向简单过一下流程
将TrAXFilter.class
通过InstantiateTransformer.transformer
中获取构造器,而后实例化
1 | public Object transform(Object input) { |
TrAXFilter
实例化时被我们传入了TemplatesImpl
实例化时他会直接调用我们传入TemplatesImpl
的newTransformer
方法
1 | public TrAXFilter(Templates templates) throws |
在TemplatesImpl.newTransformer
中会调用getTransletInstance
1 | public synchronized Transformer newTransformer() |
然后getTransletInstance
中就会调用到TemplatesImpl
自己defineTransletClasses
方法来给_class
赋值
1 | private Translet getTransletInstance() |
通过我们反射修改读取test.class
传入的_bytecodes
二维数组,在这里获取类
1 | private void defineTransletClasses() |
又回到外层newTransformer
对我们传入的class
进行实例化最终实现rce
1 | AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance(); |
这个因为最外层的InstantiateTransformer
也是transform
所以和cc6以及cc1的chain中的部分可以做替换
1 | Transformer[] transformers = new Transformer[]{ |
cc4流程和一些容易出问题的点
首先,cc4是commons.collections4
的
transformingComparamtor
中的compare()
方法会触发transform
1 | public int compare(final I obj1, final I obj2) { |
往前走找到个PriorityQueue
类的readObject
->heapify()
->siftDown()
1 | private void heapify() { |
往下走comparator
不为空,调用siftDownUsingComparator()
1 | private void siftDown(int k, E x) { |
siftDownUsingComparator
里面就调用了comparator.compare()
1 | private void siftDownUsingComparator(int k, E x) { |
他那个comparator
是构造方法里直接传的
1 | public PriorityQueue(int initialCapacity, |
所以可以替代像cc3
这类链条的前半段
HashMap.readObject()->TideEntrymap.hashcode()->Lazymap.get()
这部分
整个前半段流程就是PriovrityQueue.readObject()
->heapify()
->siftDown()
->siftDownUsingComparator
->TransformingComparator.compare()
->transformer.transform()
比较容易出问题的是heapify()
中size>>>1
的话才能进入siftDown
方法
1 | private void heapify() { |
所以这里我直接用反射改的size
为2就可,yso则是另一种方式
直接priorityQueue.add(1)
加了两个,让他数组里有东西就行
然后它add()
和hashmap.put
那个问题差不多,它自己又会调用priorityQueue.offer()
里面又会调用siftDown
最后就给执行了。
所以这里和cc6
前半段一样处理就行,把执行路径中的一个给扬了,后面反射再给加回来就行。
所以可以直接选一个倒霉蛋比如TransformingComparator
本来要传入的chaintansformers
1 | ChainedTransformer chaintransformers = new ChainedTransformer(transformers); |
直接改成ConstantTransformer(1)
1 | TransformingComparator transformingComparator = new TransformingComparator<>(new ConstantTransformer(1)); |
让链子执行到TransformingComparator.compare()
时候调用个寂寞。
cc2的流程和一些小细节
cc2
其实可以看作是cc4
将链条的中段
chainTransformer.transfrom()
->InstantiateTransformer.transform()->TrAXFilter()->TemplatesImpl.newTransformer()
这一大段替换为直接用InvokerTransformer().transfrom
反射执行TemplatesImpl.newTransformer()
说白了就是想办法给InvokerTransformer
的Transform(value)
传入TemplateImpl
以反射调用的他的newTransfromer()
这里找到可以用PriorityQueue.add
将TemplatesImpl
当作参数给传入,一直到链条最后让invokeTransformer.transform(value)
来执行
下面的add
会将传入的值添加到queue
数组
1 | PriorityQueue |
这里给它用add
传入观察一下参数怎么才能传到InvokerTransformer
这里传入的在heapify
中被以轮询的形式传入的siftDown()
然后在这里又被传入到comparator.compare()
调用到了TransformingComparator.compare()
部分,可以看到最初传入的1
作为参数被传入给了.transformer(obj1)
既然能传过去就把1
替换为TemplateImpl
即可实现后续的反射调用newTransform()
所以这就是cc2
与cc4
不同的地方,cc4
中段和cc3
一样是是依赖于类的实例化来最后实现调用TemplateImpl.newTransform()
cc2
是直接用了cc4
的前段直到触发transform
的部分
而中段直接传参TemplateImpl
给invokerTransformer.transform()
来反射调用TemplateImpl.newTransform()
当然,这也是得益于这个参数能传的下来..