|
本帖最后由 luozhenni 于 2022-4-14 11:05 编辑
CC gadget
原文链接:https://phisabella.github.io/2022/02/22/CC-gadget.html
CC链利用点
反序列化理解
说起Java RCE,第一反应就是要构造并执行Runtime.getRuntime().exec("calc")这句代码(或者别的RCE点如7u21),而在Java反序列化中会自动触发readObject()方法,也就是说,如果要利用反序列化达到RCE的效果,就需要找到某个修改了readObject()的类,并且修改内容里有能构造(通过一系列调用)Runtime.getRuntime().exec("calc")的方法。
CC链利用总结
分类
调试完七条CC链后,能够发现其实7条CC链使用了两种思路:
1.[Chained-->Constant-->Invoker]Transformer相互组合构造RCE
2.通过TemplatesImpl利用7u21 jdk本身的RCE
当然从CC链挖掘利用的顺序来讲24是一组,13是一组,567在是CC链在1.8中的适配和改进
CC1/CC5/CC6/CC7
CC1中用了LazyMap的get()函数来调三个Transformer,再用重写了了readObject的AnnotationInvocationHandler来调get()
因为AnnotationInvocationHandler被ban,因此替换为BadAttributeValueExpException,即CC5
随后又衍生出CC6(HashSet)和CC7(Hashtable)增加CC链的适用性
CC2/CC3/CC4/CB1
CC2没用三个Transformer的组合,用的是7u21RCE,为此引入了TemplatesImpl,为了调用TemplatesImpl的任意方法(newTransformer)使用了InvokerTransformer来反射执行,TransformingComparator的compare来调transform,使用PriorityQueue来调compare
CC3将CC1和CC2结合,用InstantiateTransformer代替InvokerTransformer,调transform前和CC1一样,后面和CC2一样
CC4将CC2和CC3结合,用ConstantTransformer代替TransformingComparator来调transform,其他和CC2一样
(题外话:CB1链其实和CC2有些相似,都是PriorityQueue为入口,TemplatesImpl来执行,区别在于中间调用部分,CC2用的是TransformingComparator(InvokerTransformer),而CB1用了BeanComparator;CC2在siftDownUsingComparator 调的是InvokerTransformer的compare,而CB1调的是BeanComparator的)
四个Transformer
InvokerTransformer和InstantiateTransformer在CC链的地位类似,都是负责最后一步的RCE,
不过InstantiateTransformer在CC3和CC4中配合TemplatesImpl实例化造成7u21 RCE,
InvokerTransformer则在CC3其余5条链中使用,CC2中配合TemplatesImpl,1567配合ConstantTransformer和ChainedTransformer
onstantTransformer和ChainedTransformer
InvokerTransformer | ConstantTransformer | ChainedTransformer | InstantiateTransformer | 构造函数接受三个参数 | 构造函数接受一个参数 | 构造函数接受一个TransFormer类型的数组 | 构造函数接受两个参数 | transform方法通过反射可以执行一个对象的任意方法 | transform返回构造函数传入的参数 | transform方法执行构造函数传入数组的每一个成员的transform方法 | transform通过反射的方法返回传入参数input的实例 |
五个反序列化入口类
AnnotationInvocationHandler | PriorityQueue | BadAttributeValueExpException | HashSet | Hashtable | 调lazyMap(CC1/CC3) | 调TransformingComparator(CC2/CC4) | 调TiedMapEntry(CC5) | 调HashMap(CC6) | 调lazyMap(CC7) | 反序列化的时候会循环调用成员变量的get方法 | 反序列化的时候会调用TransformingComparator中的transformer的tranform方法 | 反序列化的时候会去调用成员变量val的toString函数(TiedMapEntry的toString函数会再去调自身的getValue) | 反序列化的时候会去循环调用自身map中的put方法 | 当里面包含2个及以上的map的时候,循环调用map的get方法 | 三个Map
lazyMap | TiedMapEntry | HashMap | CC1/CC3/CC5/CC6/CC7 | CC2 | CC6 | 通过调用lazyMap的get方法可以触发它的成员变量factory的tranform方法 | 通过调用TiedMapEntry的getValue方法实现对他的成员变量map的get方法的调用 | 通过调用HashMap的put方法实现对成员变量hashCode方法的调用(TiedMapEntry的hashCode函数会再去调自身的getValue) | CC链典型RCE点
CC链中最典型的构造组合是,[Invoker、Constant、Chained]Transformer相互组合构造Runtime.getRuntime().exec("calc"),它们都实现了TransFormer这个接口,都有一个transform方法,ConstantTransformer可以执行Runtime,InvokerTransformer可以凑出后续的getRuntime().exec("calc")部分,在ChainedTransformer的transform方法里可以将上述两者拼接成一条完整的RCE命令。
典型代码如下:
- Transformer[] transformers_exec = new Transformer[]{
- #先实例化
- new ConstantTransformer(Runtime.class),
- new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
- new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}),
- new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
- };
- Transformer chain = new ChainedTransformer(transformers_exec);
- #调用transform方法
- chain.transform("qwe");
复制代码
下面逐个介绍
ConstantTransformer除了CC2都用到了
transform方法会返回构造函数的参数iConstant。因此实例化时传入Runtime.class,也会返回一样的内容。只需要调用transform方法。
InvokerTransformer
12567
` transform用到了反射,执行了某个对象的某个方法,并且相关参数也是构造函数里的可控参数,因此也只需要调用transform`方法即可。
ChainedTransformer
除了CC2以外都用到了
正好,ChainedTransformer 的transform函数会遍历构造函数中的Transformer数组,并调用数组中的每一个成员的transform方法,并且上一个成员调用返回的对象会作为下一个成员transform的参数,也就是说,只需要调用ChainedTransformer 的transform方法即可构造出Runtime.getRuntime().exec("calc")
那么问题来了,怎么触发ChainedTransformer的transform方法?
InstantiateTransformer
在CC3和CC4中使用
CC链调试——触发transform
如果用一个词来总结CC链,那一定是“transform”,不管怎么变化,用什么类来改写还是调用,最终都是要想办法调用四个transform里的某一个某几个transform的transform()函数。
TransformedMap
虽然没在CC链里出现,还是可以看一看找利用链的思路是什么样的
注:因为1.8删去了关键的setValue方法,因此只能在1.7下使用
思路
从挖洞的思路来讲此时应该搜索transform(关键字来寻找有相关调用的类,但既然是复现就直接跳过这一步,找到TransformedMap类,
TransformedMap类里有三个transform调用,keyTransformer 和valurTransformer都是Transformer类型且可控,所以可以将其值赋为ChainedTransformer,但是这三个方法都是protected不能直接调用,找到4个public方法,其中有一个put()方法,调用了transformKey以及transformValue,这两个方法又都调用了transform方法,所以TransformedMap类可以满足需求
只需要实例化一个TransforomedMap对象,然后调用对象的put方法,就可以RCE。
但是并不能在反序列化中触发,因为没有readObject()类,还需要一个重写readObject()类的方法,并能调用上述的方法。
一般来讲会采取向上回溯找方法(transformKey、transformValue、checkSetValue)调用位置,或者全局搜索readObject()看看有没有哪个类直接就调用了这三个方法中的一个或者readObject中有可疑的操作,最后能够间接触发这几个方法。
从名字就能推断TransformedMap其实是Map类型,范围就可以扩大到引用了Map的readObject()类,根据摸索git得到TransformedMap里的每个entryset在调用setValue方法时会自动调用
TransformedMap类的checkSetValue方法
因此就变成了找一个对Map类型的属性的entry进行了setValue操作的readObject()类,刚好sun.reflect.annotation.AnnotationInvocationHandler类就可以满足需求(但jdk1.8已经没有了setValue)
但有一个条件,if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy))(其实就是var1不能被转化为var2即可)
从截图代码可知,需要知道this.type是什么,
是一个可控的,需要继承Annotation,那就应该是Annotation的子类(所有注解类型的公用接口),于是找到java.lang.annotation.Retention.class这个注解类(map的键必须有”value”),但这个类的访问权限不是public,而是包访问权限,构造poc的时候只有通过反射机制来实例化它,至此构造结束。
调用链
- sun.reflect.annotation.AnnotationInvocationHandler(readObject()的setValue) –>
- TransformedMap(checkSetValue 调用this.valueTransformer即ChainedTransformer的transform()) –>
- ChainedTransformer循环调用数组的transform() –>
- ConstantTransformer and InvokerTransformer(RCE)
复制代码
CC1–LazyMap+InvokerTransformer(3.1-3.2.1,jdk<1.8)
CommonsCollections版本主要是针对在ysoserial里的
思路
LazyMap有this.factory.transform(key),而this.factory可控,factory传ChainedTransformer即可
然后找调用get的地方,老朋友AnnotationInvocationHandler的invoke符合条件,this.memberValues也是可控的(上个思路截图),传入LazyMap即可
找触发AnnotationInvocationHandler.invoke()的地方
动态代理
被动态代理的对象调用任意方法都会通过对应的InvocationHandler的invoke方法触发
(可以将InvocationHandler接口类看做一个中介类,中介类持有一个被代理对象即真实对象,在invoke()方法中调用了被代理对象相应的方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能,即无侵入式的代码扩展,不用修改源码)
所以此时只需要创建一个LazyMap的动态代理,动态代理调用某个方法,但是反序列化需要readObject,即找一个类,其readObject方法可以通过动态代理调用LazyMap的某个方法(和直接调用LazyMap某个方法需要满足的条件几乎是一样的,因为某个类的动态代理与它本身实现了同一个接口)
AnnotationInvocationHandler的readObject方法会调用某个Map类型对象的entrySet()方法,而LazyMap以及他的动态代理都是Map类型,可以满足需求
调用链
- sun.reflect.annotation.AnnotationInvocationHandler(readObject()的get) –>
- LazyMap(this.factory.transform(key),即ChainedTransformer的transform()) –>
- ChainedTransformer循环调用数组的transform() –>
- ConstantTransformer and InvokerTransformer(RCE)
- /*
- Gadget chain:
- ObjectInputStream.readObject()
- AnnotationInvocationHandler.readObject()
- Map(Proxy).entrySet()
- AnnotationInvocationHandler.invoke()
- LazyMap.get()
- ChainedTransformer.transform()
- ConstantTransformer.transform()
- InvokerTransformer.transform()
- Method.invoke()
- Class.getMethod()
- InvokerTransformer.transform()
- Method.invoke()
- Runtime.getRuntime()
- InvokerTransformer.transform()
- Method.invoke()
- Runtime.exec()
- Requires:
- commons-collections
- */
复制代码
CC2–PriorityQueue+TemplatesImpl(4.0,jdk<=7u21)
有个不同点在于没有构造Runtime,而是使用了7u21的RCE,简言之只要能调用包含恶意字节码的TemplatesImpl对象的利用链中的任意函数(getOutputProperties、newTransformer等等),就能造成RCE
思路
因此思路也有变化,只用到了InvokerTransformer(3.22被拉黑但是4.0又可以用了,然后又被拉黑了)
首先得想办法触发TemplatesImpl的函数,正好InvokerTransformer就可以用反射执行某个对象的任意方法,这里传入newTransformer方法
然后就是想办法调用transform方法,找到了TransformingComparator,
需要找一个调用其compare的类,找到PriorityQueue,其siftDownUsingComparator符合条件,把TransformingComparator放到PriorityQueue里就能调用到PriorityQueue的compare方法
为什么选PriorityQueue,因为其重写了readObject,而且,会调用heapify(),可以一路调用到siftDownUsingComparator,并且可传入任意类型的对象,可以顺利引入TemplatesImpl
至此利用链结束
调用链
PriorityQueue的readObject调用自己的heapify,一直调用到siftDownUsingComparator ,方法内调用compare–>
InvokerTransformer的compare调this.transformer.transform–>
即InvokerTransformer的transform被调用–>
一直被传递的TemplatesImpl的newTransformer被invoke,进入7u21 RCE
- /*
- Gadget chain:
- ObjectInputStream.readObject()
- PriorityQueue.readObject()
- ...
- TransformingComparator.compare()
- InvokerTransformer.transform()
- Method.invoke()
- Runtime.exec()
- */
复制代码
CC3–LazyMap+InstantiateTransformer+TemplatesImpl(3.1-3.2.1,jdk<=7u21)
和CC1基本一样,区别在于,RCE的点还是用的CC2的TemplatesImpl,但是反射调用的是InstantiateTransformer而不是InvokerTransformer
还是和CC2一样的,如果要TemplatesImplRCE,需要找newTransformer被调用的地方,InstantiateTransformer的transform可以生成任意类的实例,能够顺利进入RCE利用点,
而InstantiateTransformer自己的transform又可以用CC1的LazyMap来调用ChainedTransformer的transform调用,LazyMap的entrySet方法又被AnnotationInvocationHandler的readObject调用,构造完成。
- * Variation on CommonsCollections1 that uses InstantiateTransformer instead of InvokerTransformer
复制代码 CC4–PriorityQueue+InstantiateTransformer+TemplatesImpl(4.0,jdk<=7u21)
改进了CC2,类似CC3和CC2的结合。
PriorityQueue的readObject一路调下去调的是ChainedTransformer然后到InstantiateTransformer而不是InvokerTransformer,其他都是重复的内容
* Variation on CommonsCollections2 that uses InstantiateTransformer instead of InvokerTransformer.
CC5–LazyMap(3.1-3.2.1,jdk8u76)
终于不用1.7了 jdk1.8对老朋友AnnotationInvocationHandler进行了限制,需要新的类
思路
于是找到了BadAttributeValueExpException,其实变化不大,就是调用 LazyMap.get()方法的地方变了,BadAttributeValueExpException(3.2.2被拉黑)的readObject()
有一个toString方法,即TiedMapEntry
然后就调用到get了, LazyMap.get()被调用,后面的就很熟悉了,和CC1一样,还简单一些
调用链
BadAttributeValueExpException的 readObject()–>
TiedMapEntry toString() 的getValue()–>
getValue() 调get() –>
Cha∈edTranormer.tranorm()
–>
ConstantTransformer and InvokerTransformer(RCE)
- /*
- Gadget chain:
- ObjectInputStream.readObject()
- BadAttributeValueExpException.readObject()
- TiedMapEntry.toString()
- LazyMap.get()
- ChainedTransformer.transform()
- ConstantTransformer.transform()
- InvokerTransformer.transform()
- Method.invoke()
- Class.getMethod()
- InvokerTransformer.transform()
- Method.invoke()
- Runtime.getRuntime()
- InvokerTransformer.transform()
- Method.invoke()
- Runtime.exec()
- Requires:
- commons-collections
- */
- This only works in JDK 8u76 and WITHOUT a security manager
复制代码
CC6–HashSet+LazyMap(3.1-3.2.1,jdk1.7,1.8)
CC6同样使用的是LazyMap来触发ChainedTransformer的transform,不同的是在触发LazyMap.get()时用了不同的方法
思路
CC6用了HashSet的readObject()
TiedMapEntry对象被带入put函数
在put进入hash函数,
在hashCode函数中会调用恶意的TiedMapEntry对象自身的getValue函数
getValue这里就会调用LazyMap的get函数了
然后就是熟悉的LazyMap调用链了
调用链
HashSet.readObject() 的 put–>
HashMap.put() 和hash() ,调hashCode()–>
TiedMapEntry.hashCode()和getValue(),调this.map.get(this.key) –>
LazyMap(this.factory.transform(key),即ChainedTransformer的transform()) –>
ChainedTransformer循环调用数组的transform() –>
ConstantTransformer and InvokerTransformer(RCE)
- /*
- Gadget chain:
- ObjectInputStream.readObject()
- HashSet.readObject()
- HashMap.put()
- HashMap.hash()
- TiedMapEntry.hashCode()
- TiedMapEntry.getValue()
- LazyMap.get()
- ChainedTransformer.transform()
- InvokerTransformer.transform()
- reflect.Method.invoke()
- java.lang.Runtime.exec()
- */
复制代码
CC7–Hashtable+LazyMap(3.1-3.2.1,jdk1.7,1.8)
思路
这次是Hashtable的readObject()了
会调用readHashtable,
然后是reconstitutionPut,这里需要满足e.hash == hash ,才能到e.key.equals(key)的equals(),因此需要构造hash相同的两个lazymap强制进行比较
注:因为两个lazymap的hash相同,所以hashtable放进第二个lazymap时,会把第一个lazymap的key值"yy"放到第二个lazymap中(首先lazymap.get(‘yy’)尝试从第二个lazymap中拿),此时将导致lazymap2中新添加yy->processImpl键值对,造成第二个lazymap空间大小为2,和第一个不一样,hash不同无法继续,改成lazyMap2.remove("yy")即可
equal判断的时候就会去取值,即调用get(),进入LazyMap调用链
调用链
- /*
- Payload method chain:
- java.util.Hashtable.readObject
- java.util.Hashtable.reconstitutionPut
- org.apache.commons.collections.map.AbstractMapDecorator.equals
- java.util.AbstractMap.equals
- org.apache.commons.collections.map.LazyMap.get
- org.apache.commons.collections.functors.ChainedTransformer.transform
- org.apache.commons.collections.functors.InvokerTransformer.transform
- java.lang.reflect.Method.invoke
- java.lang.Runtime.exec
- */
复制代码
参考javasec/3. apache commons-collections中的反序列化
玩转Ysoserial-CommonsCollection的七种利用方式分析
java反序列化-ysoserial-调试分析总结篇(1) - tr1ple
|
|