CommonsCollections3
CC3和CC1很像,我的java版本是1.8.0_301,反序列化时失败。网上查了应该是jdk版本的问题,下面会分析问题原因。
分析
如图,这段代码看起来就很熟悉了,和CommonsCollection1很像。
先创建了templatesImpl对象,然后创建了ChainedTransformer
先创建个空的ChainedTransformer,在最后将ChainedTransformer对象的iTransformers修改为真实的Transforms,这种方式前面遇到很多次了,是为了防止在构造利用链时触发命令执行。
再看一下transformers对象:
代码语言:javascript复制Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl})
};
这里和CommonsCollection1不一样,有了新面孔:InstantiateTransformer;
如图,它的构造方法是参数类型和参数,transform方法返回的是实例化的对象。
所以,这个transformers相当于return new TrAXFilter(templatesImpl);
紧接着下面的代码,似乎和CommonsCollection1一模一样。
现在暂时还不清楚的是TrAXFilter这个类,它是做什么的?
暂时先跳过,先分析下反序列化时,会经过的流程。
回忆一下CommonsCollection1,
首先是handler对象反序列化时调用了streamVals.entrySet,而streamVals.entrySet是LazyMap的代理类,调用代理类的所有方法,都会进入invoke方法。
invoke方法如图,当调用它的任意一个除toString、hashCode、annotationType外的参数为空的方法都会调用memberValues.get,而memberValues是LazyMap对象。
如图,当调用LayMap的get(key)方法时,如果key不存在,则会通过Transformer对象生成一个键值对。
而这里的transform对象是开始时构建的transformer对象,
本篇开始时分析过,transformer对象的transform方法相当于return new TrAXFilter(templatesImpl);
在调用LazyMap的get方法是,获得的就是new TrAXFilter(templatesImpl);
所以,相当于,super.map.put("entrySet",new TrAXFilter(templatesImpl));
下面分析下这句话,如何造成rce。
调试
调试下发现反序列化时报错,序列化没问题。
问题出在这里,之前分析的,memberValues对象应该是LazyMap类型。
而这次调试,memberValues对象是LinkedHashMap类型,导致反序列化失败。
之前因为是LazyMap,get一个不存在的键会触发Transformer,造成rce,这里用了LinkedHashMap,get一个不存在的键,所以会报错java.lang.Override missing element entrySet。
为什么序列化时用的是LazyMap,反序列化就变成了LinkedHashMap?
回想一下之前反序列化篇讲过的序列化过程:
反序列化时是通过递归,由内向外的。最外层的handler的memberValues是代理map,代理map里是LazyMap,LazyMap里是HashMap。
问题发生在AnnotationInvocationHandler类的invoke方法,调用memberValues的get方法,而memberValues的get方法是在AnnotationInvocationHandler反序列化时触发的。
而最外层的handler和代理map都是AnnotationInvocationHandler类型,因为是由内而外反序列化,所以,先反序列化代理map,所以问题出现在代理map反序列化时,再看下AnnotationInvocationHandler的readObject方法。
调试时发现这个函数调用了两次,第一次是handler的,第二次是代理map的,可以看见第二个红框,创建了LinkedHashMap对象mv。第一次调用,如图,streamVals还是LazyMap类型。
第二次调用,如图,idea已经不知道是什么类型了。
问题出现在readObject函数最后一行,将memberValues的值修改为了mv。所以,下一次代理类调用readObject时,memberValues就是LinkedHashMap类型了。
java这不是胡来嘛,反序列化时怎么把对象类型还改了?就不怕java项目实际应用时出问题吗?
其实想一下,序列化和反序列化是相对的。一个正常的map序列化后,键值对应该都存在于序列化数据中,在反序列化时应该直接从数据中读取。所以像LazyMap这样生成新数据,肯定不是正常操作,反序列化目的是还原对象而不是生成新对象,所以确实没必要用LazyMap。
假设是正常反序列化过程,反序列化前后handler的memberValues类型变了,这不管了,java这么做肯定有他的道理。
既然不能调试,那就试试静态分析吧。
静态分析
反序列化流程开始已经分析过了,重点分析super.map.put("entrySet",new TrAXFilter(templatesImpl));
先看下TrAXFilter的构造方法,如图:
这,看红方框,调用了templates的newTransformer方法。
上一篇学过的,newTransformer方法会加载templates里构造好的命令执行字节码,命令执行代码在静态代码块,所以加载字节码就会造成rce。
总结
这一篇结合了之前学过的CC1和CC2的知识点:
· 构造handler对象,在反序列化时会导致调用Transformer(CC1学过的);
· 构造templates对象,只要调用它的netTransformer方法就会执行命令(CC2学过的);
· 新知识点:使用templates做参数,创建TrAXFilter对象,会调用netTransformer方法。TrAXFilter对象的构造函数,刚好将1、2两条链连起来。
除了分析CC3,还分析了jdk高版本不能使用CC1和CC3链的原因,
因为AnnotationInvocationHandler的readObject方法变了,在方法的最后一行,将memberValue值改为了LinkedHashMap对象,导致调用memberValue的get方法,不会触发Transformer链。