CC gadget

整理了一些有关CC链的笔记,也算重新梳理一下

整理了一些有关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中用了LazyMapget()函数来调三个Transformer,再用重写了了readObjectAnnotationInvocationHandler来调get()

因为AnnotationInvocationHandler被ban,因此替换为BadAttributeValueExpException,即CC5

随后又衍生出CC6(HashSet)和CC7(Hashtable)增加CC链的适用性

CC2/CC3/CC4/CB1

CC2没用三个Transformer的组合,用的是7u21RCE,为此引入了TemplatesImpl,为了调用TemplatesImpl的任意方法(newTransformer)使用了InvokerTransformer来反射执行,TransformingComparatorcompare来调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 调的是InvokerTransformercompare,而CB1调的是BeanComparator的)

四个Transformer

InvokerTransformerInstantiateTransformer在CC链的地位类似,都是负责最后一步的RCE,

不过InstantiateTransformer在CC3和CC4中配合TemplatesImpl实例化造成7u21 RCE,

InvokerTransformer则在CC3其余5条链中使用,CC2中配合TemplatesImpl,1567配合ConstantTransformerChainedTransformer

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可以执行RuntimeInvokerTransformer可以凑出后续的getRuntime().exec("calc")部分,在ChainedTransformertransform方法里可以将上述两者拼接成一条完整的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");

image-20220222173028243

下面逐个介绍

ConstantTransformer

除了CC2都用到了

image-20220222170917126

transform方法会返回构造函数的参数iConstant。因此实例化时传入Runtime.class,也会返回一样的内容。只需要调用transform方法。

InvokerTransformer

12567

image-20220222171149845

image-20220222171155967

` transform用到了反射,执行了某个对象的某个方法,并且相关参数也是构造函数里的可控参数,因此也只需要调用transform`方法即可。

ChainedTransformer

除了CC2以外都用到了

image-20220222171324507

正好,ChainedTransformertransform函数会遍历构造函数中的Transformer数组,并调用数组中的每一个成员的transform方法,并且上一个成员调用返回的对象会作为下一个成员transform的参数,也就是说,只需要调用ChainedTransformer 的transform方法即可构造出Runtime.getRuntime().exec("calc")

那么问题来了,怎么触发ChainedTransformertransform方法?

InstantiateTransformer

在CC3和CC4中使用

image-20220226173146004

CC链调试——触发transform

如果用一个词来总结CC链,那一定是“transform”,不管怎么变化,用什么类来改写还是调用,最终都是要想办法调用四个transform里的某一个某几个transformtransform()函数。

TransformedMap

虽然没在CC链里出现,还是可以看一看找利用链的思路是什么样的

注:因为1.8删去了关键的setValue方法,因此只能在1.7下使用

思路

从挖洞的思路来讲此时应该搜索transform(关键字来寻找有相关调用的类,但既然是复现就直接跳过这一步,找到TransformedMap类,

TransformedMap类里有三个transform调用,keyTransformervalurTransformer都是Transformer类型且可控,所以可以将其值赋为ChainedTransformer,但是这三个方法都是protected不能直接调用,找到4个public方法,其中有一个put()方法,调用了transformKey以及transformValue,这两个方法又都调用了transform方法,所以TransformedMap类可以满足需求

只需要实例化一个TransforomedMap对象,然后调用对象的put方法,就可以RCE。

但是并不能在反序列化中触发,因为没有readObject()类,还需要一个重写readObject()类的方法,并能调用上述的方法。

一般来讲会采取向上回溯找方法transformKeytransformValuecheckSetValue)调用位置,或者全局搜索readObject()看看有没有哪个类直接就调用了这三个方法中的一个或者readObject中有可疑的操作,最后能够间接触发这几个方法。

从名字就能推断TransformedMap其实是Map类型,范围就可以扩大到引用了Map的readObject()类,根据摸索git得到TransformedMap里的每个entryset在调用setValue方法时会自动调用TransformedMap类的checkSetValue方法

img

因此就变成了找一个对Map类型的属性的entry进行了setValue操作的readObject()类,刚好sun.reflect.annotation.AnnotationInvocationHandler类就可以满足需求(但jdk1.8已经没有了setValue)

image-20220223115426157

但有一个条件,if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy))(其实就是var1不能被转化为var2即可)

从截图代码可知,需要知道this.type是什么,

image-20220223120302596

是一个可控的,需要继承Annotation,那就应该是Annotation的子类(所有注解类型的公用接口),于是找到java.lang.annotation.Retention.class这个注解类(map的键必须有”value”),但这个类的访问权限不是public,而是包访问权限,构造poc的时候只有通过反射机制来实例化它,至此构造结束。

调用链

sun.reflect.annotation.AnnotationInvocationHandler(readObject()setValue) –>

TransformedMap(checkSetValue 调用this.valueTransformerChainedTransformertransform()) –>

ChainedTransformer循环调用数组的transform() –>

ConstantTransformer and InvokerTransformer(RCE)

CC1–LazyMap+InvokerTransformer(3.1-3.2.1,jdk<1.8)

CommonsCollections版本主要是针对在ysoserial里的

思路

LazyMapthis.factory.transform(key),而this.factory可控,factoryChainedTransformer即可

image-20220223152541210

