freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

Z3专栏 | Java代码审计之CommonCollection2分析
2021-12-15 21:29:44

七、CommonCollection2

本篇文章基础有:
队列
优先队列

分析

代码分析

首先还是先看下代码

这个和1、6两条链有点不一样了,这里没用Map,而是Queue

看下第一行代码

public static Object createTemplatesImpl(String command) throws Exception {
    return Boolean.parseBoolean(System.getProperty("properXalan", "false"))
    ?
    createTemplatesImpl(
        command, 
        Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"),
        Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), 
        Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")
    )
    :
    createTemplatesImpl(
        command, 
        TemplatesImpl.class, 
        AbstractTranslet.class, 
        TransformerFactoryImpl.class
    );
}

首先获得系统属性值properXalan
如果返回值为 true 则通过反射创建这三个Class对象
如果返回值为 false或者没有找到 properXalan 属性 , 则直接调用三个类的Class对象(java会自动为每一个类创建Class对象,使用类名.class就能获取到)

properXalan是什么?
不知道,网上搜这个全是关于这条链的分析,但都是互相抄来抄去,没人介绍properXalan,这个问题先跳过

继续看createTemplatesImpl方法

看不懂,反正是将cmd命令写入了templates
继续看下面代码

InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
PriorityQueue<Object> queue = new PriorityQueue(2, new TransformingComparator(transformer));

创建了一个InvokerTransformer,反射调用的方法是toString
创建了一个优先队列,大小为2,第二个参数看起来像是比较的,创建了一个用来比较优先级的Transformer
看下TransformingComparator构造函数


如图,将上面构造的transformer对象保存在了TransformingComparator对象的transformer变量里

然后在队列里连续添加了两个1
然后修改transformer的iMethodName为newTransformer,即transformer反射调用的是newTransformer方法
然后通过反射获取了queue对象的queue数组

将第一个元素设为templates
第二个元素为1

然后,,这,就结束了
好吧,再调试一遍反序列化的过程

调试分析反序列化过程

代码如下,反序列化时虽然报错,但是成功弹出计算器了

之前已经调试过很多次ObjectInputStream的readObject方法了
直接看ObjectInputStream的readSerialData吧,在这个方法内,通过递归反序列化出完整的对象
如图,通过desc可以看见当前正在被反序列化的对象

经过反复调试,最终发现
PriorityQueue对象的readObject方法内调用了heapify(这个函数是用来把数组转堆的,方便排序),导致弹出计算器
看一下代码

代码执行到了siftDownUsingComparator
如图

可以看见,这里的cmp就是之前代码里的TransformingComparator对象,
如图,调用了transform方法
回想一下,这个transformer对象是InvokerTransformer类型,反射方法是newTransformer
也就是这里会调用obj1的newTransformer方法,而obj是通过Gadgets.createTemplatesImpl创建的,包含了命令的对象

如图,就是这句代码,执行后弹出计算器

好,总结下

总结

首先是PriorityQueue在反序列化时
如图,会先把队列内的数据存到数组中,然后调用heapify方法,将数据按堆排列

在构建堆的过程中,会通过TransformingComparator比较两个元素,
而TransformingComparator会对比较的两个数据通过transformer进行处理,而transformer正是前面构造好的,反射调用newTransformer方法的InvokerTransformer对象
而队列中的两个元素分别是TemplatesImpl对象和1
在对他们比较时,InvokerTransformer对象调用了TemplatesImpl的newTransformer方法,导致rce

所以,这条利用链分为几部分
一、构造TemplatesImpl对象,在被调用newTransformer方法时,就会导致rce
二、构造InvokerTransformer对象,通过反射调用对象的newTransformer方法
三、构造PriorityQueue对象,PriorityQueue对象在反序列化时需要构建堆,就需要比较元素,比较元素时通过TransformingComparator对象比较,而TransformingComparator对象比较时会调用Transformer对象,而Transformer对象是第二步构造的InvokerTransformer对象

所以利用链是:
PriorityQueue的readObject->TransformingComparator的cmpare->InvokerTransformer通过反射调用TemplatesImpl的newTransformer方法->导致rce

填坑

上面还有个问题没解决,TemplatesImpl对象是什么?怎么构建的?为什么调用它的newTransformer方法会命令执行
写代码调试一下,,结果,,TemplatesImp对象不能调用newTransformer方法?

代码改写一下,可以运行了,但是不会弹计算器

反复对比刚才反序列化的场景,感觉一毛一样啊
又仔细对比了下
有两个TemplatesImpl包,一个是ysoserial自带的,一个是jdk的
而jdk的包不允许直接import(好像是JDK 9开始的模块封装)

