整理了一些有关CC链的笔记,也算重新梳理一下
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
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一样的,如果要TemplatesImpl
RCE,需要找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()
–>
` ChainedTransformer.transform()` –>
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中的反序列化