然后找调用get的地方,老朋友AnnotationInvocationHandlerinvoke符合条件,this.memberValues也是可控的(上个思路截图),传入LazyMap即可

image-20220223153224172

找触发AnnotationInvocationHandler.invoke()的地方

动态代理

被动态代理的对象调用任意方法都会通过对应的InvocationHandlerinvoke方法触发

(可以将InvocationHandler接口类看做一个中介类,中介类持有一个被代理对象即真实对象,在invoke()方法中调用了被代理对象相应的方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。

代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能,即无侵入式的代码扩展,不用修改源码)

所以此时只需要创建一个LazyMap的动态代理,动态代理调用某个方法,但是反序列化需要readObject,即找一个类,其readObject方法可以通过动态代理调用LazyMap的某个方法(和直接调用LazyMap某个方法需要满足的条件几乎是一样的,因为某个类的动态代理与它本身实现了同一个接口)

AnnotationInvocationHandlerreadObject方法会调用某个Map类型对象的entrySet()方法,而LazyMap以及他的动态代理都是Map类型,可以满足需求

image-20220223155214284

调用链

sun.reflect.annotation.AnnotationInvocationHandler(readObject()get) –>

LazyMap(this.factory.transform(key),即ChainedTransformertransform()) –>

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对象的利用链中的任意函数(getOutputPropertiesnewTransformer等等),就能造成RCE

思路

因此思路也有变化,只用到了InvokerTransformer(3.22被拉黑但是4.0又可以用了,然后又被拉黑了)

首先得想办法触发TemplatesImpl的函数,正好InvokerTransformer就可以用反射执行某个对象的任意方法,这里传入newTransformer方法

111

然后就是想办法调用transform方法,找到了TransformingComparator

image-20220224114904751

需要找一个调用其compare的类,找到PriorityQueue,其siftDownUsingComparator符合条件,把TransformingComparator放到PriorityQueue里就能调用到PriorityQueuecompare方法

image-20220224115125973

为什么选PriorityQueue,因为其重写了readObject,而且,会调用heapify(),可以一路调用到siftDownUsingComparator,并且可传入任意类型的对象,可以顺利引入TemplatesImpl

222

444

20

至此利用链结束

调用链

PriorityQueuereadObject调用自己的heapify,一直调用到siftDownUsingComparator ,方法内调用compare–>

InvokerTransformercomparethis.transformer.transform–>

InvokerTransformertransform被调用–>

一直被传递的TemplatesImplnewTransformerinvoke,进入7u21 RCE

21

/*
	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

image-20220224150652592

还是和CC2一样的,如果要TemplatesImplRCE,需要找newTransformer被调用的地方,InstantiateTransformertransform可以生成任意类的实例,能够顺利进入RCE利用点,

image-20220224150707144

InstantiateTransformer自己的transform又可以用CC1的LazyMap来调用ChainedTransformertransform调用,LazyMapentrySet方法又被AnnotationInvocationHandlerreadObject调用,构造完成。

 * Variation on CommonsCollections1 that uses InstantiateTransformer instead of InvokerTransformer.

CC4–PriorityQueue+InstantiateTransformer+TemplatesImpl(4.0,jdk<=7u21)

改进了CC2,类似CC3和CC2的结合。

PriorityQueuereadObject一路调下去调的是ChainedTransformer然后到InstantiateTransformer而不是InvokerTransformer,其他都是重复的内容

image-20220224154853119

 * 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()

image-20220224165241850

有一个toString方法,即TiedMapEntry

26

然后就调用到get了, LazyMap.get()被调用,后面的就很熟悉了,和CC1一样,还简单一些

image-20220224165337417

调用链

BadAttributeValueExpExceptionreadObject()–>

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来触发ChainedTransformertransform,不同的是在触发LazyMap.get()时用了不同的方法

思路

CC6用了HashSetreadObject()

image-20220224171522615

TiedMapEntry对象被带入put函数

30

put进入hash函数,

image-20220224172525489

hashCode函数中会调用恶意的TiedMapEntry对象自身的getValue函数

image-20220224172848602

getValue这里就会调用LazyMapget函数了

image-20220224173101482

image-20220224172601833

然后就是熟悉的LazyMap调用链了

调用链

HashSet.readObject()put–>

HashMap.put()hash() ,调hashCode()–>

TiedMapEntry.hashCode()getValue(),调this.map.get(this.key) –>

LazyMap(this.factory.transform(key),即ChainedTransformertransform()) –>

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)

思路

这次是HashtablereadObject()

image-20220224183806847

会调用readHashtable

image-20220224182847018

然后是reconstitutionPut,这里需要满足e.hash == hash ,才能到e.key.equals(key)equals(),因此需要构造hash相同的两个lazymap强制进行比较

:因为两个lazymap的hash相同,所以hashtable放进第二个lazymap时,会把第一个lazymapkey"yy"放到第二个lazymap中(首先lazymap.get(‘yy’)尝试从第二个lazymap中拿),此时将导致lazymap2中新添加yy->processImpl键值对,造成第二个lazymap空间大小为2,和第一个不一样,hash不同无法继续,改成lazyMap2.remove("yy")即可

image-20220224184517358

38

equal判断的时候就会去取值,即调用get(),进入LazyMap调用链

39

调用链

/*
    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