看一下com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl包,,结果里面有newTransformer方法,而且是public,看来是因为模块封装,导致不能调用的

再调试下反序列化过程,发现红框这里弹出了计算器,,看起来是创建了一个对象,,对象名是一串数字

而在执行蓝框之前,_class为null
看一下defineTransletClasses方法,在这里,从_bytecodes中加载了_class

回想一下,之前在Gadgets.createTemplatesImpl方法中设置过_bytecodes
所以是因为TemplateImpl的getTransletInstance方法,实例化了Gadgets.createTemplatesImpl中构造的_bytecodes,导致rce

所以现在看下Gadgets.createTemplatesImpl,如何构造的_bytecodes
之前这里没看懂,调试了下ClassPool对象,,越看越复杂,,网上查了下,原来这个是属于Javassist库
Javassist库是一个Java字节码类库,java程序编译好后都是后缀为.class文件,这就是由字节码组成的
通过Javassist库可以处理字节码
现在再来看下这段代码,它是如何生成字节码

ClassPool pool = ClassPool.getDefault(); // getDefault 方法返回一个单例模式的ClassPool对象
pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); // 把AbstractTranslet类的路径添加进pool对象(一会下面创建AbstractTranslet对象会用到)
CtClass clazz = pool.makeClass(String.valueOf(System.nanoTime())); // 创建一个空类,类名是系统时间(纳秒)
String cmd = DirectiveProcessor.process(command); // 通过需要执行的命令,构造了一段代码
clazz.makeClassInitializer().insertAfter(cmd); // 通过makeClassInitializer创建了静态代码块,并将上一步构造好的代码,插入到静态代码块
CtClass superC = pool.get(AbstractTranslet.class.getName()); // 通过AbstractTranslet创建了CtClass对象
clazz.setSuperclass(superC); // 设置包含命令执行代码的对象的父类为AbstractTranslet
byte[] classBytes = clazz.toBytecode(); // 包命令执行对象转bytecode即字节码

这个CtClass和Class有点像,都是用来储存类信息的,但是Class对象正常是不能由开发者创建的,是由jvm创建的,开发者只能通过.class属性值或getClass、forName方法去获取到
分析下过程
这里创建了CtClass对象,然后写入静态代码块,设置父类为AbstractTranslet,然后转字节码
静态代码块在讲反射时讲过,当加载类时,会先执行静态代码块里的内容
设置父类为AbstractTranslet是为了在TemplatesImpl中可以执行newTransformer时可以正常创建AbstractTranslet对象

还有个问题就是,怎么构造静态代码块里内容的?
看下DirectiveProcessor.process的代码

构造好了之后是这样的

java.lang.Runtime.getRuntime().exec("calc");

ok,至此,完全明白了CommonsCollections2利用链的利用细节

现在想一下,为什么我刚开始写的代码不能触发rce?

因为这里构造的templates对象,虽然包含了字节码,但是字节码是通过反射,illegal的方式写入的,所以这个templates对象是不完整的
templates对象内_tfactory的值为null,在加载字节码时,会用到它,所以这时就会异常退出
而templates对象反序列化时,不仅读取出了字节码信息,还创建了_tfactory对象(反序列化时创建了完整的templates对象)

再总结下

再总结

  1. 首先通过Javassist库,创建一个字节码类,这个类包含静态代码块,里面是命令执行代码,它的父类是AbstractTranslet

  2. 创建个空的TemplatesImpl对象,把它的_bytecode值改为1中的字节码

  3. 构造InvokeTransformer对象,可以通过反射调用newTransformer方法

  4. 构造优先级队列,添加两个元素,创建TransformingComparator对象,而TransformingComparator对象内部实现是通过InvokeTransformer对象对象

所以在反序列化优先级队列时,,会重新构建堆->调用TransformingComparator的compare方法->InvokeTransformer的transform方法->TemplatesImpl的newTransformer方法->getTransletInstance方法加载了字节码,实例化对象->加载了字节码中的恶意静态代码块,导致rce

ok,至此CommonCollection2学习完毕

本文作者:, 转载请注明来自FreeBuf.COM

# java漏洞 # java # java反序列化 # Java代码审计 # JAVA安全
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
评论 按热度排序

登录/注册后在FreeBuf发布内容哦

相关推荐
\
  • 0 文章数
  • 0 评论数
  • 0 关注者
文章目录
登录 / 注册后在FreeBuf发布内容哦
收入专辑